1#!/usr/bin/python3 -i 2# 3# Copyright (c) 2015-2019 The Khronos Group Inc. 4# Copyright (c) 2015-2019 Valve Corporation 5# Copyright (c) 2015-2019 LunarG, Inc. 6# Copyright (c) 2015-2019 Google Inc. 7# 8# Licensed under the Apache License, Version 2.0 (the "License"); 9# you may not use this file except in compliance with the License. 10# You may obtain a copy of the License at 11# 12# http://www.apache.org/licenses/LICENSE-2.0 13# 14# Unless required by applicable law or agreed to in writing, software 15# distributed under the License is distributed on an "AS IS" BASIS, 16# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17# See the License for the specific language governing permissions and 18# limitations under the License. 19# 20# Author: Mark Lobodzinski <mark@lunarg.com> 21# Author: Dave Houlton <daveh@lunarg.com> 22 23import os,re,sys,string,json 24import xml.etree.ElementTree as etree 25from generator import * 26from collections import namedtuple 27from common_codegen import * 28 29# This is a workaround to use a Python 2.7 and 3.x compatible syntax. 30from io import open 31 32# ObjectTrackerGeneratorOptions - subclass of GeneratorOptions. 33# 34# Adds options used by ObjectTrackerOutputGenerator objects during 35# object_tracker layer generation. 36# 37# Additional members 38# prefixText - list of strings to prefix generated header with 39# (usually a copyright statement + calling convention macros). 40# protectFile - True if multiple inclusion protection should be 41# generated (based on the filename) around the entire header. 42# protectFeature - True if #ifndef..#endif protection should be 43# generated around a feature interface in the header file. 44# genFuncPointers - True if function pointer typedefs should be 45# generated 46# protectProto - If conditional protection should be generated 47# around prototype declarations, set to either '#ifdef' 48# to require opt-in (#ifdef protectProtoStr) or '#ifndef' 49# to require opt-out (#ifndef protectProtoStr). Otherwise 50# set to None. 51# protectProtoStr - #ifdef/#ifndef symbol to use around prototype 52# declarations, if protectProto is set 53# apicall - string to use for the function declaration prefix, 54# such as APICALL on Windows. 55# apientry - string to use for the calling convention macro, 56# in typedefs, such as APIENTRY. 57# apientryp - string to use for the calling convention macro 58# in function pointer typedefs, such as APIENTRYP. 59# indentFuncProto - True if prototype declarations should put each 60# parameter on a separate line 61# indentFuncPointer - True if typedefed function pointers should put each 62# parameter on a separate line 63# alignFuncParam - if nonzero and parameters are being put on a 64# separate line, align parameter names at the specified column 65class ObjectTrackerGeneratorOptions(GeneratorOptions): 66 def __init__(self, 67 filename = None, 68 directory = '.', 69 apiname = None, 70 profile = None, 71 versions = '.*', 72 emitversions = '.*', 73 defaultExtensions = None, 74 addExtensions = None, 75 removeExtensions = None, 76 emitExtensions = None, 77 sortProcedure = regSortFeatures, 78 prefixText = "", 79 genFuncPointers = True, 80 protectFile = True, 81 protectFeature = True, 82 apicall = '', 83 apientry = '', 84 apientryp = '', 85 indentFuncProto = True, 86 indentFuncPointer = False, 87 alignFuncParam = 0, 88 expandEnumerants = True, 89 valid_usage_path = ''): 90 GeneratorOptions.__init__(self, filename, directory, apiname, profile, 91 versions, emitversions, defaultExtensions, 92 addExtensions, removeExtensions, emitExtensions, sortProcedure) 93 self.prefixText = prefixText 94 self.genFuncPointers = genFuncPointers 95 self.protectFile = protectFile 96 self.protectFeature = protectFeature 97 self.apicall = apicall 98 self.apientry = apientry 99 self.apientryp = apientryp 100 self.indentFuncProto = indentFuncProto 101 self.indentFuncPointer = indentFuncPointer 102 self.alignFuncParam = alignFuncParam 103 self.expandEnumerants = expandEnumerants 104 self.valid_usage_path = valid_usage_path 105 106 107# ObjectTrackerOutputGenerator - subclass of OutputGenerator. 108# Generates object_tracker layer object validation code 109# 110# ---- methods ---- 111# ObjectTrackerOutputGenerator(errFile, warnFile, diagFile) - args as for OutputGenerator. Defines additional internal state. 112# ---- methods overriding base class ---- 113# beginFile(genOpts) 114# endFile() 115# beginFeature(interface, emit) 116# endFeature() 117# genCmd(cmdinfo) 118# genStruct() 119# genType() 120class ObjectTrackerOutputGenerator(OutputGenerator): 121 """Generate ObjectTracker code based on XML element attributes""" 122 # This is an ordered list of sections in the header file. 123 ALL_SECTIONS = ['command'] 124 def __init__(self, 125 errFile = sys.stderr, 126 warnFile = sys.stderr, 127 diagFile = sys.stdout): 128 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 129 self.INDENT_SPACES = 4 130 self.prototypes = [] 131 self.instance_extensions = [] 132 self.device_extensions = [] 133 # Commands which are not autogenerated but still intercepted 134 self.no_autogen_list = [ 135 'vkDestroyInstance', 136 'vkCreateInstance', 137 'vkEnumeratePhysicalDevices', 138 'vkGetPhysicalDeviceQueueFamilyProperties', 139 'vkGetPhysicalDeviceQueueFamilyProperties2', 140 'vkGetPhysicalDeviceQueueFamilyProperties2KHR', 141 'vkGetDeviceQueue', 142 'vkGetDeviceQueue2', 143 'vkCreateDescriptorSetLayout', 144 'vkDestroyDescriptorPool', 145 'vkDestroyCommandPool', 146 'vkAllocateCommandBuffers', 147 'vkAllocateDescriptorSets', 148 'vkFreeDescriptorSets', 149 'vkFreeCommandBuffers', 150 'vkUpdateDescriptorSets', 151 'vkBeginCommandBuffer', 152 'vkGetDescriptorSetLayoutSupport', 153 'vkGetDescriptorSetLayoutSupportKHR', 154 'vkDestroySwapchainKHR', 155 'vkGetSwapchainImagesKHR', 156 'vkCmdPushDescriptorSetKHR', 157 'vkDestroyDevice', 158 'vkResetDescriptorPool', 159 'vkGetPhysicalDeviceDisplayPropertiesKHR', 160 'vkGetPhysicalDeviceDisplayProperties2KHR', 161 'vkGetDisplayModePropertiesKHR', 162 'vkGetDisplayModeProperties2KHR', 163 ] 164 # These VUIDS are not implicit, but are best handled in this layer. Codegen for vkDestroy calls will generate a key 165 # which is translated here into a good VU. Saves ~40 checks. 166 self.manual_vuids = dict() 167 self.manual_vuids = { 168 "fence-compatalloc": "\"VUID-vkDestroyFence-fence-01121\"", 169 "fence-nullalloc": "\"VUID-vkDestroyFence-fence-01122\"", 170 "event-compatalloc": "\"VUID-vkDestroyEvent-event-01146\"", 171 "event-nullalloc": "\"VUID-vkDestroyEvent-event-01147\"", 172 "buffer-compatalloc": "\"VUID-vkDestroyBuffer-buffer-00923\"", 173 "buffer-nullalloc": "\"VUID-vkDestroyBuffer-buffer-00924\"", 174 "image-compatalloc": "\"VUID-vkDestroyImage-image-01001\"", 175 "image-nullalloc": "\"VUID-vkDestroyImage-image-01002\"", 176 "shaderModule-compatalloc": "\"VUID-vkDestroyShaderModule-shaderModule-01092\"", 177 "shaderModule-nullalloc": "\"VUID-vkDestroyShaderModule-shaderModule-01093\"", 178 "pipeline-compatalloc": "\"VUID-vkDestroyPipeline-pipeline-00766\"", 179 "pipeline-nullalloc": "\"VUID-vkDestroyPipeline-pipeline-00767\"", 180 "sampler-compatalloc": "\"VUID-vkDestroySampler-sampler-01083\"", 181 "sampler-nullalloc": "\"VUID-vkDestroySampler-sampler-01084\"", 182 "renderPass-compatalloc": "\"VUID-vkDestroyRenderPass-renderPass-00874\"", 183 "renderPass-nullalloc": "\"VUID-vkDestroyRenderPass-renderPass-00875\"", 184 "descriptorUpdateTemplate-compatalloc": "\"VUID-vkDestroyDescriptorUpdateTemplate-descriptorSetLayout-00356\"", 185 "descriptorUpdateTemplate-nullalloc": "\"VUID-vkDestroyDescriptorUpdateTemplate-descriptorSetLayout-00357\"", 186 "imageView-compatalloc": "\"VUID-vkDestroyImageView-imageView-01027\"", 187 "imageView-nullalloc": "\"VUID-vkDestroyImageView-imageView-01028\"", 188 "pipelineCache-compatalloc": "\"VUID-vkDestroyPipelineCache-pipelineCache-00771\"", 189 "pipelineCache-nullalloc": "\"VUID-vkDestroyPipelineCache-pipelineCache-00772\"", 190 "pipelineLayout-compatalloc": "\"VUID-vkDestroyPipelineLayout-pipelineLayout-00299\"", 191 "pipelineLayout-nullalloc": "\"VUID-vkDestroyPipelineLayout-pipelineLayout-00300\"", 192 "descriptorSetLayout-compatalloc": "\"VUID-vkDestroyDescriptorSetLayout-descriptorSetLayout-00284\"", 193 "descriptorSetLayout-nullalloc": "\"VUID-vkDestroyDescriptorSetLayout-descriptorSetLayout-00285\"", 194 "semaphore-compatalloc": "\"VUID-vkDestroySemaphore-semaphore-01138\"", 195 "semaphore-nullalloc": "\"VUID-vkDestroySemaphore-semaphore-01139\"", 196 "queryPool-compatalloc": "\"VUID-vkDestroyQueryPool-queryPool-00794\"", 197 "queryPool-nullalloc": "\"VUID-vkDestroyQueryPool-queryPool-00795\"", 198 "bufferView-compatalloc": "\"VUID-vkDestroyBufferView-bufferView-00937\"", 199 "bufferView-nullalloc": "\"VUID-vkDestroyBufferView-bufferView-00938\"", 200 "surface-compatalloc": "\"VUID-vkDestroySurfaceKHR-surface-01267\"", 201 "surface-nullalloc": "\"VUID-vkDestroySurfaceKHR-surface-01268\"", 202 "framebuffer-compatalloc": "\"VUID-vkDestroyFramebuffer-framebuffer-00893\"", 203 "framebuffer-nullalloc": "\"VUID-vkDestroyFramebuffer-framebuffer-00894\"", 204 } 205 206 # Commands shadowed by interface functions and are not implemented 207 self.interface_functions = [ 208 ] 209 self.headerVersion = None 210 # Internal state - accumulators for different inner block text 211 self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) 212 self.cmd_list = [] # list of commands processed to maintain ordering 213 self.cmd_info_dict = {} # Per entry-point data for code generation and validation 214 self.structMembers = [] # List of StructMemberData records for all Vulkan structs 215 self.extension_structs = [] # List of all structs or sister-structs containing handles 216 # A sister-struct may contain no handles but shares <validextensionstructs> with one that does 217 self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType 218 self.struct_member_dict = dict() 219 # Named tuples to store struct and command data 220 self.StructType = namedtuple('StructType', ['name', 'value']) 221 self.CmdInfoData = namedtuple('CmdInfoData', ['name', 'cmdinfo', 'members', 'extra_protect', 'alias', 'iscreate', 'isdestroy', 'allocator']) 222 self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'isconst', 'isoptional', 'iscount', 'iscreate', 'len', 'extstructs', 'cdecl', 'islocal']) 223 self.StructMemberData = namedtuple('StructMemberData', ['name', 'members']) 224 self.object_types = [] # List of all handle types 225 self.valid_vuids = set() # Set of all valid VUIDs 226 self.vuid_dict = dict() # VUID dictionary (from JSON) 227 # 228 # Check if the parameter passed in is optional 229 def paramIsOptional(self, param): 230 # See if the handle is optional 231 isoptional = False 232 # Simple, if it's optional, return true 233 optString = param.attrib.get('optional') 234 if optString: 235 if optString == 'true': 236 isoptional = True 237 elif ',' in optString: 238 opts = [] 239 for opt in optString.split(','): 240 val = opt.strip() 241 if val == 'true': 242 opts.append(True) 243 elif val == 'false': 244 opts.append(False) 245 else: 246 print('Unrecognized len attribute value',val) 247 isoptional = opts 248 if not isoptional: 249 # Matching logic in parameter validation and ValidityOutputGenerator.isHandleOptional 250 optString = param.attrib.get('noautovalidity') 251 if optString and optString == 'true': 252 isoptional = True; 253 return isoptional 254 # 255 # Get VUID identifier from implicit VUID tag 256 def GetVuid(self, parent, suffix): 257 vuid_string = 'VUID-%s-%s' % (parent, suffix) 258 vuid = "kVUIDUndefined" 259 if '->' in vuid_string: 260 return vuid 261 if vuid_string in self.valid_vuids: 262 vuid = "\"%s\"" % vuid_string 263 else: 264 alias = self.cmd_info_dict[parent].alias if parent in self.cmd_info_dict else None 265 if alias: 266 alias_string = 'VUID-%s-%s' % (alias, suffix) 267 if alias_string in self.valid_vuids: 268 vuid = "\"%s\"" % vuid_string 269 return vuid 270 # 271 # Increases indent by 4 spaces and tracks it globally 272 def incIndent(self, indent): 273 inc = ' ' * self.INDENT_SPACES 274 if indent: 275 return indent + inc 276 return inc 277 # 278 # Decreases indent by 4 spaces and tracks it globally 279 def decIndent(self, indent): 280 if indent and (len(indent) > self.INDENT_SPACES): 281 return indent[:-self.INDENT_SPACES] 282 return '' 283 # 284 # Override makeProtoName to drop the "vk" prefix 285 def makeProtoName(self, name, tail): 286 return self.genOpts.apientry + name[2:] + tail 287 # 288 # Check if the parameter passed in is a pointer to an array 289 def paramIsArray(self, param): 290 return param.attrib.get('len') is not None 291 292 # 293 # Generate the object tracker undestroyed object validation function 294 def GenReportFunc(self): 295 output_func = '' 296 output_func += 'bool ObjectLifetimes::ReportUndestroyedObjects(VkDevice device, const std::string& error_code) {\n' 297 output_func += ' bool skip = false;\n' 298 output_func += ' skip |= DeviceReportUndestroyedObjects(device, kVulkanObjectTypeCommandBuffer, error_code);\n' 299 for handle in self.object_types: 300 if self.isHandleTypeNonDispatchable(handle): 301 output_func += ' skip |= DeviceReportUndestroyedObjects(device, %s, error_code);\n' % (self.GetVulkanObjType(handle)) 302 output_func += ' return skip;\n' 303 output_func += '}\n' 304 return output_func 305 306 # 307 # Generate the object tracker undestroyed object destruction function 308 def GenDestroyFunc(self): 309 output_func = '' 310 output_func += 'void ObjectLifetimes::DestroyUndestroyedObjects(VkDevice device) {\n' 311 output_func += ' DeviceDestroyUndestroyedObjects(device, kVulkanObjectTypeCommandBuffer);\n' 312 for handle in self.object_types: 313 if self.isHandleTypeNonDispatchable(handle): 314 output_func += ' DeviceDestroyUndestroyedObjects(device, %s);\n' % (self.GetVulkanObjType(handle)) 315 output_func += '}\n' 316 return output_func 317 318 # 319 # Walk the JSON-derived dict and find all "vuid" key values 320 def ExtractVUIDs(self, d): 321 if hasattr(d, 'items'): 322 for k, v in d.items(): 323 if k == "vuid": 324 yield v 325 elif isinstance(v, dict): 326 for s in self.ExtractVUIDs(v): 327 yield s 328 elif isinstance (v, list): 329 for l in v: 330 for s in self.ExtractVUIDs(l): 331 yield s 332 # 333 # Separate content for validation source and header files 334 def otwrite(self, dest, formatstring): 335 if 'object_tracker.h' in self.genOpts.filename and (dest == 'hdr' or dest == 'both'): 336 write(formatstring, file=self.outFile) 337 elif 'object_tracker.cpp' in self.genOpts.filename and (dest == 'cpp' or dest == 'both'): 338 write(formatstring, file=self.outFile) 339 340 # 341 # Called at beginning of processing as file is opened 342 def beginFile(self, genOpts): 343 OutputGenerator.beginFile(self, genOpts) 344 345 header_file = (genOpts.filename == 'object_tracker.h') 346 source_file = (genOpts.filename == 'object_tracker.cpp') 347 348 if not header_file and not source_file: 349 print("Error: Output Filenames have changed, update generator source.\n") 350 sys.exit(1) 351 352 self.valid_usage_path = genOpts.valid_usage_path 353 vu_json_filename = os.path.join(self.valid_usage_path + os.sep, 'validusage.json') 354 if os.path.isfile(vu_json_filename): 355 json_file = open(vu_json_filename, 'r') 356 self.vuid_dict = json.load(json_file) 357 json_file.close() 358 if len(self.vuid_dict) == 0: 359 print("Error: Could not find, or error loading %s/validusage.json\n", vu_json_filename) 360 sys.exit(1) 361 362 # Build a set of all vuid text strings found in validusage.json 363 for json_vuid_string in self.ExtractVUIDs(self.vuid_dict): 364 self.valid_vuids.add(json_vuid_string) 365 366 # File Comment 367 file_comment = '// *** THIS FILE IS GENERATED - DO NOT EDIT ***\n' 368 file_comment += '// See object_tracker_generator.py for modifications\n' 369 self.otwrite('both', file_comment) 370 # Copyright Statement 371 copyright = '' 372 copyright += '\n' 373 copyright += '/***************************************************************************\n' 374 copyright += ' *\n' 375 copyright += ' * Copyright (c) 2015-2019 The Khronos Group Inc.\n' 376 copyright += ' * Copyright (c) 2015-2019 Valve Corporation\n' 377 copyright += ' * Copyright (c) 2015-2019 LunarG, Inc.\n' 378 copyright += ' * Copyright (c) 2015-2019 Google Inc.\n' 379 copyright += ' *\n' 380 copyright += ' * Licensed under the Apache License, Version 2.0 (the "License");\n' 381 copyright += ' * you may not use this file except in compliance with the License.\n' 382 copyright += ' * You may obtain a copy of the License at\n' 383 copyright += ' *\n' 384 copyright += ' * http://www.apache.org/licenses/LICENSE-2.0\n' 385 copyright += ' *\n' 386 copyright += ' * Unless required by applicable law or agreed to in writing, software\n' 387 copyright += ' * distributed under the License is distributed on an "AS IS" BASIS,\n' 388 copyright += ' * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n' 389 copyright += ' * See the License for the specific language governing permissions and\n' 390 copyright += ' * limitations under the License.\n' 391 copyright += ' *\n' 392 copyright += ' * Author: Mark Lobodzinski <mark@lunarg.com>\n' 393 copyright += ' * Author: Dave Houlton <daveh@lunarg.com>\n' 394 copyright += ' *\n' 395 copyright += ' ****************************************************************************/\n' 396 self.otwrite('both', copyright) 397 self.newline() 398 self.otwrite('cpp', '#include "chassis.h"') 399 self.otwrite('cpp', '#include "object_lifetime_validation.h"') 400 401 # 402 # Now that the data is all collected and complete, generate and output the object validation routines 403 def endFile(self): 404 self.struct_member_dict = dict(self.structMembers) 405 # Generate the list of APIs that might need to handle wrapped extension structs 406 # self.GenerateCommandWrapExtensionList() 407 self.WrapCommands() 408 # Build undestroyed objects reporting function 409 report_func = self.GenReportFunc() 410 self.newline() 411 # Build undestroyed objects destruction function 412 destroy_func = self.GenDestroyFunc() 413 self.otwrite('cpp', '\n') 414 self.otwrite('cpp', '// ObjectTracker undestroyed objects validation function') 415 self.otwrite('cpp', '%s' % report_func) 416 self.otwrite('cpp', '%s' % destroy_func) 417 # Actually write the interface to the output file. 418 if (self.emit): 419 self.newline() 420 if self.featureExtraProtect is not None: 421 prot = '#ifdef %s' % self.featureExtraProtect 422 self.otwrite('both', '%s' % prot) 423 # Write the object_tracker code to the file 424 if self.sections['command']: 425 source = ('\n'.join(self.sections['command'])) 426 self.otwrite('both', '%s' % source) 427 if (self.featureExtraProtect is not None): 428 prot = '\n#endif // %s', self.featureExtraProtect 429 self.otwrite('both', prot) 430 else: 431 self.otwrite('both', '\n') 432 433 434 self.otwrite('hdr', 'void PostCallRecordDestroyInstance(VkInstance instance, const VkAllocationCallbacks *pAllocator);') 435 self.otwrite('hdr', 'void PreCallRecordResetDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags);') 436 self.otwrite('hdr', 'void PostCallRecordGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties *pQueueFamilyProperties);') 437 self.otwrite('hdr', 'void PreCallRecordFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer *pCommandBuffers);') 438 self.otwrite('hdr', 'void PreCallRecordFreeDescriptorSets(VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, const VkDescriptorSet *pDescriptorSets);') 439 self.otwrite('hdr', 'void PostCallRecordGetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties2KHR *pQueueFamilyProperties);') 440 self.otwrite('hdr', 'void PostCallRecordGetPhysicalDeviceQueueFamilyProperties2KHR(VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties2KHR *pQueueFamilyProperties);') 441 self.otwrite('hdr', 'void PostCallRecordGetPhysicalDeviceDisplayPropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, VkDisplayPropertiesKHR *pProperties, VkResult result);') 442 self.otwrite('hdr', 'void PostCallRecordGetDisplayModePropertiesKHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t *pPropertyCount, VkDisplayModePropertiesKHR *pProperties, VkResult result);') 443 self.otwrite('hdr', 'void PostCallRecordGetPhysicalDeviceDisplayProperties2KHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, VkDisplayProperties2KHR *pProperties, VkResult result);') 444 self.otwrite('hdr', 'void PostCallRecordGetDisplayModeProperties2KHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t *pPropertyCount, VkDisplayModeProperties2KHR *pProperties, VkResult result);') 445 OutputGenerator.endFile(self) 446 # 447 # Processing point at beginning of each extension definition 448 def beginFeature(self, interface, emit): 449 # Start processing in superclass 450 OutputGenerator.beginFeature(self, interface, emit) 451 self.headerVersion = None 452 self.featureExtraProtect = GetFeatureProtect(interface) 453 454 if self.featureName != 'VK_VERSION_1_0' and self.featureName != 'VK_VERSION_1_1': 455 white_list_entry = [] 456 if (self.featureExtraProtect is not None): 457 white_list_entry += [ '#ifdef %s' % self.featureExtraProtect ] 458 white_list_entry += [ '"%s"' % self.featureName ] 459 if (self.featureExtraProtect is not None): 460 white_list_entry += [ '#endif' ] 461 featureType = interface.get('type') 462 if featureType == 'instance': 463 self.instance_extensions += white_list_entry 464 elif featureType == 'device': 465 self.device_extensions += white_list_entry 466 # 467 # Processing point at end of each extension definition 468 def endFeature(self): 469 # Finish processing in superclass 470 OutputGenerator.endFeature(self) 471 # 472 # Process enums, structs, etc. 473 def genType(self, typeinfo, name, alias): 474 OutputGenerator.genType(self, typeinfo, name, alias) 475 typeElem = typeinfo.elem 476 # If the type is a struct type, traverse the imbedded <member> tags generating a structure. 477 # Otherwise, emit the tag text. 478 category = typeElem.get('category') 479 if (category == 'struct' or category == 'union'): 480 self.genStruct(typeinfo, name, alias) 481 if category == 'handle': 482 self.object_types.append(name) 483 # 484 # Append a definition to the specified section 485 def appendSection(self, section, text): 486 # self.sections[section].append('SECTION: ' + section + '\n') 487 self.sections[section].append(text) 488 # 489 # Check if the parameter passed in is a pointer 490 def paramIsPointer(self, param): 491 ispointer = False 492 for elem in param: 493 if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail: 494 ispointer = True 495 return ispointer 496 # 497 # Get the category of a type 498 def getTypeCategory(self, typename): 499 types = self.registry.tree.findall("types/type") 500 for elem in types: 501 if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename: 502 return elem.attrib.get('category') 503 # 504 # Check if a parent object is dispatchable or not 505 def isHandleTypeObject(self, handletype): 506 handle = self.registry.tree.find("types/type/[name='" + handletype + "'][@category='handle']") 507 if handle is not None: 508 return True 509 else: 510 return False 511 # 512 # Check if a parent object is dispatchable or not 513 def isHandleTypeNonDispatchable(self, handletype): 514 handle = self.registry.tree.find("types/type/[name='" + handletype + "'][@category='handle']") 515 if handle is not None and handle.find('type').text == 'VK_DEFINE_NON_DISPATCHABLE_HANDLE': 516 return True 517 else: 518 return False 519 # 520 # Retrieve the type and name for a parameter 521 def getTypeNameTuple(self, param): 522 type = '' 523 name = '' 524 for elem in param: 525 if elem.tag == 'type': 526 type = noneStr(elem.text) 527 elif elem.tag == 'name': 528 name = noneStr(elem.text) 529 return (type, name) 530 # 531 # Retrieve the value of the len tag 532 def getLen(self, param): 533 result = None 534 len = param.attrib.get('len') 535 if len and len != 'null-terminated': 536 # For string arrays, 'len' can look like 'count,null-terminated', indicating that we 537 # have a null terminated array of strings. We strip the null-terminated from the 538 # 'len' field and only return the parameter specifying the string count 539 if 'null-terminated' in len: 540 result = len.split(',')[0] 541 else: 542 result = len 543 # Spec has now notation for len attributes, using :: instead of platform specific pointer symbol 544 result = str(result).replace('::', '->') 545 return result 546 # 547 # Generate a VkStructureType based on a structure typename 548 def genVkStructureType(self, typename): 549 # Add underscore between lowercase then uppercase 550 value = re.sub('([a-z0-9])([A-Z])', r'\1_\2', typename) 551 # Change to uppercase 552 value = value.upper() 553 # Add STRUCTURE_TYPE_ 554 return re.sub('VK_', 'VK_STRUCTURE_TYPE_', value) 555 # 556 # Struct parameter check generation. 557 # This is a special case of the <type> tag where the contents are interpreted as a set of 558 # <member> tags instead of freeform C type declarations. The <member> tags are just like 559 # <param> tags - they are a declaration of a struct or union member. Only simple member 560 # declarations are supported (no nested structs etc.) 561 def genStruct(self, typeinfo, typeName, alias): 562 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 563 members = typeinfo.elem.findall('.//member') 564 # Iterate over members once to get length parameters for arrays 565 lens = set() 566 for member in members: 567 len = self.getLen(member) 568 if len: 569 lens.add(len) 570 # Generate member info 571 membersInfo = [] 572 for member in members: 573 # Get the member's type and name 574 info = self.getTypeNameTuple(member) 575 type = info[0] 576 name = info[1] 577 cdecl = self.makeCParamDecl(member, 0) 578 # Process VkStructureType 579 if type == 'VkStructureType': 580 # Extract the required struct type value from the comments 581 # embedded in the original text defining the 'typeinfo' element 582 rawXml = etree.tostring(typeinfo.elem).decode('ascii') 583 result = re.search(r'VK_STRUCTURE_TYPE_\w+', rawXml) 584 if result: 585 value = result.group(0) 586 else: 587 value = self.genVkStructureType(typeName) 588 # Store the required type value 589 self.structTypes[typeName] = self.StructType(name=name, value=value) 590 # Store pointer/array/string info 591 extstructs = member.attrib.get('validextensionstructs') if name == 'pNext' else None 592 membersInfo.append(self.CommandParam(type=type, 593 name=name, 594 isconst=True if 'const' in cdecl else False, 595 isoptional=self.paramIsOptional(member), 596 iscount=True if name in lens else False, 597 len=self.getLen(member), 598 extstructs=extstructs, 599 cdecl=cdecl, 600 islocal=False, 601 iscreate=False)) 602 self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo)) 603 # 604 # Insert a lock_guard line 605 def lock_guard(self, indent): 606 return '%sstd::lock_guard<std::mutex> lock(global_lock);\n' % indent 607 # 608 # Determine if a struct has an object as a member or an embedded member 609 def struct_contains_object(self, struct_item): 610 struct_member_dict = dict(self.structMembers) 611 struct_members = struct_member_dict[struct_item] 612 613 for member in struct_members: 614 if self.isHandleTypeObject(member.type): 615 return True 616 # recurse for member structs, guard against infinite recursion 617 elif member.type in struct_member_dict and member.type != struct_item: 618 if self.struct_contains_object(member.type): 619 return True 620 return False 621 # 622 # Return list of struct members which contain, or whose sub-structures contain an obj in a given list of parameters or members 623 def getParmeterStructsWithObjects(self, item_list): 624 struct_list = set() 625 for item in item_list: 626 paramtype = item.find('type') 627 typecategory = self.getTypeCategory(paramtype.text) 628 if typecategory == 'struct': 629 if self.struct_contains_object(paramtype.text) == True: 630 struct_list.add(item) 631 return struct_list 632 # 633 # Return list of objects from a given list of parameters or members 634 def getObjectsInParameterList(self, item_list, create_func): 635 object_list = set() 636 if create_func == True: 637 member_list = item_list[0:-1] 638 else: 639 member_list = item_list 640 for item in member_list: 641 if self.isHandleTypeObject(paramtype.text): 642 object_list.add(item) 643 return object_list 644 # 645 # Construct list of extension structs containing handles, or extension structs that share a <validextensionstructs> 646 # tag WITH an extension struct containing handles. 647 def GenerateCommandWrapExtensionList(self): 648 for struct in self.structMembers: 649 if (len(struct.members) > 1) and struct.members[1].extstructs is not None: 650 found = False; 651 for item in struct.members[1].extstructs.split(','): 652 if item != '' and self.struct_contains_object(item) == True: 653 found = True 654 if found == True: 655 for item in struct.members[1].extstructs.split(','): 656 if item != '' and item not in self.extension_structs: 657 self.extension_structs.append(item) 658 # 659 # Returns True if a struct may have a pNext chain containing an object 660 def StructWithExtensions(self, struct_type): 661 if struct_type in self.struct_member_dict: 662 param_info = self.struct_member_dict[struct_type] 663 if (len(param_info) > 1) and param_info[1].extstructs is not None: 664 for item in param_info[1].extstructs.split(','): 665 if item in self.extension_structs: 666 return True 667 return False 668 # 669 # Generate VulkanObjectType from object type 670 def GetVulkanObjType(self, type): 671 return 'kVulkanObjectType%s' % type[2:] 672 # 673 # Return correct dispatch table type -- instance or device 674 def GetDispType(self, type): 675 return 'instance' if type in ['VkInstance', 'VkPhysicalDevice'] else 'device' 676 # 677 # Generate source for creating a Vulkan object 678 def generate_create_object_code(self, indent, proto, params, cmd_info, allocator): 679 create_obj_code = '' 680 handle_type = params[-1].find('type') 681 is_create_pipelines = False 682 683 if self.isHandleTypeObject(handle_type.text): 684 # Check for special case where multiple handles are returned 685 object_array = False 686 if cmd_info[-1].len is not None: 687 object_array = True; 688 handle_name = params[-1].find('name') 689 object_dest = '*%s' % handle_name.text 690 if object_array == True: 691 if 'CreateGraphicsPipelines' in proto.text or 'CreateComputePipelines' in proto.text or 'CreateRayTracingPipelines' in proto.text: 692 is_create_pipelines = True 693 create_obj_code += '%sif (VK_ERROR_VALIDATION_FAILED_EXT == result) return;\n' % indent 694 countispointer = '' 695 if 'uint32_t*' in cmd_info[-2].cdecl: 696 countispointer = '*' 697 create_obj_code += '%sfor (uint32_t index = 0; index < %s%s; index++) {\n' % (indent, countispointer, cmd_info[-1].len) 698 indent = self.incIndent(indent) 699 object_dest = '%s[index]' % cmd_info[-1].name 700 701 dispobj = params[0].find('type').text 702 if is_create_pipelines: 703 create_obj_code += '%sif (!pPipelines[index]) continue;\n' % indent 704 create_obj_code += '%sCreateObject(%s, %s, %s, %s);\n' % (indent, params[0].find('name').text, object_dest, self.GetVulkanObjType(cmd_info[-1].type), allocator) 705 if object_array == True: 706 indent = self.decIndent(indent) 707 create_obj_code += '%s}\n' % indent 708 indent = self.decIndent(indent) 709 return create_obj_code 710 # 711 # Generate source for destroying a non-dispatchable object 712 def generate_destroy_object_code(self, indent, proto, cmd_info): 713 validate_code = '' 714 record_code = '' 715 object_array = False 716 if True in [destroy_txt in proto.text for destroy_txt in ['Destroy', 'Free']]: 717 # Check for special case where multiple handles are returned 718 if cmd_info[-1].len is not None: 719 object_array = True; 720 param = -1 721 else: 722 param = -2 723 compatalloc_vuid_string = '%s-compatalloc' % cmd_info[param].name 724 nullalloc_vuid_string = '%s-nullalloc' % cmd_info[param].name 725 compatalloc_vuid = self.manual_vuids.get(compatalloc_vuid_string, "kVUIDUndefined") 726 nullalloc_vuid = self.manual_vuids.get(nullalloc_vuid_string, "kVUIDUndefined") 727 if self.isHandleTypeObject(cmd_info[param].type) == True: 728 if object_array == True: 729 # This API is freeing an array of handles -- add loop control 730 validate_code += 'HEY, NEED TO DESTROY AN ARRAY\n' 731 else: 732 dispobj = cmd_info[0].type 733 # Call Destroy a single time 734 validate_code += '%sskip |= ValidateDestroyObject(%s, %s, %s, pAllocator, %s, %s);\n' % (indent, cmd_info[0].name, cmd_info[param].name, self.GetVulkanObjType(cmd_info[param].type), compatalloc_vuid, nullalloc_vuid) 735 record_code += '%sRecordDestroyObject(%s, %s, %s);\n' % (indent, cmd_info[0].name, cmd_info[param].name, self.GetVulkanObjType(cmd_info[param].type)) 736 return object_array, validate_code, record_code 737 # 738 # Output validation for a single object (obj_count is NULL) or a counted list of objects 739 def outputObjects(self, obj_type, obj_name, obj_count, prefix, index, indent, disp_name, parent_name, null_allowed, top_level): 740 pre_call_code = '' 741 param_suffix = '%s-parameter' % (obj_name) 742 parent_suffix = '%s-parent' % (obj_name) 743 param_vuid = self.GetVuid(parent_name, param_suffix) 744 parent_vuid = self.GetVuid(parent_name, parent_suffix) 745 746 # If no parent VUID for this member, look for a commonparent VUID 747 if parent_vuid == 'kVUIDUndefined': 748 parent_vuid = self.GetVuid(parent_name, 'commonparent') 749 if obj_count is not None: 750 pre_call_code += '%sfor (uint32_t %s = 0; %s < %s; ++%s) {\n' % (indent, index, index, obj_count, index) 751 indent = self.incIndent(indent) 752 pre_call_code += '%sskip |= ValidateObject(%s, %s%s[%s], %s, %s, %s, %s);\n' % (indent, disp_name, prefix, obj_name, index, self.GetVulkanObjType(obj_type), null_allowed, param_vuid, parent_vuid) 753 indent = self.decIndent(indent) 754 pre_call_code += '%s}\n' % indent 755 else: 756 pre_call_code += '%sskip |= ValidateObject(%s, %s%s, %s, %s, %s, %s);\n' % (indent, disp_name, prefix, obj_name, self.GetVulkanObjType(obj_type), null_allowed, param_vuid, parent_vuid) 757 return pre_call_code 758 # 759 # first_level_param indicates if elements are passed directly into the function else they're below a ptr/struct 760 def validate_objects(self, members, indent, prefix, array_index, disp_name, parent_name, first_level_param): 761 pre_code = '' 762 index = 'index%s' % str(array_index) 763 array_index += 1 764 # Process any objects in this structure and recurse for any sub-structs in this struct 765 for member in members: 766 # Handle objects 767 if member.iscreate and first_level_param and member == members[-1]: 768 continue 769 if self.isHandleTypeObject(member.type) == True: 770 count_name = member.len 771 if (count_name is not None): 772 count_name = '%s%s' % (prefix, member.len) 773 null_allowed = member.isoptional 774 tmp_pre = self.outputObjects(member.type, member.name, count_name, prefix, index, indent, disp_name, parent_name, str(null_allowed).lower(), first_level_param) 775 pre_code += tmp_pre 776 # Handle Structs that contain objects at some level 777 elif member.type in self.struct_member_dict: 778 # Structs at first level will have an object 779 if self.struct_contains_object(member.type) == True: 780 struct_info = self.struct_member_dict[member.type] 781 # TODO (jbolz): Can this use paramIsPointer? 782 ispointer = '*' in member.cdecl; 783 # Struct Array 784 if member.len is not None: 785 # Update struct prefix 786 new_prefix = '%s%s' % (prefix, member.name) 787 pre_code += '%sif (%s%s) {\n' % (indent, prefix, member.name) 788 indent = self.incIndent(indent) 789 pre_code += '%sfor (uint32_t %s = 0; %s < %s%s; ++%s) {\n' % (indent, index, index, prefix, member.len, index) 790 indent = self.incIndent(indent) 791 local_prefix = '%s[%s].' % (new_prefix, index) 792 # Process sub-structs in this struct 793 tmp_pre = self.validate_objects(struct_info, indent, local_prefix, array_index, disp_name, member.type, False) 794 pre_code += tmp_pre 795 indent = self.decIndent(indent) 796 pre_code += '%s}\n' % indent 797 indent = self.decIndent(indent) 798 pre_code += '%s}\n' % indent 799 # Single Struct 800 elif ispointer: 801 # Update struct prefix 802 new_prefix = '%s%s->' % (prefix, member.name) 803 # Declare safe_VarType for struct 804 pre_code += '%sif (%s%s) {\n' % (indent, prefix, member.name) 805 indent = self.incIndent(indent) 806 # Process sub-structs in this struct 807 tmp_pre = self.validate_objects(struct_info, indent, new_prefix, array_index, disp_name, member.type, False) 808 pre_code += tmp_pre 809 indent = self.decIndent(indent) 810 pre_code += '%s}\n' % indent 811 return pre_code 812 # 813 # For a particular API, generate the object handling code 814 def generate_wrapping_code(self, cmd): 815 indent = ' ' 816 pre_call_validate = '' 817 pre_call_record = '' 818 post_call_record = '' 819 820 destroy_array = False 821 validate_destroy_code = '' 822 record_destroy_code = '' 823 824 proto = cmd.find('proto/name') 825 params = cmd.findall('param') 826 if proto.text is not None: 827 cmddata = self.cmd_info_dict[proto.text] 828 cmd_info = cmddata.members 829 disp_name = cmd_info[0].name 830 # Handle object create operations if last parameter is created by this call 831 if cmddata.iscreate: 832 post_call_record += self.generate_create_object_code(indent, proto, params, cmd_info, cmddata.allocator) 833 # Handle object destroy operations 834 if cmddata.isdestroy: 835 (destroy_array, validate_destroy_code, record_destroy_code) = self.generate_destroy_object_code(indent, proto, cmd_info) 836 837 pre_call_record += record_destroy_code 838 pre_call_validate += self.validate_objects(cmd_info, indent, '', 0, disp_name, proto.text, True) 839 pre_call_validate += validate_destroy_code 840 841 return pre_call_validate, pre_call_record, post_call_record 842 # 843 # Capture command parameter info needed to create, destroy, and validate objects 844 def genCmd(self, cmdinfo, cmdname, alias): 845 846 # Add struct-member type information to command parameter information 847 OutputGenerator.genCmd(self, cmdinfo, cmdname, alias) 848 members = cmdinfo.elem.findall('.//param') 849 # Iterate over members once to get length parameters for arrays 850 lens = set() 851 for member in members: 852 length = self.getLen(member) 853 if length: 854 lens.add(length) 855 struct_member_dict = dict(self.structMembers) 856 857 # Set command invariant information needed at a per member level in validate... 858 is_create_command = any(filter(lambda pat: pat in cmdname, ('Create', 'Allocate', 'Enumerate', 'RegisterDeviceEvent', 'RegisterDisplayEvent'))) 859 last_member_is_pointer = len(members) and self.paramIsPointer(members[-1]) 860 iscreate = is_create_command or ('vkGet' in cmdname and last_member_is_pointer) 861 isdestroy = any([destroy_txt in cmdname for destroy_txt in ['Destroy', 'Free']]) 862 863 # Generate member info 864 membersInfo = [] 865 constains_extension_structs = False 866 allocator = 'nullptr' 867 for member in members: 868 # Get type and name of member 869 info = self.getTypeNameTuple(member) 870 type = info[0] 871 name = info[1] 872 cdecl = self.makeCParamDecl(member, 0) 873 # Check for parameter name in lens set 874 iscount = True if name in lens else False 875 length = self.getLen(member) 876 isconst = True if 'const' in cdecl else False 877 # Mark param as local if it is an array of objects 878 islocal = False; 879 if self.isHandleTypeObject(type) == True: 880 if (length is not None) and (isconst == True): 881 islocal = True 882 # Or if it's a struct that contains an object 883 elif type in struct_member_dict: 884 if self.struct_contains_object(type) == True: 885 islocal = True 886 if type == 'VkAllocationCallbacks': 887 allocator = name 888 extstructs = member.attrib.get('validextensionstructs') if name == 'pNext' else None 889 membersInfo.append(self.CommandParam(type=type, 890 name=name, 891 isconst=isconst, 892 isoptional=self.paramIsOptional(member), 893 iscount=iscount, 894 len=length, 895 extstructs=extstructs, 896 cdecl=cdecl, 897 islocal=islocal, 898 iscreate=iscreate)) 899 900 self.cmd_list.append(cmdname) 901 self.cmd_info_dict[cmdname] =self.CmdInfoData(name=cmdname, cmdinfo=cmdinfo, members=membersInfo, iscreate=iscreate, isdestroy=isdestroy, allocator=allocator, extra_protect=self.featureExtraProtect, alias=alias) 902 # 903 # Create code Create, Destroy, and validate Vulkan objects 904 def WrapCommands(self): 905 for cmdname in self.cmd_list: 906 cmddata = self.cmd_info_dict[cmdname] 907 cmdinfo = cmddata.cmdinfo 908 if cmdname in self.interface_functions: 909 continue 910 manual = False 911 if cmdname in self.no_autogen_list: 912 manual = True 913 914 # Generate object handling code 915 (pre_call_validate, pre_call_record, post_call_record) = self.generate_wrapping_code(cmdinfo.elem) 916 917 feature_extra_protect = cmddata.extra_protect 918 if (feature_extra_protect is not None): 919 self.appendSection('command', '') 920 self.appendSection('command', '#ifdef '+ feature_extra_protect) 921 self.prototypes += [ '#ifdef %s' % feature_extra_protect ] 922 923 # Add intercept to procmap 924 self.prototypes += [ ' {"%s", (void*)%s},' % (cmdname,cmdname[2:]) ] 925 926 decls = self.makeCDecls(cmdinfo.elem) 927 928 # Gather the parameter items 929 params = cmdinfo.elem.findall('param/name') 930 # Pull out the text for each of the parameters, separate them by commas in a list 931 paramstext = ', '.join([str(param.text) for param in params]) 932 # Generate the API call template 933 fcn_call = cmdinfo.elem.attrib.get('name').replace('vk', 'TOKEN', 1) + '(' + paramstext + ');' 934 935 func_decl_template = decls[0][:-1].split('VKAPI_CALL ') 936 func_decl_template = func_decl_template[1] 937 938 result_type = cmdinfo.elem.find('proto/type') 939 940 if 'object_tracker.h' in self.genOpts.filename: 941 # Output PreCallValidateAPI prototype if necessary 942 if pre_call_validate: 943 pre_cv_func_decl = 'bool PreCallValidate' + func_decl_template + ';' 944 self.appendSection('command', pre_cv_func_decl) 945 946 # Output PreCallRecordAPI prototype if necessary 947 if pre_call_record: 948 pre_cr_func_decl = 'void PreCallRecord' + func_decl_template + ';' 949 self.appendSection('command', pre_cr_func_decl) 950 951 # Output PosCallRecordAPI prototype if necessary 952 if post_call_record: 953 post_cr_func_decl = 'void PostCallRecord' + func_decl_template + ';' 954 if result_type.text == 'VkResult': 955 post_cr_func_decl = post_cr_func_decl.replace(')', ',\n VkResult result)') 956 self.appendSection('command', post_cr_func_decl) 957 958 if 'object_tracker.cpp' in self.genOpts.filename: 959 # Output PreCallValidateAPI function if necessary 960 if pre_call_validate and not manual: 961 pre_cv_func_decl = 'bool ObjectLifetimes::PreCallValidate' + func_decl_template + ' {' 962 self.appendSection('command', '') 963 self.appendSection('command', pre_cv_func_decl) 964 self.appendSection('command', ' bool skip = false;') 965 self.appendSection('command', pre_call_validate) 966 self.appendSection('command', ' return skip;') 967 self.appendSection('command', '}') 968 969 # Output PreCallRecordAPI function if necessary 970 if pre_call_record and not manual: 971 pre_cr_func_decl = 'void ObjectLifetimes::PreCallRecord' + func_decl_template + ' {' 972 self.appendSection('command', '') 973 self.appendSection('command', pre_cr_func_decl) 974 self.appendSection('command', pre_call_record) 975 self.appendSection('command', '}') 976 977 # Output PosCallRecordAPI function if necessary 978 if post_call_record and not manual: 979 post_cr_func_decl = 'void ObjectLifetimes::PostCallRecord' + func_decl_template + ' {' 980 self.appendSection('command', '') 981 982 if result_type.text == 'VkResult': 983 post_cr_func_decl = post_cr_func_decl.replace(')', ',\n VkResult result)') 984 # The two createpipelines APIs may create on failure -- skip the success result check 985 if 'CreateGraphicsPipelines' not in cmdname and 'CreateComputePipelines' not in cmdname and 'CreateRayTracingPipelines' not in cmdname: 986 post_cr_func_decl = post_cr_func_decl.replace('{', '{\n if (result != VK_SUCCESS) return;') 987 self.appendSection('command', post_cr_func_decl) 988 989 990 self.appendSection('command', post_call_record) 991 self.appendSection('command', '}') 992 993 if (feature_extra_protect is not None): 994 self.appendSection('command', '#endif // '+ feature_extra_protect) 995 self.prototypes += [ '#endif' ] 996