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>