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