blob: 5ed7d49920007d23657e5425be323b968ca99ad8 [file] [log] [blame]
Tobin Ehlis3198ba32017-04-19 17:30:52 -06001#!/usr/bin/python -i
Tobin Ehlis5ade0692016-10-05 17:18:15 -06002
3import sys
4import xml.etree.ElementTree as etree
5import urllib2
Tobin Ehlis3198ba32017-04-19 17:30:52 -06006from bs4 import BeautifulSoup
Tobin Ehlis5ade0692016-10-05 17:18:15 -06007
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
Tobin Ehlis5ade0692016-10-05 17:18:15 -060029#
30#############################
31
32
33spec_filename = "vkspec.html" # can override w/ '-spec <filename>' option
34out_filename = "vk_validation_error_messages.h" # can override w/ '-out <filename>' option
35db_filename = "vk_validation_error_database.txt" # can override w/ '-gendb <filename>' option
36gen_db = False # set to True when '-gendb <filename>' option provided
37spec_compare = False # set to True with '-compare <db_filename>' option
38# This is the root spec link that is used in error messages to point users to spec sections
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -060039#old_spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html"
Tobin Ehlisa55b1d42017-04-04 12:23:48 -060040spec_url = "https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html"
Tobin Ehlis5ade0692016-10-05 17:18:15 -060041# 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'}
Mark Lobodzinski629d47b2016-10-18 13:34:58 -060045validation_error_enum_name = "VALIDATION_ERROR_"
Tobin Ehlis5ade0692016-10-05 17:18:15 -060046# Dict of new enum values that should be forced to remap to old handles, explicitly set by -remap option
47remap_dict = {}
48
49def printHelp():
Tobin Ehlis3198ba32017-04-19 17:30:52 -060050 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]")
51 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")
52 print (" Default specfile is from online at %s" % (spec_url))
53 print (" Default headerfile is %s" % (out_filename))
54 print (" Default databasefile is %s" % (db_filename))
55 print ("\nIf '-gendb' option is specified then a database file is generated to default file or <databasefile.txt> if supplied. The database file stores")
56 print (" the list of enums and their error messages.")
57 print ("\nIf '-compare' option is specified then the given database file will be read in as the baseline for generating the new specfile")
58 print ("\nIf '-update' option is specified this triggers the master flow to automate updating header and database files using default db file as baseline")
59 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.")
60 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")
61 print (" option. Starting at newid and remapping to oldid, count ids will be remapped. Default count is '1' and use ':' to specify multiple remappings.")
Tobin Ehlis5ade0692016-10-05 17:18:15 -060062
63class Specification:
64 def __init__(self):
65 self.tree = None
Tobin Ehlise7560e72016-10-19 15:59:38 -060066 self.val_error_dict = {} # string for enum is key that references 'error_msg' and 'api'
Tobin Ehlis5ade0692016-10-05 17:18:15 -060067 self.error_db_dict = {} # dict of previous error values read in from database file
68 self.delimiter = '~^~' # delimiter for db file
Tobin Ehlis2a176b12017-01-11 16:18:20 -070069 self.implicit_count = 0
Tobin Ehlisa55b1d42017-04-04 12:23:48 -060070 # Global dicts used for tracking spec updates from old to new VUs
71 self.orig_full_msg_dict = {} # Original full error msg to ID mapping
72 self.orig_no_link_msg_dict = {} # Pair of API,Original msg w/o spec link to ID list mapping
73 self.orig_core_msg_dict = {} # Pair of API,Original core msg (no link or section) to ID list mapping
74 self.last_mapped_id = -10 # start as negative so we don't hit an accidental sequence
75 self.orig_test_imp_enums = set() # Track old enums w/ tests and/or implementation to flag any that aren't carried fwd
Tobin Ehlis5ade0692016-10-05 17:18:15 -060076 self.copyright = """/* THIS FILE IS GENERATED. DO NOT EDIT. */
77
78/*
79 * Vulkan
80 *
81 * Copyright (c) 2016 Google Inc.
Mark Lobodzinski629d47b2016-10-18 13:34:58 -060082 * Copyright (c) 2016 LunarG, Inc.
Tobin Ehlis5ade0692016-10-05 17:18:15 -060083 *
84 * Licensed under the Apache License, Version 2.0 (the "License");
85 * you may not use this file except in compliance with the License.
86 * You may obtain a copy of the License at
87 *
88 * http://www.apache.org/licenses/LICENSE-2.0
89 *
90 * Unless required by applicable law or agreed to in writing, software
91 * distributed under the License is distributed on an "AS IS" BASIS,
92 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
93 * See the License for the specific language governing permissions and
94 * limitations under the License.
95 *
96 * Author: Tobin Ehlis <tobine@google.com>
97 */"""
98 def _checkInternetSpec(self):
99 """Verify that we can access the spec online"""
100 try:
101 online = urllib2.urlopen(spec_url,timeout=1)
102 return True
103 except urllib2.URLError as err:
104 return False
105 return False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600106 def soupLoadFile(self, online=True, spec_file=spec_filename):
107 """Load a spec file into BeutifulSoup"""
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600108 if (online and self._checkInternetSpec()):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600109 print ("Making soup from spec online at %s, this will take a minute" % (spec_url))
110 self.soup = BeautifulSoup(urllib2.urlopen(spec_url), 'html.parser')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600111 else:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600112 print ("Making soup from local spec %s, this will take a minute" % (spec_file))
Tobin Ehlisec45e422017-05-19 08:24:04 -0600113 with open(spec_file, "r") as sf:
114 self.soup = BeautifulSoup(sf, 'html.parser')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600115 self.parseSoup()
116 #print(self.soup.prettify())
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600117 def updateDict(self, updated_dict):
118 """Assign internal dict to use updated_dict"""
119 self.val_error_dict = updated_dict
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600120 def parseSoup(self):
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600121 """Parse the registry Element, once created"""
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600122 print ("Parsing spec file...")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600123 unique_enum_id = 0
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600124 #self.root = self.tree.getroot()
125 #print ("ROOT: %s") % self.root
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600126 prev_heading = '' # Last seen section heading or sub-heading
127 prev_link = '' # Last seen link id within the spec
Tobin Ehlise7560e72016-10-19 15:59:38 -0600128 api_function = '' # API call that a check appears under
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600129 error_strings = set() # Flag any exact duplicate error strings and skip them
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600130 for tag in self.soup.find_all(True):#self.root.iter(): # iterate down tree
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600131 # Grab most recent section heading and link
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600132 #print ("tag.name is %s and class is %s" % (tag.name, tag.get('class')))
133 if tag.name in ['h2', 'h3', 'h4']:
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800134 #if tag.get('class') != 'title':
135 # continue
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600136 #print ("Found heading %s w/ string %s" % (tag.name, tag.string))
137 if None == tag.string:
138 prev_heading = ""
139 else:
140 prev_heading = "".join(tag.string)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600141 # Insert a space between heading number & title
142 sh_list = prev_heading.rsplit('.', 1)
143 prev_heading = '. '.join(sh_list)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600144 prev_link = tag['id']
145 #print ("Set prev_heading %s to have link of %s" % (prev_heading.encode("ascii", "ignore"), prev_link.encode("ascii", "ignore")))
146 elif tag.name == 'a': # grab any intermediate links
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600147 if tag.get('id') != None:
148 prev_link = tag.get('id')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600149 #print ("Updated prev link to %s" % (prev_link))
150 elif tag.name == 'div' and tag.get('class') is not None and tag['class'][0] == 'listingblock':
Tobin Ehlise7560e72016-10-19 15:59:38 -0600151 # Check and see if this is API function
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600152 code_text = "".join(tag.strings).replace('\n', '')
Tobin Ehlise7560e72016-10-19 15:59:38 -0600153 code_text_list = code_text.split()
154 if len(code_text_list) > 1 and code_text_list[1].startswith('vk'):
155 api_function = code_text_list[1].strip('(')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600156 #print ("Found API function: %s" % (api_function))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600157 prev_link = api_function
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600158 #print ("Updated prev link to %s" % (prev_link))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600159 elif tag.get('id') != None:
160 prev_link = tag.get('id')
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600161 #print ("Updated prev link to %s" % (prev_link))
162 #elif tag.name == '{http://www.w3.org/1999/xhtml}div' and tag.get('class') == 'sidebar':
163 elif tag.name == 'div' and tag.get('class') is not None and tag['class'][0] == 'content':
164 #print("Parsing down a div content tag")
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600165 # parse down sidebar to check for valid usage cases
166 valid_usage = False
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700167 implicit = False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600168 for elem in tag.find_all(True):
169 #print(" elem is %s w/ string %s" % (elem.name, elem.string))
170 if elem.name == 'div' and None != elem.string and 'Valid Usage' in elem.string:
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600171 valid_usage = True
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600172 if '(Implicit)' in elem.string:
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700173 implicit = True
174 else:
175 implicit = False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600176 elif valid_usage and elem.name == 'li': # grab actual valid usage requirements
177 #print("I think this is a VU w/ elem.strings is %s" % (elem.strings))
178 error_msg_str = "%s '%s' which states '%s' (%s#%s)" % (error_msg_prefix, prev_heading, "".join(elem.strings).replace('\n', ' ').strip(), spec_url, prev_link)
Tobin Ehlis69ebddf2016-10-18 15:55:07 -0600179 # Some txt has multiple spaces so split on whitespace and join w/ single space
180 error_msg_str = " ".join(error_msg_str.split())
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600181 if error_msg_str in error_strings:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600182 print ("WARNING: SKIPPING adding repeat entry for string. Please review spec and file issue as appropriate. Repeat string is: %s" % (error_msg_str))
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600183 else:
184 error_strings.add(error_msg_str)
185 enum_str = "%s%05d" % (validation_error_enum_name, unique_enum_id)
186 # TODO : '\' chars in spec error messages are most likely bad spec txt that needs to be updated
Tobin Ehlise7560e72016-10-19 15:59:38 -0600187 self.val_error_dict[enum_str] = {}
188 self.val_error_dict[enum_str]['error_msg'] = error_msg_str.encode("ascii", "ignore").replace("\\", "/")
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600189 self.val_error_dict[enum_str]['api'] = api_function.encode("ascii", "ignore")
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700190 self.val_error_dict[enum_str]['implicit'] = False
191 if implicit:
192 self.val_error_dict[enum_str]['implicit'] = True
193 self.implicit_count = self.implicit_count + 1
Tobin Ehlis85008cd2016-10-19 15:32:35 -0600194 unique_enum_id = unique_enum_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600195 #print ("Validation Error Dict has a total of %d unique errors and contents are:\n%s" % (unique_enum_id, self.val_error_dict))
196 print ("Validation Error Dict has a total of %d unique errors" % (unique_enum_id))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600197 def genHeader(self, header_file):
198 """Generate a header file based on the contents of a parsed spec"""
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600199 print ("Generating header %s..." % (header_file))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600200 file_contents = []
201 file_contents.append(self.copyright)
202 file_contents.append('\n#pragma once')
Mark Lobodzinski267a7cf2017-01-25 09:33:25 -0700203 file_contents.append('\n// Disable auto-formatting for generated file')
204 file_contents.append('// clang-format off')
205 file_contents.append('\n#include <unordered_map>')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600206 file_contents.append('\n// enum values for unique validation error codes')
207 file_contents.append('// Corresponding validation error message for each enum is given in the mapping table below')
208 file_contents.append('// When a given error occurs, these enum values should be passed to the as the messageCode')
209 file_contents.append('// parameter to the PFN_vkDebugReportCallbackEXT function')
Tobin Ehlis387fd632016-12-08 13:32:05 -0700210 enum_decl = ['enum UNIQUE_VALIDATION_ERROR_CODE {\n VALIDATION_ERROR_UNDEFINED = -1,']
Tobin Ehlisbf98b692016-10-06 12:58:06 -0600211 error_string_map = ['static std::unordered_map<int, char const *const> validation_error_map{']
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800212 enum_value = 0
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600213 for enum in sorted(self.val_error_dict):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600214 #print ("Header enum is %s" % (enum))
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800215 enum_value = int(enum.split('_')[-1])
216 enum_decl.append(' %s = %d,' % (enum, enum_value))
Tobin Ehlise7560e72016-10-19 15:59:38 -0600217 error_string_map.append(' {%s, "%s"},' % (enum, self.val_error_dict[enum]['error_msg']))
Tobin Ehlisce1e56f2017-01-25 12:42:55 -0800218 enum_decl.append(' %sMAX_ENUM = %d,' % (validation_error_enum_name, enum_value + 1))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600219 enum_decl.append('};')
Tobin Ehlise7560e72016-10-19 15:59:38 -0600220 error_string_map.append('};\n')
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600221 file_contents.extend(enum_decl)
222 file_contents.append('\n// Mapping from unique validation error enum to the corresponding error message')
223 file_contents.append('// The error message should be appended to the end of a custom error message that is passed')
224 file_contents.append('// as the pMessage parameter to the PFN_vkDebugReportCallbackEXT function')
225 file_contents.extend(error_string_map)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600226 #print ("File contents: %s" % (file_contents))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600227 with open(header_file, "w") as outfile:
228 outfile.write("\n".join(file_contents))
229 def analyze(self):
230 """Print out some stats on the valid usage dict"""
231 # Create dict for # of occurences of identical strings
232 str_count_dict = {}
233 unique_id_count = 0
234 for enum in self.val_error_dict:
Tobin Ehlise7560e72016-10-19 15:59:38 -0600235 err_str = self.val_error_dict[enum]['error_msg']
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600236 if err_str in str_count_dict:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600237 print ("Found repeat error string")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600238 str_count_dict[err_str] = str_count_dict[err_str] + 1
239 else:
240 str_count_dict[err_str] = 1
241 unique_id_count = unique_id_count + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600242 print ("Processed %d unique_ids" % (unique_id_count))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600243 repeat_string = 0
244 for es in str_count_dict:
245 if str_count_dict[es] > 1:
246 repeat_string = repeat_string + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600247 print ("String '%s' repeated %d times" % (es, repeat_string))
248 print ("Found %d repeat strings" % (repeat_string))
249 print ("Found %d implicit checks" % (self.implicit_count))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600250 def genDB(self, db_file):
251 """Generate a database of check_enum, check_coded?, testname, error_string"""
252 db_lines = []
253 # Write header for database file
254 db_lines.append("# This is a database file with validation error check information")
255 db_lines.append("# Comments are denoted with '#' char")
256 db_lines.append("# The format of the lines is:")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600257 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))
Mark Lobodzinski629d47b2016-10-18 13:34:58 -0600258 db_lines.append("# error_enum: Unique error enum for this check of format %s<uniqueid>" % validation_error_enum_name)
Mike Weiblenfe186122017-02-03 12:44:53 -0700259 db_lines.append("# check_implemented: 'Y' if check has been implemented in layers, or 'N' for not implemented")
Dave Houlton14f7e662017-05-17 13:25:53 -0600260 db_lines.append("# testname: Name of validation test for this check, 'Unknown' for unknown, 'None' if not implemented, or 'NotTestable' if cannot be implemented")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600261 db_lines.append("# api: Vulkan API function that this check is related to")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600262 db_lines.append("# errormsg: The unique error message for this check that includes spec language and link")
Tobin Ehlise7560e72016-10-19 15:59:38 -0600263 db_lines.append("# note: Free txt field with any custom notes related to the check in question")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600264 for enum in sorted(self.val_error_dict):
Mike Weiblenfe186122017-02-03 12:44:53 -0700265 # Default check/test implementation status to N/Unknown, then update below if appropriate
266 implemented = 'N'
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600267 testname = 'Unknown'
Tobin Ehlis70980c02016-10-25 14:00:20 -0600268 note = ''
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700269 implicit = self.val_error_dict[enum]['implicit']
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600270 # If we have an existing db entry for this enum, use its implemented/testname values
271 if enum in self.error_db_dict:
272 implemented = self.error_db_dict[enum]['check_implemented']
273 testname = self.error_db_dict[enum]['testname']
Tobin Ehlis70980c02016-10-25 14:00:20 -0600274 note = self.error_db_dict[enum]['note']
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700275 if implicit and 'implicit' not in note: # add implicit note
276 if '' != note:
277 note = "implicit, %s" % (note)
278 else:
279 note = "implicit"
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600280 #print ("delimiter: %s, id: %s, str: %s" % (self.delimiter, enum, self.val_error_dict[enum])
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600281 # No existing entry so default to N for implemented and None for testname
Tobin Ehlis70980c02016-10-25 14:00:20 -0600282 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))
Tobin Ehlisaf75f7c2016-10-31 11:10:38 -0600283 db_lines.append("\n") # newline at end of file
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600284 print ("Generating database file %s" % (db_file))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600285 with open(db_file, "w") as outfile:
286 outfile.write("\n".join(db_lines))
287 def readDB(self, db_file):
288 """Read a db file into a dict, format of each line is <enum><implemented Y|N?><testname><errormsg>"""
289 db_dict = {} # This is a simple db of just enum->errormsg, the same as is created from spec
290 max_id = 0
291 with open(db_file, "r") as infile:
292 for line in infile:
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600293 line = line.strip()
Tobin Ehlisf4245cb2016-10-31 07:55:19 -0600294 if line.startswith('#') or '' == line:
295 continue
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600296 db_line = line.split(self.delimiter)
Tobin Ehlis70980c02016-10-25 14:00:20 -0600297 if len(db_line) != 6:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600298 print ("ERROR: Bad database line doesn't have 6 elements: %s" % (line))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600299 error_enum = db_line[0]
300 implemented = db_line[1]
301 testname = db_line[2]
Tobin Ehlis70980c02016-10-25 14:00:20 -0600302 api = db_line[3]
303 error_str = db_line[4]
304 note = db_line[5]
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600305 db_dict[error_enum] = error_str
306 # Also read complete database contents into our class var for later use
307 self.error_db_dict[error_enum] = {}
308 self.error_db_dict[error_enum]['check_implemented'] = implemented
309 self.error_db_dict[error_enum]['testname'] = testname
Tobin Ehlis70980c02016-10-25 14:00:20 -0600310 self.error_db_dict[error_enum]['api'] = api
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600311 self.error_db_dict[error_enum]['error_string'] = error_str
Tobin Ehlis70980c02016-10-25 14:00:20 -0600312 self.error_db_dict[error_enum]['note'] = note
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600313 unique_id = int(db_line[0].split('_')[-1])
314 if unique_id > max_id:
315 max_id = unique_id
316 return (db_dict, max_id)
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600317 # This is a helper function to do bookkeeping on data structs when comparing original
318 # error ids to current error ids
319 # It tracks all updated enums in mapped_enums and removes those enums from any lists
320 # in the no_link and core dicts
321 def _updateMappedEnum(self, mapped_enums, enum):
322 mapped_enums.add(enum)
323 # When looking for ID to map, we favor sequences so track last ID mapped
324 self.last_mapped_id = int(enum.split('_')[-1])
325 for msg in self.orig_no_link_msg_dict:
326 if enum in self.orig_no_link_msg_dict[msg]:
327 self.orig_no_link_msg_dict[msg].remove(enum)
328 for msg in self.orig_core_msg_dict:
329 if enum in self.orig_core_msg_dict[msg]:
330 self.orig_core_msg_dict[msg].remove(enum)
331 return mapped_enums
332 # Check all ids in given id list to see if one is in sequence from last mapped id
333 def findSeqID(self, id_list):
334 next_seq_id = self.last_mapped_id + 1
335 for map_id in id_list:
336 id_num = int(map_id.split('_')[-1])
337 if id_num == next_seq_id:
338 return True
339 return False
340 # Use the next ID in sequence. This should only be called if findSeqID() just returned True
341 def useSeqID(self, id_list, mapped_enums):
342 next_seq_id = self.last_mapped_id + 1
343 mapped_id = ''
344 for map_id in id_list:
345 id_num = int(map_id.split('_')[-1])
346 if id_num == next_seq_id:
347 mapped_id = map_id
348 self._updateMappedEnum(mapped_enums, mapped_id)
349 return (mapped_enums, mapped_id)
350 return (mapped_enums, mapped_id)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600351 # Compare unique ids from original database to data generated from updated spec
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600352 # First, make 3 separate mappings of original error messages:
353 # 1. Map the full error message to its id. There should only be 1 ID per full message (orig_full_msg_dict)
354 # 2. Map the intial portion of the message w/o link to list of IDs. There May be a little aliasing here (orig_no_link_msg_dict)
355 # 3. Map the core spec message w/o link or section info to list of IDs. There will be lots of aliasing here (orig_core_msg_dict)
356 # Also store a set of all IDs that have been mapped to that will serve 2 purposes:
357 # 1. Pull IDs out of the above dicts as they're remapped since we know they won't be used
358 # 2. Make sure that we don't re-use an ID
359 # The general algorithm for remapping from new IDs to old IDs is:
360 # 1. If there is a user-specified remapping, use that above all else
361 # 2. Elif the new error message hits in orig_full_msg_dict then use that ID
362 # 3. Elif the new error message hits orig_no_link_msg_dict then
363 # a. If only a single ID, use it
364 # b. Elif multiple IDs & one matches last used ID in sequence, use it
365 # c. Else assign a new ID and flag for manual remapping
366 # 4. Elif the new error message hits orig_core_msg_dict then
367 # a. If only a single ID, use it
368 # b. Elif multiple IDs & one matches last used ID in sequence, use it
369 # c. Else assign a new ID and flag for manual remapping
370 # 5. Else - No matches use a new ID
Tobin Ehlise7560e72016-10-19 15:59:38 -0600371 def compareDB(self, orig_error_msg_dict, max_id):
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600372 """Compare orig database dict to new dict, report out findings, and return potential new dict for parsed spec"""
373 # First create reverse dicts of err_strings to IDs
374 next_id = max_id + 1
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600375 ids_parsed = 0
376 mapped_enums = set() # store all enums that have been mapped to avoid re-use
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600377 # Create an updated dict in-place that will be assigned to self.val_error_dict when done
378 updated_val_error_dict = {}
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600379 # Create a few separate mappings of error msg formats to associated ID(s)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600380 for enum in orig_error_msg_dict:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600381 api = self.error_db_dict[enum]['api']
382 original_full_msg = orig_error_msg_dict[enum]
383 orig_no_link_msg = "%s,%s" % (api, original_full_msg.split('(https', 1)[0])
384 orig_core_msg = "%s,%s" % (api, orig_no_link_msg.split(' which states ', 1)[-1])
385 orig_core_msg_period = "%s.' " % (orig_core_msg[:-2])
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600386 print ("Orig core msg:%s\nOrig cw/o per:%s" % (orig_core_msg, orig_core_msg_period))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600387
388 # First store mapping of full error msg to ID, shouldn't have duplicates
389 if original_full_msg in self.orig_full_msg_dict:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600390 print ("ERROR: Found duplicate full msg in original full error messages: %s" % (original_full_msg))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600391 self.orig_full_msg_dict[original_full_msg] = enum
392 # Now map API,no_link_msg to list of IDs
393 if orig_no_link_msg in self.orig_no_link_msg_dict:
394 self.orig_no_link_msg_dict[orig_no_link_msg].append(enum)
395 else:
396 self.orig_no_link_msg_dict[orig_no_link_msg] = [enum]
397 # Finally map API,core_msg to list of IDs
398 if orig_core_msg in self.orig_core_msg_dict:
399 self.orig_core_msg_dict[orig_core_msg].append(enum)
400 else:
401 self.orig_core_msg_dict[orig_core_msg] = [enum]
402 if orig_core_msg_period in self.orig_core_msg_dict:
403 self.orig_core_msg_dict[orig_core_msg_period].append(enum)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600404 print ("Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600405 else:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600406 print ("Added msg '%s' w/ enum %s to orig_core_msg_dict" % (orig_core_msg_period, enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600407 self.orig_core_msg_dict[orig_core_msg_period] = [enum]
408 # Also capture all enums that have a test and/or implementation
Dave Houlton14f7e662017-05-17 13:25:53 -0600409 if self.error_db_dict[enum]['check_implemented'] == 'Y' or self.error_db_dict[enum]['testname'] not in ['None','Unknown','NotTestable']:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600410 print ("Recording %s with implemented value %s and testname %s" % (enum, self.error_db_dict[enum]['check_implemented'], self.error_db_dict[enum]['testname']))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600411 self.orig_test_imp_enums.add(enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600412 # Values to be used for the update dict
413 update_enum = ''
414 update_msg = ''
415 update_api = ''
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600416 # Now parse through new dict and figure out what to do with non-matching things
417 for enum in sorted(self.val_error_dict):
418 ids_parsed = ids_parsed + 1
419 enum_list = enum.split('_') # grab sections of enum for use below
Tobin Ehlise7560e72016-10-19 15:59:38 -0600420 # Default update values to be the same
421 update_enum = enum
422 update_msg = self.val_error_dict[enum]['error_msg']
423 update_api = self.val_error_dict[enum]['api']
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700424 implicit = self.val_error_dict[enum]['implicit']
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600425 new_full_msg = update_msg
426 new_no_link_msg = "%s,%s" % (update_api, new_full_msg.split('(https', 1)[0])
427 new_core_msg = "%s,%s" % (update_api, new_no_link_msg.split(' which states ', 1)[-1])
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -0600428 # Any user-forced remap takes precendence
429 if enum_list[-1] in remap_dict:
430 enum_list[-1] = remap_dict[enum_list[-1]]
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600431 self.last_mapped_id = int(enum_list[-1])
Tobin Ehlisbd0a9c62016-10-14 18:06:16 -0600432 new_enum = "_".join(enum_list)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600433 print ("NOTE: Using user-supplied remap to force %s to be %s" % (enum, new_enum))
434 mapped_enums = self._updateMappedEnum(mapped_enums, new_enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600435 update_enum = new_enum
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600436 elif new_full_msg in self.orig_full_msg_dict:
437 orig_enum = self.orig_full_msg_dict[new_full_msg]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600438 print ("Found exact match for full error msg so switching new ID %s to original ID %s" % (enum, orig_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600439 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
440 update_enum = orig_enum
441 elif new_no_link_msg in self.orig_no_link_msg_dict:
442 # Try to get single ID to map to from no_link matches
443 if len(self.orig_no_link_msg_dict[new_no_link_msg]) == 1: # Only 1 id, use it!
444 orig_enum = self.orig_no_link_msg_dict[new_no_link_msg][0]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600445 print ("Found no-link err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600446 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
447 update_enum = orig_enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600448 else:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600449 if self.findSeqID(self.orig_no_link_msg_dict[new_no_link_msg]): # If we have an id in sequence, use it!
450 (mapped_enums, update_enum) = self.useSeqID(self.orig_no_link_msg_dict[new_no_link_msg], mapped_enums)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600451 print ("Found no-link err msg match w/ seq ID match so switching new ID %s to original ID %s" % (enum, update_enum))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600452 else:
453 enum_list[-1] = "%05d" % (next_id)
454 new_enum = "_".join(enum_list)
455 next_id = next_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600456 print ("Found no-link msg match but have multiple matched IDs w/o a sequence ID, updating ID %s to unique ID %s for msg %s" % (enum, new_enum, new_no_link_msg))
Tobin Ehlise7560e72016-10-19 15:59:38 -0600457 update_enum = new_enum
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600458 elif new_core_msg in self.orig_core_msg_dict:
459 # Do similar stuff here
460 if len(self.orig_core_msg_dict[new_core_msg]) == 1:
461 orig_enum = self.orig_core_msg_dict[new_core_msg][0]
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600462 print ("Found core err msg match w/ only 1 ID match so switching new ID %s to original ID %s" % (enum, orig_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600463 mapped_enums = self._updateMappedEnum(mapped_enums, orig_enum)
464 update_enum = orig_enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600465 else:
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600466 if self.findSeqID(self.orig_core_msg_dict[new_core_msg]):
467 (mapped_enums, update_enum) = self.useSeqID(self.orig_core_msg_dict[new_core_msg], mapped_enums)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600468 print ("Found core err msg match w/ seq ID match so switching new ID %s to original ID %s" % (enum, update_enum))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600469 else:
470 enum_list[-1] = "%05d" % (next_id)
471 new_enum = "_".join(enum_list)
472 next_id = next_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600473 print ("Found core msg match but have multiple matched IDs w/o a sequence ID, updating ID %s to unique ID %s for msg %s" % (enum, new_enum, new_no_link_msg))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600474 update_enum = new_enum
475 # This seems to be a new error so need to pick it up from end of original unique ids & flag for review
476 else:
477 enum_list[-1] = "%05d" % (next_id)
478 new_enum = "_".join(enum_list)
479 next_id = next_id + 1
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600480 print ("Completely new id and error code, update new id from %s to unique %s for core message:%s" % (enum, new_enum, new_core_msg))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600481 update_enum = new_enum
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600482 if update_enum in updated_val_error_dict:
483 print ("ERROR: About to OVERWRITE entry for %s" % update_enum)
Tobin Ehlise7560e72016-10-19 15:59:38 -0600484 updated_val_error_dict[update_enum] = {}
485 updated_val_error_dict[update_enum]['error_msg'] = update_msg
486 updated_val_error_dict[update_enum]['api'] = update_api
Tobin Ehlis2a176b12017-01-11 16:18:20 -0700487 updated_val_error_dict[update_enum]['implicit'] = implicit
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600488 # Assign parsed dict to be the updated dict based on db compare
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600489 print ("In compareDB parsed %d entries" % (ids_parsed))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600490 return updated_val_error_dict
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600491
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600492 def validateUpdateDict(self, update_dict):
493 """Compare original dict vs. update dict and make sure that all of the checks are still there"""
494 # Currently just make sure that the same # of checks as the original checks are there
495 #orig_ids = {}
496 orig_id_count = len(self.val_error_dict)
497 #update_ids = {}
498 update_id_count = len(update_dict)
499 if orig_id_count != update_id_count:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600500 print ("Original dict had %d unique_ids, but updated dict has %d!" % (orig_id_count, update_id_count))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600501 return False
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600502 print ("Original dict and updated dict both have %d unique_ids. Great!" % (orig_id_count))
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600503 # Now flag any original dict enums that had tests and/or checks that are missing from updated
504 for enum in update_dict:
505 if enum in self.orig_test_imp_enums:
506 self.orig_test_imp_enums.remove(enum)
507 if len(self.orig_test_imp_enums) > 0:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600508 print ("TODO: Have some enums with tests and/or checks implemented that are missing in update:")
Tobin Ehlisa55b1d42017-04-04 12:23:48 -0600509 for enum in sorted(self.orig_test_imp_enums):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600510 print ("\t%s") % enum
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600511 return True
512 # TODO : include some more analysis
513
514# User passes in arg of form <new_id1>-<old_id1>[,count1]:<new_id2>-<old_id2>[,count2]:...
515# new_id# = the new enum id that was assigned to an error
516# old_id# = the previous enum id that was assigned to the same error
517# [,count#] = The number of ids to remap starting at new_id#=old_id# and ending at new_id[#+count#-1]=old_id[#+count#-1]
518# If not supplied, then ,1 is assumed, which will only update a single id
519def updateRemapDict(remap_string):
520 """Set up global remap_dict based on user input"""
521 remap_list = remap_string.split(":")
522 for rmap in remap_list:
523 count = 1 # Default count if none supplied
524 id_count_list = rmap.split(',')
525 if len(id_count_list) > 1:
526 count = int(id_count_list[1])
527 new_old_id_list = id_count_list[0].split('-')
528 for offset in range(count):
529 remap_dict["%05d" % (int(new_old_id_list[0]) + offset)] = "%05d" % (int(new_old_id_list[1]) + offset)
530 for new_id in sorted(remap_dict):
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600531 print ("Set to remap new id %s to old id %s" % (new_id, remap_dict[new_id]))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600532
533if __name__ == "__main__":
534 i = 1
535 use_online = True # Attempt to grab spec from online by default
536 update_option = False
537 while (i < len(sys.argv)):
538 arg = sys.argv[i]
539 i = i + 1
540 if (arg == '-spec'):
541 spec_filename = sys.argv[i]
542 # If user specifies local specfile, skip online
543 use_online = False
544 i = i + 1
545 elif (arg == '-out'):
546 out_filename = sys.argv[i]
547 i = i + 1
548 elif (arg == '-gendb'):
549 gen_db = True
550 # Set filename if supplied, else use default
551 if i < len(sys.argv) and not sys.argv[i].startswith('-'):
552 db_filename = sys.argv[i]
553 i = i + 1
554 elif (arg == '-compare'):
555 db_filename = sys.argv[i]
556 spec_compare = True
557 i = i + 1
558 elif (arg == '-update'):
559 update_option = True
560 spec_compare = True
561 gen_db = True
562 elif (arg == '-remap'):
563 updateRemapDict(sys.argv[i])
564 i = i + 1
565 elif (arg in ['-help', '-h']):
566 printHelp()
567 sys.exit()
568 if len(remap_dict) > 1 and not update_option:
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600569 print ("ERROR: '-remap' option can only be used along with '-update' option. Exiting.")
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600570 sys.exit()
571 spec = Specification()
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600572 spec.soupLoadFile(use_online, spec_filename)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600573 spec.analyze()
574 if (spec_compare):
575 # Read in old spec info from db file
Tobin Ehlise7560e72016-10-19 15:59:38 -0600576 (orig_err_msg_dict, max_id) = spec.readDB(db_filename)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600577 # New spec data should already be read into self.val_error_dict
Tobin Ehlise7560e72016-10-19 15:59:38 -0600578 updated_dict = spec.compareDB(orig_err_msg_dict, max_id)
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600579 update_valid = spec.validateUpdateDict(updated_dict)
580 if update_valid:
581 spec.updateDict(updated_dict)
582 else:
583 sys.exit()
584 if (gen_db):
585 spec.genDB(db_filename)
Tobin Ehlis3198ba32017-04-19 17:30:52 -0600586 print ("Writing out file (-out) to '%s'" % (out_filename))
Tobin Ehlis5ade0692016-10-05 17:18:15 -0600587 spec.genHeader(out_filename)
588
589##### Example dataset
590# <div class="sidebar">
591# <div class="titlepage">
592# <div>
593# <div>
594# <p class="title">
595# <strong>Valid Usage</strong> # When we get to this guy, we know we're under interesting sidebar
596# </p>
597# </div>
598# </div>
599# </div>
600# <div class="itemizedlist">
601# <ul class="itemizedlist" style="list-style-type: disc; ">
602# <li class="listitem">
603# <em class="parameter">
604# <code>device</code>
605# </em>
606# <span class="normative">must</span> be a valid
607# <code class="code">VkDevice</code> handle
608# </li>
609# <li class="listitem">
610# <em class="parameter">
611# <code>commandPool</code>
612# </em>
613# <span class="normative">must</span> be a valid
614# <code class="code">VkCommandPool</code> handle
615# </li>
616# <li class="listitem">
617# <em class="parameter">
618# <code>flags</code>
619# </em>
620# <span class="normative">must</span> be a valid combination of
621# <code class="code">
622# <a class="link" href="#VkCommandPoolResetFlagBits">VkCommandPoolResetFlagBits</a>
623# </code> values
624# </li>
625# <li class="listitem">
626# <em class="parameter">
627# <code>commandPool</code>
628# </em>
629# <span class="normative">must</span> have been created, allocated, or retrieved from
630# <em class="parameter">
631# <code>device</code>
632# </em>
633# </li>
634# <li class="listitem">All
635# <code class="code">VkCommandBuffer</code>
636# objects allocated from
637# <em class="parameter">
638# <code>commandPool</code>
639# </em>
640# <span class="normative">must</span> not currently be pending execution
641# </li>
642# </ul>
643# </div>
644# </div>
645##### Second example dataset
646# <div class="sidebar">
647# <div class="titlepage">
648# <div>
649# <div>
650# <p class="title">
651# <strong>Valid Usage</strong>
652# </p>
653# </div>
654# </div>
655# </div>
656# <div class="itemizedlist">
657# <ul class="itemizedlist" style="list-style-type: disc; ">
658# <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>
659# </li>
660# </ul>
661# </div>
662# </div>
663# <div class="sidebar">
664# <div class="titlepage">
665# <div>
666# <div>
667# <p class="title">
668# <strong>Valid Usage (Implicit)</strong>
669# </p>
670# </div>
671# </div>
672# </div>
673# <div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">
674#<em class="parameter"><code>sType</code></em> <span class="normative">must</span> be <code class="code">VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO</code>
675#</li><li class="listitem">
676#<em class="parameter"><code>pNext</code></em> <span class="normative">must</span> be <code class="literal">NULL</code>
677#</li><li class="listitem">
678#<em class="parameter"><code>flags</code></em> <span class="normative">must</span> be <code class="literal">0</code>
679#</li><li class="listitem">
680#<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
Mark Lobodzinski267a7cf2017-01-25 09:33:25 -0700681#</li>