blob: ceb77247a7fa032d9b13af687738d834ff8f0760 [file] [log] [blame]
Tobin Ehlis5ade0692016-10-05 17:18:15 -06001#!/usr/bin/python3 -i
2
3import sys
4import xml.etree.ElementTree as etree
5import urllib2
6#import codecs
7
8#############################
9# spec.py script
10#
11# Overview - this script is intended to generate validation error codes and message strings from the xhtml version of
12# the specification. In addition to generating the header file, it provides a number of corrollary services to aid in
13# generating/updating the header.
14#
15# Ideal flow - Not there currently, but the ideal flow for this script would be that you run the script, it pulls the
16# latest spec, compares it to the current set of generated error codes, and makes any updates as needed
17#
18# Current flow - the current flow acheives all of the ideal flow goals, but with more steps than are desired
19# 1. Get the spec - right now spec has to be manually generated or pulled from the web
20# 2. Generate header from spec - This is done in a single command line
21# 3. Generate database file from spec - Can be done along with step #2 above, the database file contains a list of
22# all error enums and message strings, along with some other info on if those errors are implemented/tested
23# 4. Update header using a given database file as the root and a new spec file as goal - This makes sure that existing
24# errors keep the same enum identifier while also making sure that new errors get a unique_id that continues on
25# from the end of the previous highest unique_id.
26#
27# TODO:
28# 1. Improve string matching to add more automation for figuring out which messages are changed vs. completely new
29#
30#
31#############################
32
33
34spec_filename = "vkspec.html" # can override w/ '-spec <filename>' option
35out_filename = "vk_validation_error_messages.h" # can override w/ '-out <filename>' option
36db_filename = "vk_validation_error_database.txt" # can override w/ '-gendb <filename>' option
37gen_db = False # set to True when '-gendb <filename>' option provided
38spec_compare = False # set to True with '-compare <db_filename>' option
39# This is the root spec link that is used in error messages to point users to spec sections
40spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html"
41# After the custom validation error message, this is the prefix for the standard message that includes the
42# spec valid usage language as well as the link to nearest section of spec to that language
43error_msg_prefix = "For more information refer to Vulkan Spec Section "
44ns = {'ns': 'http://www.w3.org/1999/xhtml'}
45# Dict of new enum values that should be forced to remap to old handles, explicitly set by -remap option
46remap_dict = {}
47
48def printHelp():
49 print "Usage: python spec.py [-spec <specfile.html>] [-out <headerfile.h>] [-gendb <databasefile.txt>] [-compare <databasefile.txt>] [-update] [-remap <new_id-old_id,count>] [-help]"
50 print "\n Default script behavior is to parse the specfile and generate a header of unique error enums and corresponding error messages based on the specfile.\n"
51 print " Default specfile is from online at %s" % (spec_url)
52 print " Default headerfile is %s" % (out_filename)
53 print " Default databasefile is %s" % (db_filename)
54 print "\nIf '-gendb' option is specified then a database file is generated to default file or <databasefile.txt> if supplied. The database file stores"
55 print " the list of enums and their error messages."
56 print "\nIf '-compare' option is specified then the given database file will be read in as the baseline for generating the new specfile"
57 print "\nIf '-update' option is specified this triggers the master flow to automate updating header and database files using default db file as baseline"
58 print " and online spec file as the latest. The default header and database files will be updated in-place for review and commit to the git repo."
59 print "\nIf '-remap' option is specified it supplies forced remapping from new enum ids to old enum ids. This should only be specified along with -update"
60 print " option. Starting at newid and remapping to oldid, count ids will be remapped. Default count is '1' and use ':' to specify multiple remappings."
61
62class Specification:
63 def __init__(self):
64 self.tree = None
65 self.val_error_dict = {} # string for enum is key that references text for output message
66 self.error_db_dict = {} # dict of previous error values read in from database file
67 self.delimiter = '~^~' # delimiter for db file
68 self.copyright = """/* THIS FILE IS GENERATED. DO NOT EDIT. */
69
70/*
71 * Vulkan
72 *
73 * Copyright (c) 2016 Google Inc.
74 *
75 * Licensed under the Apache License, Version 2.0 (the "License");
76 * you may not use this file except in compliance with the License.
77 * You may obtain a copy of the License at
78 *
79 * http://www.apache.org/licenses/LICENSE-2.0
80 *
81 * Unless required by applicable law or agreed to in writing, software
82 * distributed under the License is distributed on an "AS IS" BASIS,
83 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
84 * See the License for the specific language governing permissions and
85 * limitations under the License.
86 *
87 * Author: Tobin Ehlis <tobine@google.com>
88 */"""
89 def _checkInternetSpec(self):
90 """Verify that we can access the spec online"""
91 try:
92 online = urllib2.urlopen(spec_url,timeout=1)
93 return True
94 except urllib2.URLError as err:
95 return False
96 return False
97 def loadFile(self, online=True, spec_file=spec_filename):
98 """Load an API registry XML file into a Registry object and parse it"""
99 # Check if spec URL is available
100 if (online and self._checkInternetSpec()):
101 print "Using spec from online at %s" % (spec_url)
102 self.tree = etree.parse(urllib2.urlopen(spec_url))
103 else:
104 print "Using local spec %s" % (spec_file)
105 self.tree = etree.parse(spec_file)
106 #self.tree.write("tree_output.xhtml")
107 #self.tree = etree.parse("tree_output.xhtml")
108 self.parseTree()
109 def updateDict(self, updated_dict):
110 """Assign internal dict to use updated_dict"""
111 self.val_error_dict = updated_dict
112 def parseTree(self):
113 """Parse the registry Element, once created"""
114 print "Parsing spec file..."
115 valid_usage = False # are we under a valid usage branch?
116 unique_enum_id = 0
117 self.root = self.tree.getroot()
118 #print "ROOT: %s" % self.root
119 prev_heading = '' # Last seen section heading or sub-heading
120 prev_link = '' # Last seen link id within the spec
121 for tag in self.root.iter(): # iterate down tree
122 # Grab most recent section heading and link
123 if tag.tag in ['{http://www.w3.org/1999/xhtml}h2', '{http://www.w3.org/1999/xhtml}h3']:
124 if tag.get('class') != 'title':
125 continue
126 #print "Found heading %s" % (tag.tag)
127 prev_heading = "".join(tag.itertext())
128 # Insert a space between heading number & title
129 sh_list = prev_heading.rsplit('.', 1)
130 prev_heading = '. '.join(sh_list)
131 prev_link = tag[0].get('id')
132 #print "Set prev_heading %s to have link of %s" % (prev_heading.encode("ascii", "ignore"), prev_link.encode("ascii", "ignore"))
133 elif tag.tag == '{http://www.w3.org/1999/xhtml}a': # grab any intermediate links
134 if tag.get('id') != None:
135 prev_link = tag.get('id')
136 #print "Updated prev link to %s" % (prev_link)
137 elif tag.tag == '{http://www.w3.org/1999/xhtml}strong': # identify valid usage sections
138 if None != tag.text and 'Valid Usage' in tag.text:
139 valid_usage = True
140 else:
141 valid_usage = False
142 elif tag.tag == '{http://www.w3.org/1999/xhtml}li' and valid_usage: # grab actual valid usage requirements
143 error_msg_str = "%s '%s' which states '%s' (%s#%s)" % (error_msg_prefix, prev_heading, "".join(tag.itertext()).replace('\n', ''), spec_url, prev_link)
144 # Some txt has multiple spaces so split on whitespace and join w/ single space
145 error_msg_str = " ".join(error_msg_str.split())
146 enum_str = "VALIDATION_ERROR_%05d" % (unique_enum_id)
147 # TODO : '\' chars in spec error messages are most likely bad spec txt that needs to be updated
148 self.val_error_dict[enum_str] = error_msg_str.encode("ascii", "ignore").replace("\\", "/")
149 unique_enum_id = unique_enum_id + 1
150 #print "dict contents: %s:" % (self.val_error_dict)
151 #print "Added enum to dict: %s" % (enum_str.encode("ascii", "ignore"))
152 #print "Validation Error Dict has a total of %d unique errors and contents are:\n%s" % (unique_enum_id, self.val_error_dict)
153 def genHeader(self, header_file):
154 """Generate a header file based on the contents of a parsed spec"""
155 print "Generating header %s..." % (header_file)
156 file_contents = []
157 file_contents.append(self.copyright)
158 file_contents.append('\n#pragma once')
Tobin Ehlisbf98b692016-10-06 12:58:06 -0600159 file_contents.append('#include <unordered_map>')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600160 file_contents.append('\n// enum values for unique validation error codes')
161 file_contents.append('// Corresponding validation error message for each enum is given in the mapping table below')
162 file_contents.append('// When a given error occurs, these enum values should be passed to the as the messageCode')
163 file_contents.append('// parameter to the PFN_vkDebugReportCallbackEXT function')
164 enum_decl = ['enum UNIQUE_VALIDATION_ERROR_CODE {']
Tobin Ehlisbf98b692016-10-06 12:58:06 -0600165 error_string_map = ['static std::unordered_map<int, char const *const> validation_error_map{']
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600166 for enum in sorted(self.val_error_dict):
167 #print "Header enum is %s" % (enum)
168 enum_decl.append(' %s = %d,' % (enum, int(enum.split('_')[-1])))
169 error_string_map.append(' {%s, "%s"},' % (enum, self.val_error_dict[enum]))
170 enum_decl.append('};')
171 error_string_map.append('};')
172 file_contents.extend(enum_decl)
173 file_contents.append('\n// Mapping from unique validation error enum to the corresponding error message')
174 file_contents.append('// The error message should be appended to the end of a custom error message that is passed')
175 file_contents.append('// as the pMessage parameter to the PFN_vkDebugReportCallbackEXT function')
176 file_contents.extend(error_string_map)
177 #print "File contents: %s" % (file_contents)
178 with open(header_file, "w") as outfile:
179 outfile.write("\n".join(file_contents))
180 def analyze(self):
181 """Print out some stats on the valid usage dict"""
182 # Create dict for # of occurences of identical strings
183 str_count_dict = {}
184 unique_id_count = 0
185 for enum in self.val_error_dict:
186 err_str = self.val_error_dict[enum]
187 if err_str in str_count_dict:
188 #print "Found repeat error string"
189 str_count_dict[err_str] = str_count_dict[err_str] + 1
190 else:
191 str_count_dict[err_str] = 1
192 unique_id_count = unique_id_count + 1
193 print "Processed %d unique_ids" % (unique_id_count)
194 repeat_string = 0
195 for es in str_count_dict:
196 if str_count_dict[es] > 1:
197 repeat_string = repeat_string + 1
198 #print "String '%s' repeated %d times" % (es, repeat_string)
199 print "Found %d repeat strings" % (repeat_string)
200 def genDB(self, db_file):
201 """Generate a database of check_enum, check_coded?, testname, error_string"""
202 db_lines = []
203 # Write header for database file
204 db_lines.append("# This is a database file with validation error check information")
205 db_lines.append("# Comments are denoted with '#' char")
206 db_lines.append("# The format of the lines is:")
207 db_lines.append("# <error_enum>%s<check_implemented>%s<testname>%s<errormsg>" % (self.delimiter, self.delimiter, self.delimiter))
208 db_lines.append("# error_enum: Unique error enum for this check of format VALIDATION_ERROR_<uniqueid>")
209 db_lines.append("# check_implemented: 'Y' if check has been implemented in layers, 'U' for unknown, or 'N' for not implemented")
210 db_lines.append("# testname: Name of validation test for this check, 'Unknown' for unknown, or 'None' if not implmented")
211 db_lines.append("# errormsg: The unique error message for this check that includes spec language and link")
212 for enum in sorted(self.val_error_dict):
213 # Default to unknown if check or test are implemented, then update below if appropriate
214 implemented = 'U'
215 testname = 'Unknown'
216 # If we have an existing db entry for this enum, use its implemented/testname values
217 if enum in self.error_db_dict:
218 implemented = self.error_db_dict[enum]['check_implemented']
219 testname = self.error_db_dict[enum]['testname']
220 #print "delimiter: %s, id: %s, str: %s" % (self.delimiter, enum, self.val_error_dict[enum])
221 # No existing entry so default to N for implemented and None for testname
222 db_lines.append("%s%s%s%s%s%s%s" % (enum, self.delimiter, implemented, self.delimiter, testname, self.delimiter, self.val_error_dict[enum]))
223 print "Generating database file %s" % (db_file)
224 with open(db_file, "w") as outfile:
225 outfile.write("\n".join(db_lines))
226 def readDB(self, db_file):
227 """Read a db file into a dict, format of each line is <enum><implemented Y|N?><testname><errormsg>"""
228 db_dict = {} # This is a simple db of just enum->errormsg, the same as is created from spec
229 max_id = 0
230 with open(db_file, "r") as infile:
231 for line in infile:
232 if line.startswith('#'):
233 continue
234 line = line.strip()
235 db_line = line.split(self.delimiter)
Tobin Ehlis802b16e2016-10-11 09:37:19 -0600236 if len(db_line) != 4:
237 print "ERROR: Bad database line doesn't have 4 elements: %s" % (line)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600238 error_enum = db_line[0]
239 implemented = db_line[1]
240 testname = db_line[2]
241 error_str = db_line[3]
242 db_dict[error_enum] = error_str
243 # Also read complete database contents into our class var for later use
244 self.error_db_dict[error_enum] = {}
245 self.error_db_dict[error_enum]['check_implemented'] = implemented
246 self.error_db_dict[error_enum]['testname'] = testname
247 self.error_db_dict[error_enum]['error_string'] = error_str
248 unique_id = int(db_line[0].split('_')[-1])
249 if unique_id > max_id:
250 max_id = unique_id
251 return (db_dict, max_id)
252 # Compare unique ids from original database to data generated from updated spec
253 # 1. If a new id and error code exactly match original, great
254 # 2. If new id is not in original, but exact error code is, need to use original error code
255 # 3. If new id and new error are not in original, make sure new id picks up from end of original list
256 # 4. If new id in original, but error strings don't match then:
257 # 4a. If error string has exact match in original, update new to use original
258 # 4b. If error string not in original, may be updated error message, manually address
259 def compareDB(self, orig_db_dict, max_id):
260 """Compare orig database dict to new dict, report out findings, and return potential new dict for parsed spec"""
261 # First create reverse dicts of err_strings to IDs
262 next_id = max_id + 1
263 orig_err_to_id_dict = {}
264 # Create an updated dict in-place that will be assigned to self.val_error_dict when done
265 updated_val_error_dict = {}
266 for enum in orig_db_dict:
267 orig_err_to_id_dict[orig_db_dict[enum]] = enum
268 new_err_to_id_dict = {}
269 for enum in self.val_error_dict:
270 new_err_to_id_dict[self.val_error_dict[enum]] = enum
271 ids_parsed = 0
272 # Now parse through new dict and figure out what to do with non-matching things
273 for enum in sorted(self.val_error_dict):
274 ids_parsed = ids_parsed + 1
275 enum_list = enum.split('_') # grab sections of enum for use below
276 if enum in orig_db_dict:
277 if self.val_error_dict[enum] == orig_db_dict[enum]:
278 #print "Exact match for enum %s" % (enum)
279 # Nothing to see here
280 if enum in updated_val_error_dict:
281 print "ERROR: About to overwrite entry for %s" % (enum)
282 updated_val_error_dict[enum] = self.val_error_dict[enum]
283 elif self.val_error_dict[enum] in orig_err_to_id_dict:
284 # Same value w/ different error id, need to anchor to original id
285 #print "Need to switch new id %s to original id %s" % (enum, orig_err_to_id_dict[self.val_error_dict[enum]])
286 # Update id at end of new enum to be same id from original enum
287 enum_list[-1] = orig_err_to_id_dict[self.val_error_dict[enum]].split('_')[-1]
288 new_enum = "_".join(enum_list)
289 if new_enum in updated_val_error_dict:
290 print "ERROR: About to overwrite entry for %s" % (new_enum)
291 updated_val_error_dict[new_enum] = self.val_error_dict[enum]
292 else:
293 # No error match:
294 # First check if only link has changed, in which case keep ID but update message
295 orig_msg_list = orig_db_dict[enum].split('(', 1)
296 new_msg_list = self.val_error_dict[enum].split('(', 1)
297 if orig_msg_list[0] == new_msg_list[0]: # Msg is same bug link has changed, keep enum & update msg
298 print "NOTE: Found that only spec link changed for %s so keeping same id w/ new link" % (enum)
299 updated_val_error_dict[enum] = self.val_error_dict[enum]
300 # Second, check if user is forcing remap here
301 elif enum_list[-1] in remap_dict:
302 enum_list[-1] = remap_dict[enum_list[-1]]
303 new_enum = "_".join(enum_list)
304 print "NOTE: Using user-supplied remap to force %s to be %s" % (enum, new_enum)
305 updated_val_error_dict[new_enum] = self.val_error_dict[enum]
306 # Finally, this seems to be a new error so need to pick it up from end of original unique ids & flag for review
307 else:
308 enum_list[-1] = "%05d" % (next_id)
309 new_enum = "_".join(enum_list)
310 next_id = next_id + 1
311 print "MANUALLY VERIFY: Updated new enum %s to be unique %s. Make sure new error msg is actually unique and not just changed" % (enum, new_enum)
312 print " New error string: %s" % (self.val_error_dict[enum])
313 if new_enum in updated_val_error_dict:
314 print "ERROR: About to overwrite entry for %s" % (new_enum)
315 updated_val_error_dict[new_enum] = self.val_error_dict[enum]
316 else: # new enum is not in orig db
317 if self.val_error_dict[enum] in orig_err_to_id_dict:
318 #print "New enum %s not in orig dict, but exact error message matches original unique id %s" % (enum, orig_err_to_id_dict[self.val_error_dict[enum]])
319 # Update new unique_id to use original
320 enum_list[-1] = orig_err_to_id_dict[self.val_error_dict[enum]].split('_')[-1]
321 new_enum = "_".join(enum_list)
322 if new_enum in updated_val_error_dict:
323 print "ERROR: About to overwrite entry for %s" % (new_enum)
324 updated_val_error_dict[new_enum] = self.val_error_dict[enum]
325 else:
326 enum_list[-1] = "%05d" % (next_id)
327 new_enum = "_".join(enum_list)
328 next_id = next_id + 1
329 print "Completely new id and error code, update new id from %s to unique %s" % (enum, new_enum)
330 if new_enum in updated_val_error_dict:
331 print "ERROR: About to overwrite entry for %s" % (new_enum)
332 updated_val_error_dict[new_enum] = self.val_error_dict[enum]
333 # Assign parsed dict to be the udpated dict based on db compare
334 print "In compareDB parsed %d entries" % (ids_parsed)
335 return updated_val_error_dict
336 def validateUpdateDict(self, update_dict):
337 """Compare original dict vs. update dict and make sure that all of the checks are still there"""
338 # Currently just make sure that the same # of checks as the original checks are there
339 #orig_ids = {}
340 orig_id_count = len(self.val_error_dict)
341 #update_ids = {}
342 update_id_count = len(update_dict)
343 if orig_id_count != update_id_count:
344 print "Original dict had %d unique_ids, but updated dict has %d!" % (orig_id_count, update_id_count)
345 return False
346 print "Original dict and updated dict both have %d unique_ids. Great!" % (orig_id_count)
347 return True
348 # TODO : include some more analysis
349
350# User passes in arg of form <new_id1>-<old_id1>[,count1]:<new_id2>-<old_id2>[,count2]:...
351# new_id# = the new enum id that was assigned to an error
352# old_id# = the previous enum id that was assigned to the same error
353# [,count#] = The number of ids to remap starting at new_id#=old_id# and ending at new_id[#+count#-1]=old_id[#+count#-1]
354# If not supplied, then ,1 is assumed, which will only update a single id
355def updateRemapDict(remap_string):
356 """Set up global remap_dict based on user input"""
357 remap_list = remap_string.split(":")
358 for rmap in remap_list:
359 count = 1 # Default count if none supplied
360 id_count_list = rmap.split(',')
361 if len(id_count_list) > 1:
362 count = int(id_count_list[1])
363 new_old_id_list = id_count_list[0].split('-')
364 for offset in range(count):
365 remap_dict["%05d" % (int(new_old_id_list[0]) + offset)] = "%05d" % (int(new_old_id_list[1]) + offset)
366 for new_id in sorted(remap_dict):
367 print "Set to remap new id %s to old id %s" % (new_id, remap_dict[new_id])
368
369if __name__ == "__main__":
370 i = 1
371 use_online = True # Attempt to grab spec from online by default
372 update_option = False
373 while (i < len(sys.argv)):
374 arg = sys.argv[i]
375 i = i + 1
376 if (arg == '-spec'):
377 spec_filename = sys.argv[i]
378 # If user specifies local specfile, skip online
379 use_online = False
380 i = i + 1
381 elif (arg == '-out'):
382 out_filename = sys.argv[i]
383 i = i + 1
384 elif (arg == '-gendb'):
385 gen_db = True
386 # Set filename if supplied, else use default
387 if i < len(sys.argv) and not sys.argv[i].startswith('-'):
388 db_filename = sys.argv[i]
389 i = i + 1
390 elif (arg == '-compare'):
391 db_filename = sys.argv[i]
392 spec_compare = True
393 i = i + 1
394 elif (arg == '-update'):
395 update_option = True
396 spec_compare = True
397 gen_db = True
398 elif (arg == '-remap'):
399 updateRemapDict(sys.argv[i])
400 i = i + 1
401 elif (arg in ['-help', '-h']):
402 printHelp()
403 sys.exit()
404 if len(remap_dict) > 1 and not update_option:
405 print "ERROR: '-remap' option can only be used along with '-update' option. Exiting."
406 sys.exit()
407 spec = Specification()
408 spec.loadFile(use_online, spec_filename)
409 #spec.parseTree()
410 #spec.genHeader(out_filename)
411 spec.analyze()
412 if (spec_compare):
413 # Read in old spec info from db file
414 (orig_db_dict, max_id) = spec.readDB(db_filename)
415 # New spec data should already be read into self.val_error_dict
416 updated_dict = spec.compareDB(orig_db_dict, max_id)
417 update_valid = spec.validateUpdateDict(updated_dict)
418 if update_valid:
419 spec.updateDict(updated_dict)
420 else:
421 sys.exit()
422 if (gen_db):
423 spec.genDB(db_filename)
424 print "Writing out file (-out) to '%s'" % (out_filename)
425 spec.genHeader(out_filename)
426
427##### Example dataset
428# <div class="sidebar">
429# <div class="titlepage">
430# <div>
431# <div>
432# <p class="title">
433# <strong>Valid Usage</strong> # When we get to this guy, we know we're under interesting sidebar
434# </p>
435# </div>
436# </div>
437# </div>
438# <div class="itemizedlist">
439# <ul class="itemizedlist" style="list-style-type: disc; ">
440# <li class="listitem">
441# <em class="parameter">
442# <code>device</code>
443# </em>
444# <span class="normative">must</span> be a valid
445# <code class="code">VkDevice</code> handle
446# </li>
447# <li class="listitem">
448# <em class="parameter">
449# <code>commandPool</code>
450# </em>
451# <span class="normative">must</span> be a valid
452# <code class="code">VkCommandPool</code> handle
453# </li>
454# <li class="listitem">
455# <em class="parameter">
456# <code>flags</code>
457# </em>
458# <span class="normative">must</span> be a valid combination of
459# <code class="code">
460# <a class="link" href="#VkCommandPoolResetFlagBits">VkCommandPoolResetFlagBits</a>
461# </code> values
462# </li>
463# <li class="listitem">
464# <em class="parameter">
465# <code>commandPool</code>
466# </em>
467# <span class="normative">must</span> have been created, allocated, or retrieved from
468# <em class="parameter">
469# <code>device</code>
470# </em>
471# </li>
472# <li class="listitem">All
473# <code class="code">VkCommandBuffer</code>
474# objects allocated from
475# <em class="parameter">
476# <code>commandPool</code>
477# </em>
478# <span class="normative">must</span> not currently be pending execution
479# </li>
480# </ul>
481# </div>
482# </div>
483##### Second example dataset
484# <div class="sidebar">
485# <div class="titlepage">
486# <div>
487# <div>
488# <p class="title">
489# <strong>Valid Usage</strong>
490# </p>
491# </div>
492# </div>
493# </div>
494# <div class="itemizedlist">
495# <ul class="itemizedlist" style="list-style-type: disc; ">
496# <li class="listitem">The <em class="parameter"><code>queueFamilyIndex</code></em> member of any given element of <em class="parameter"><code>pQueueCreateInfos</code></em> <span class="normative">must</span> be unique within <em class="parameter"><code>pQueueCreateInfos</code></em>
497# </li>
498# </ul>
499# </div>
500# </div>
501# <div class="sidebar">
502# <div class="titlepage">
503# <div>
504# <div>
505# <p class="title">
506# <strong>Valid Usage (Implicit)</strong>
507# </p>
508# </div>
509# </div>
510# </div>
511# <div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">
512#<em class="parameter"><code>sType</code></em> <span class="normative">must</span> be <code class="code">VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO</code>
513#</li><li class="listitem">
514#<em class="parameter"><code>pNext</code></em> <span class="normative">must</span> be <code class="literal">NULL</code>
515#</li><li class="listitem">
516#<em class="parameter"><code>flags</code></em> <span class="normative">must</span> be <code class="literal">0</code>
517#</li><li class="listitem">
518#<em class="parameter"><code>pQueueCreateInfos</code></em> <span class="normative">must</span> be a pointer to an array of <em class="parameter"><code>queueCreateInfoCount</code></em> valid <code class="code">VkDeviceQueueCreateInfo</code> structures
519#</li>