| #!/usr/bin/python3 -i |
| # |
| # Copyright (c) 2015-2025 The Khronos Group Inc. |
| # Copyright (c) 2015-2025 Valve Corporation |
| # Copyright (c) 2015-2025 LunarG, Inc. |
| # Copyright (c) 2015-2025 Google Inc. |
| # Copyright (C) 2025 Arm Limited. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import sys |
| import os |
| from vulkan_object import Member |
| from base_generator import BaseGenerator |
| from generators.generator_utils import PlatformGuardHelper |
| |
| # This class is a container for any source code, data, or other behavior that is necessary to |
| # customize the generator script for a specific target API variant (e.g. Vulkan SC). As such, |
| # all of these API-specific interfaces and their use in the generator script are part of the |
| # contract between this repository and its downstream users. Changing or removing any of these |
| # interfaces or their use in the generator script will have downstream effects and thus |
| # should be avoided unless absolutely necessary. |
| class APISpecific: |
| # Returns the list of validation layers for the target API |
| @staticmethod |
| def getValidationLayerList(targetApiName: str) -> list[dict[str, str]]: |
| match targetApiName: |
| |
| # Vulkan specific validation layer list |
| case 'vulkan': |
| return [ |
| { |
| 'include': 'thread_tracker/thread_safety_validation.h', |
| 'device': 'threadsafety::Device', |
| 'instance': 'threadsafety::Instance', |
| 'type': 'LayerObjectTypeThreading', |
| 'enabled': '!settings.disabled[thread_safety]' |
| }, |
| { |
| 'include': 'stateless/stateless_validation.h', |
| 'device': 'stateless::Device', |
| 'instance': 'stateless::Instance', |
| 'type': 'LayerObjectTypeParameterValidation', |
| 'enabled': '!settings.disabled[stateless_checks]' |
| }, |
| { |
| 'include': 'generated/legacy.h', |
| 'device': 'legacy::Device', |
| 'instance': 'legacy::Instance', |
| 'type': 'LayerObjectTypeLegacy', |
| 'enabled': 'settings.enabled[legacy_detection]' |
| }, |
| { |
| 'include': 'object_tracker/object_lifetime_validation.h', |
| 'device': 'object_lifetimes::Device', |
| 'instance': 'object_lifetimes::Instance', |
| 'type': 'LayerObjectTypeObjectTracker', |
| 'enabled': '!settings.disabled[object_tracking]' |
| }, |
| { |
| 'include': 'state_tracker/state_tracker.h', |
| 'device': 'vvl::DeviceState', |
| 'instance': 'vvl::InstanceState', |
| 'type': 'LayerObjectTypeStateTracker', |
| 'enabled': ''' |
| !settings.disabled[core_checks] || |
| settings.enabled[best_practices] || |
| settings.enabled[gpu_validation] || |
| settings.enabled[debug_printf_validation] || |
| settings.enabled[sync_validation] |
| ''' |
| }, |
| { |
| 'include': 'core_checks/core_validation.h', |
| 'device': 'CoreChecks', |
| 'instance': 'core::Instance', |
| 'type': 'LayerObjectTypeCoreValidation', |
| 'enabled': '!settings.disabled[core_checks]' |
| }, |
| { |
| 'include': 'best_practices/best_practices_validation.h', |
| 'device': 'BestPractices', |
| 'instance': 'bp_state::Instance', |
| 'type': 'LayerObjectTypeBestPractices', |
| 'enabled': 'settings.enabled[best_practices]' |
| }, |
| { |
| 'include': 'gpuav/core/gpuav.h', |
| 'device': 'gpuav::Validator', |
| 'instance': 'gpuav::Instance', |
| 'type': 'LayerObjectTypeGpuAssisted', |
| 'enabled': 'settings.enabled[gpu_validation] || settings.enabled[debug_printf_validation]' |
| }, |
| { |
| 'include': 'sync/sync_validation.h', |
| 'device': 'syncval::SyncValidator', |
| 'instance': 'syncval::Instance', |
| 'type': 'LayerObjectTypeSyncValidation', |
| 'enabled': 'settings.enabled[sync_validation]' |
| } |
| ] |
| |
| |
| class DispatchObjectGenerator(BaseGenerator): |
| def __init__(self): |
| BaseGenerator.__init__(self) |
| |
| # Commands which are not autogenerated but still intercepted |
| self.no_autogen_list = ( |
| 'vkCreateInstance', |
| 'vkDestroyInstance', |
| 'vkCreateDevice', |
| 'vkDestroyDevice', |
| # Need to handle Acquired swapchain image handles |
| 'vkGetSwapchainImagesKHR', |
| 'vkDestroySwapchainKHR', |
| # Have issues with generating logic to work correctly with Safe Struct |
| 'vkQueuePresentKHR', |
| 'vkCreateGraphicsPipelines', |
| 'vkCreateComputePipelines', |
| 'vkCreateRayTracingPipelinesNV', |
| 'vkCreateRayTracingPipelinesKHR', |
| 'vkCreateDataGraphPipelinesARM', |
| # Need to only wrap on certain cases |
| 'vkCreateShadersEXT', |
| # Need handle which pool descriptors were allocated from |
| 'vkResetDescriptorPool', |
| 'vkDestroyDescriptorPool', |
| 'vkAllocateDescriptorSets', |
| 'vkFreeDescriptorSets', |
| 'vkCreateDescriptorUpdateTemplate', |
| 'vkCreateDescriptorUpdateTemplateKHR', |
| 'vkDestroyDescriptorUpdateTemplate', |
| 'vkDestroyDescriptorUpdateTemplateKHR', |
| 'vkUpdateDescriptorSetWithTemplate', |
| 'vkUpdateDescriptorSetWithTemplateKHR', |
| 'vkCmdPushDescriptorSetWithTemplate', |
| 'vkCmdPushDescriptorSetWithTemplateKHR', |
| 'vkCmdPushDescriptorSetWithTemplate2', |
| 'vkCmdPushDescriptorSetWithTemplate2KHR', |
| # Tracking renderpass state for the pipeline safe struct |
| 'vkCreateRenderPass', |
| 'vkCreateRenderPass2KHR', |
| 'vkCreateRenderPass2', |
| 'vkDestroyRenderPass', |
| # Accesses to the map itself are internally synchronized. |
| 'vkDebugMarkerSetObjectTagEXT', |
| 'vkDebugMarkerSetObjectNameEXT', |
| 'vkSetDebugUtilsObjectNameEXT', |
| 'vkSetDebugUtilsObjectTagEXT', |
| # TODO - These have no manual source, but we still produce the headers |
| 'vkEnumerateInstanceExtensionProperties', |
| 'vkEnumerateInstanceLayerProperties', |
| 'vkEnumerateDeviceLayerProperties', |
| 'vkEnumerateInstanceVersion', |
| # Manually track pToolCount |
| 'vkGetPhysicalDeviceToolProperties', |
| 'vkGetPhysicalDeviceToolPropertiesEXT', |
| # Track deferred operations |
| 'vkDeferredOperationJoinKHR', |
| 'vkGetDeferredOperationResultKHR', |
| # Need to deal with VkAccelerationStructureGeometryKHR |
| 'vkBuildAccelerationStructuresKHR', |
| 'vkGetAccelerationStructureBuildSizesKHR', |
| 'vkCmdBuildAccelerationStructuresKHR', |
| # Depends on the VkDescriptorType to pick the union pointer |
| 'vkGetDescriptorEXT', |
| # Depends on the VkIndirectExecutionSetInfoTypeEXT to pick the union pointer |
| 'vkCreateIndirectExecutionSetEXT', |
| # Special destroy call from the Acquire |
| 'vkReleasePerformanceConfigurationINTEL', |
| # need to call CopyExportMetalObjects |
| 'vkExportMetalObjectsEXT', |
| # These are for special-casing the pInheritanceInfo issue (must be ignored for primary CBs) |
| 'vkAllocateCommandBuffers', |
| 'vkFreeCommandBuffers', |
| 'vkDestroyCommandPool', |
| 'vkBeginCommandBuffer', |
| # Currently we don't properly generate a Wrap and will accidently unwrap the VkDisplayKHR handle |
| 'vkGetPhysicalDeviceDisplayPropertiesKHR', |
| 'vkGetPhysicalDeviceDisplayProperties2KHR', |
| 'vkGetPhysicalDeviceDisplayPlanePropertiesKHR', |
| 'vkGetPhysicalDeviceDisplayPlaneProperties2KHR', |
| 'vkGetDisplayModePropertiesKHR', |
| 'vkGetDisplayModeProperties2KHR', |
| 'vkGetDisplayPlaneSupportedDisplaysKHR', |
| # Need to handle binaries in VkPipelineBinaryHandlesInfoKHR as output, not input. |
| 'vkCreatePipelineBinariesKHR', |
| 'vkGetPipelineKeyKHR', |
| # Need to handle VkBindMemoryStatus |
| 'vkBindBufferMemory2', |
| 'vkBindBufferMemory2KHR', |
| 'vkBindImageMemory2', |
| 'vkBindImageMemory2KHR', |
| ) |
| |
| # List of all extension structs strings containing handles |
| self.ndo_extension_structs = [ |
| # These are added manually because vkGetPipelineKeyKHR needs to unwrap these (see VUID 09604) |
| "VkComputePipelineCreateInfo", |
| "VkGraphicsPipelineCreateInfo", |
| "VkRayTracingPipelineCreateInfoKHR", |
| "VkExecutionGraphPipelineCreateInfoAMDX", |
| ] |
| |
| self.extended_query_exts = ( |
| 'VK_KHR_get_physical_device_properties2', |
| 'VK_KHR_external_semaphore_capabilities', |
| 'VK_KHR_external_fence_capabilities', |
| 'VK_KHR_external_memory_capabilities', |
| 'VK_KHR_get_memory_requirements2', |
| ) |
| |
| # Dispatch functions that need special state tracking variables passed in |
| self.custom_definition = {} |
| |
| |
| def isNonDispatchable(self, name: str) -> bool: |
| return name in self.vk.handles and not self.vk.handles[name].dispatchable |
| |
| def containsNonDispatchableObject(self, structName: str) -> bool: |
| struct = self.vk.structs[structName] |
| for member in struct.members: |
| if self.isNonDispatchable(member.type): |
| return True |
| # recurse for member structs, guard against infinite recursion |
| elif member.type in self.vk.structs and member.type != struct.name: |
| if self.containsNonDispatchableObject(member.type): |
| return True |
| return False |
| |
| # Now that the data is all collected and complete, generate and output the wrapping/unwrapping routines |
| def generate(self): |
| self.write(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT *** |
| // See {os.path.basename(__file__)} for modifications |
| |
| /*************************************************************************** |
| * |
| * Copyright (c) 2015-2025 The Khronos Group Inc. |
| * Copyright (c) 2015-2025 Valve Corporation |
| * Copyright (c) 2015-2025 LunarG, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| ****************************************************************************/\n''') |
| self.write('// NOLINTBEGIN') # Wrap for clang-tidy to ignore |
| |
| if self.filename == 'dispatch_object_instance_methods.h': |
| self.generateInstanceMethods() |
| elif self.filename == 'dispatch_object_device_methods.h': |
| self.generateDeviceMethods() |
| elif self.filename == 'dispatch_functions.h': |
| self.generateFunctions() |
| elif self.filename == 'dispatch_object.cpp': |
| self.generateSource() |
| else: |
| self.write(f'\nFile name {self.filename} has no code to generate\n') |
| |
| self.write('// NOLINTEND') # Wrap for clang-tidy to ignore |
| |
| def generateMethods(self, want_instance): |
| out = [] |
| guard_helper = PlatformGuardHelper() |
| for command in [x for x in self.vk.commands.values() if x.instance == want_instance]: |
| prototype = command.cPrototype |
| prototype = prototype.replace('VKAPI_ATTR ', '') |
| prototype = prototype.replace('VKAPI_CALL vk', '') |
| if command.name in self.custom_definition: |
| prototype = prototype.replace(');', f'{self.custom_definition[command.name]});') |
| out.extend(guard_helper.add_guard(command.protect)) |
| out.append(f'{prototype}\n') |
| out.extend(guard_helper.add_guard(None)) |
| self.write("".join(out)) |
| |
| def generateDeviceMethods(self): |
| out = [] |
| out.append(''' |
| // This file contains methods for class vvl::dispatch::Device and it is designed to ONLY be |
| // included into dispatch_object.h. |
| |
| #pragma once |
| |
| ''') |
| self.write("".join(out)) |
| self.generateMethods(False) |
| |
| def generateInstanceMethods(self): |
| out = [] |
| out.append(''' |
| // This file contains methods for class vvl::dispatch::Instance and it is designed to ONLY be |
| // included into dispatch_object.h. |
| |
| #pragma once |
| ''') |
| self.write("".join(out)) |
| self.generateMethods(True) |
| |
| def generateFunctions(self): |
| out = [] |
| out.append(''' |
| // This file contains contains convience functions for non-chassis code that needs to |
| // make vulkan calls. |
| |
| #pragma once |
| |
| #include "chassis/dispatch_object.h" |
| |
| ''') |
| dispatchable_handles = [handle.name for handle in self.vk.handles.values() if handle.dispatchable] |
| guard_helper = PlatformGuardHelper() |
| for command in self.vk.commands.values(): |
| # Weed out weird commands like vkCreateInstance() |
| if command.params[0].type not in dispatchable_handles: |
| continue |
| |
| prototype = command.cPrototype |
| prototype = prototype.replace('VKAPI_ATTR ', 'static inline ') |
| prototype = prototype.replace('VKAPI_CALL vk', 'Dispatch') |
| proto_extra = '' |
| call_extra = '' |
| if command.name in self.custom_definition: |
| proto_extra = self.custom_definition[command.name] |
| call_extra = ', ' + proto_extra.split(' ')[-1] |
| prototype = prototype.replace(');', f'{proto_extra}) {{') |
| out.extend(guard_helper.add_guard(command.protect)) |
| out.append(f'\n{prototype}\n') |
| out.append(f'auto dispatch = vvl::dispatch::GetData({command.params[0].name});\n') |
| returnResult = 'return ' if (command.returnType != 'void') else '' |
| paramsList = ', '.join([param.name for param in command.params]) |
| out.append(f'{returnResult}{command.name.replace("vk", "dispatch->")}({paramsList}{call_extra});\n') |
| out.append('}\n') |
| out.extend(guard_helper.add_guard(None)) |
| out.append('// We make many internal dispatch calls to extended query functions which can depend on the API version\n') |
| for extended_query_ext in self.extended_query_exts: |
| for command in self.vk.extensions[extended_query_ext].commands: |
| parameters = (command.cPrototype.split('(')[1])[:-2] # leaves just the parameters |
| arguments = ','.join([x.name for x in command.params]) |
| out.append(f''' |
| static inline {command.returnType} Dispatch{command.alias[2:]}Helper(APIVersion api_version, {parameters}) {{ |
| if (api_version >= VK_API_VERSION_1_1) {{ |
| return Dispatch{command.alias[2:]}({arguments}); |
| }} else {{ |
| return Dispatch{command.name[2:]}({arguments}); |
| }} |
| }} |
| ''') |
| self.write("".join(out)) |
| |
| def generateSource(self): |
| # Construct list of extension structs containing handles |
| # Generate the list of APIs that might need to handle wrapped extension structs |
| for struct in [x for x in self.vk.structs.values() if x.sType and x.extendedBy]: |
| for extendedStruct in struct.extendedBy: |
| if self.containsNonDispatchableObject(extendedStruct) and extendedStruct not in self.ndo_extension_structs: |
| self.ndo_extension_structs.append(extendedStruct) |
| |
| out = [] |
| out.append(''' |
| #include "chassis/dispatch_object.h" |
| #include "utils/cast_utils.h" |
| #include <vulkan/utility/vk_safe_struct.hpp> |
| #include "state_tracker/pipeline_state.h" |
| #include "containers/custom_containers.h" |
| |
| ''') |
| for layer in APISpecific.getValidationLayerList(self.targetApiName): |
| include_file = layer['include'] |
| out.append(f'#include "{include_file}"\n') |
| out.append('\n') |
| |
| out.append(''' |
| #define DISPATCH_MAX_STACK_ALLOCATIONS 32 |
| |
| namespace vvl { |
| namespace dispatch { |
| |
| void Instance::InitValidationObjects() { |
| // Note that this DEFINES THE ORDER IN WHICH THE LAYER VALIDATION OBJECTS ARE CALLED |
| ''') |
| for layer in APISpecific.getValidationLayerList(self.targetApiName): |
| classname = layer['instance'] |
| out.append(f''' |
| if ({layer["enabled"]}) {{ |
| object_dispatch.emplace_back(new {classname}(this)); |
| }}''') |
| |
| out.append('\n') |
| out.append('}\n') |
| out.append(''' |
| void Device::InitValidationObjects() { |
| // Note that this DEFINES THE ORDER IN WHICH THE LAYER VALIDATION OBJECTS ARE CALLED |
| ''') |
| for layer in APISpecific.getValidationLayerList(self.targetApiName): |
| classname = layer['device'] |
| layer_type = layer['type'] |
| instance = layer['instance'] |
| instance_arg = f'static_cast<{instance}*>(dispatch_instance->GetValidationObject({layer_type}))' |
| out.append(f''' |
| if ({layer["enabled"]}) {{ |
| object_dispatch.emplace_back(new {classname}(this, {instance_arg})); |
| }}''') |
| out.append('\n') |
| out.append('}\n') |
| |
| out.append(''' |
| // Unique Objects pNext extension handling function |
| void HandleWrapper::UnwrapPnextChainHandles(const void *pNext) { |
| void *cur_pnext = const_cast<void *>(pNext); |
| while (cur_pnext != nullptr) { |
| VkBaseOutStructure *header = reinterpret_cast<VkBaseOutStructure *>(cur_pnext); |
| |
| switch (header->sType) { |
| ''') |
| guard_helper = PlatformGuardHelper() |
| for struct in [self.vk.structs[x] for x in self.ndo_extension_structs]: |
| (api_decls, api_pre, api_post) = self.uniquifyMembers(struct.members, 'safe_struct->', 0, False, False, False) |
| # Only process extension structs containing handles |
| if not api_pre: |
| continue |
| safe_name = 'vku::safe_' + struct.name |
| out.extend(guard_helper.add_guard(struct.protect)) |
| out.append(f'case {struct.sType}: {{\n') |
| out.append(f' auto *safe_struct = reinterpret_cast<{safe_name} *>(cur_pnext);\n') |
| out.append(api_pre) |
| out.append('} break;\n') |
| out.extend(guard_helper.add_guard(None)) |
| out.append(''' |
| default: |
| break; |
| } |
| |
| // Process the next structure in the chain |
| cur_pnext = header->pNext; |
| } |
| } |
| ''') |
| |
| out.append(''' |
| [[maybe_unused]] static bool NotDispatchableHandle(VkObjectType object_type) { |
| switch(object_type) { |
| ''') |
| out.extend([f'case {handle.type}:\n' for handle in self.vk.handles.values() if handle.dispatchable]) |
| out.append('''return false; |
| default: |
| return true; |
| } |
| } |
| ''') |
| |
| for command in [x for x in self.vk.commands.values() if x.name not in self.no_autogen_list]: |
| out.extend(guard_helper.add_guard(command.protect)) |
| |
| # Generate NDO wrapping/unwrapping code for all parameters |
| isCreate = any(x in command.name for x in [ |
| 'vkCreate', |
| 'vkAllocate', |
| # Create a VkFence object |
| 'vkRegisterDeviceEvent', |
| 'vkRegisterDisplayEvent', |
| # Create VkPerformanceConfigurationINTEL object |
| 'vkAcquirePerformanceConfigurationINTEL', |
| # Special calls that we wrap because they are created statically on the driver, but users query for them |
| 'vkGetWinrtDisplayNV', |
| 'vkGetRandROutputDisplayEXT', |
| 'vkGetDrmDisplayEXT', |
| ]) |
| isDestroy = any(x in command.name for x in ['vkDestroy', 'vkFree']) |
| |
| # Handle ndo create/allocate operations |
| create_ndo_code = '' |
| if isCreate: |
| lastParam = command.params[-1] |
| handle_type = lastParam.type |
| if self.isNonDispatchable(handle_type): |
| # Check for special case where multiple handles are returned |
| wrap_call = 'WrapNew' if handle_type != 'VkDisplayKHR' else 'MaybeWrapDisplay' |
| ndo_array = lastParam.length is not None |
| create_ndo_code += 'if (result == VK_SUCCESS) {\n' |
| ndo_dest = f'*{lastParam.name}' |
| if ndo_array: |
| create_ndo_code += f'for (uint32_t index0 = 0; index0 < {lastParam.length}; index0++) {{\n' |
| ndo_dest = f'{lastParam.name}[index0]' |
| create_ndo_code += f'{ndo_dest} = {wrap_call}({ndo_dest});\n' |
| if ndo_array: |
| create_ndo_code += '}\n' |
| create_ndo_code += '}\n' |
| |
| # Handle ndo destroy/free operations |
| destroy_ndo_code = '' |
| if isDestroy: |
| param = command.params[-2] # Last param is always VkAllocationCallbacks |
| if self.isNonDispatchable(param.type): |
| # Remove a single handle from the map |
| destroy_ndo_code += f'{param.name} = Erase({param.name});' |
| |
| (api_decls, api_pre, api_post) = self.uniquifyMembers(command.params, '', 0, isCreate, isDestroy, True) |
| api_post += create_ndo_code |
| if isDestroy: |
| api_pre += destroy_ndo_code |
| elif api_pre: |
| api_pre = f'{{\n{api_pre}}}\n' |
| |
| # If API doesn't contain NDO's, we still need to make a down-chain call |
| down_chain_call_only = False |
| if not api_decls and not api_pre and not api_post: |
| down_chain_call_only = True |
| |
| prototype = command.cPrototype[:-1] |
| prototype = prototype.replace('VKAPI_ATTR ', '') |
| prototype = prototype.replace('VKAPI_CALL vk', 'Instance::' if command.instance else 'Device::') |
| out.append(f'\n{prototype} {{\n')\ |
| |
| # Pull out the text for each of the parameters, separate them by commas in a list |
| paramstext = ', '.join([param.name for param in command.params]) |
| wrapped_paramstext = paramstext |
| # If any of these paramters has been replaced by a local var, fix up the list |
| for param in command.params: |
| struct = self.vk.structs[param.type] if param.type in self.vk.structs else None |
| isLocal = (self.isNonDispatchable(param.type) and param.length and param.const) or (struct and self.containsNonDispatchableObject(struct.name)) |
| isExtended = struct and struct.extendedBy and any(x in self.ndo_extension_structs for x in struct.extendedBy) |
| if isLocal or isExtended: |
| if param.pointer: |
| if param.const: |
| wrapped_paramstext = wrapped_paramstext.replace(param.name, f'(const {param.type}*)local_{param.name}') |
| else: |
| wrapped_paramstext = wrapped_paramstext.replace(param.name, f'({param.type}*)local_{param.name}') |
| else: |
| wrapped_paramstext = wrapped_paramstext.replace(param.name, f'(const {param.type})local_{param.name}') |
| |
| # First, add check and down-chain call. Use correct dispatch table |
| dispatch_table = 'instance_dispatch_table' if command.instance else 'device_dispatch_table' |
| |
| # Put all this together for the final down-chain call |
| if not down_chain_call_only: |
| out.append(f'if (!wrap_handles) return {dispatch_table}.{command.name[2:]}({paramstext});\n') |
| |
| # Handle return values, if any |
| assignResult = f'{command.returnType} result = ' if command.returnType != 'void' else '' |
| |
| # Pre-pend declarations and pre-api-call codegen |
| if api_decls: |
| out.append("\n".join(str(api_decls).rstrip().split("\n"))) |
| if api_pre: |
| out.append("\n".join(str(api_pre).rstrip().split("\n"))) |
| out.append('\n') |
| # Generate the wrapped dispatch call |
| out.append(f'{assignResult}{dispatch_table}.{command.name[2:]}({wrapped_paramstext});\n') |
| |
| out.append("\n".join(str(api_post).rstrip().split("\n"))) |
| out.append('\n') |
| # Handle the return result variable, if any |
| if assignResult != '': |
| out.append('return result;\n') |
| out.append('}\n') |
| out.extend(guard_helper.add_guard(None)) |
| out.append('} // namespace dispatch\n') |
| out.append('} // namespace vvl\n') |
| |
| self.write("".join(out)) |
| |
| # |
| # Clean up local declarations |
| def cleanUpLocalDeclarations(self, prefix, name, len, deferred_name): |
| cleanup = '' |
| if deferred_name is not None: |
| delete_var = f'local_{prefix}{name}' |
| if len is None: |
| delete_code = f'delete {delete_var}' |
| else: |
| delete_code = f'delete[] {delete_var}' |
| cleanup = f'if ({delete_var}) {{\n' |
| cleanup += f''' |
| // Fix check for deferred ray tracing pipeline creation |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/5817 |
| const bool is_operation_deferred = ({deferred_name} != VK_NULL_HANDLE) && (result == VK_OPERATION_DEFERRED_KHR); |
| if (is_operation_deferred) {{ |
| std::vector<std::function<void()>> cleanup{{[{delete_var}](){{ {delete_code}; }}}}; |
| deferred_operation_post_completion.insert({deferred_name}, cleanup); |
| }} else {{ |
| {delete_code}; |
| }}''' |
| cleanup += '}\n' |
| return cleanup |
| |
| # |
| # topLevel indicates if elements are passed directly into the function else they're below a ptr/struct |
| # isCreate means that this is API creates or allocates NDOs |
| # isDestroy indicates that this API destroys or frees NDOs |
| def uniquifyMembers(self, members: list[Member], prefix: str, arrayIndex: int, isCreate: bool, isDestroy: bool, topLevel: bool): |
| decls = '' |
| pre_code = '' |
| post_code = '' |
| index = f'index{str(arrayIndex)}' |
| arrayIndex += 1 |
| # Process any NDOs in this structure and recurse for any sub-structs in this struct |
| for member in members: |
| # Handle NDOs |
| if self.isNonDispatchable(member.type): |
| count_name = member.length |
| if (count_name is not None) and not topLevel: |
| count_name = f'{prefix}{member.length}' |
| |
| # Handle the case when the member specifying the count is a pointer |
| for count_member in members: |
| if count_member.name == member.length and count_member.pointer: |
| count_name = f'*{count_name}' |
| |
| if (not topLevel) or (not isCreate) or (not member.pointer): |
| if count_name is not None: |
| if topLevel: |
| decls += f'small_vector<{member.type}, DISPATCH_MAX_STACK_ALLOCATIONS> var_local_{prefix}{member.name};\n' |
| decls += f'{member.type} *local_{prefix}{member.name} = nullptr;\n' |
| pre_code += f' if ({prefix}{member.name}) {{\n' |
| if topLevel: |
| pre_code += f''' |
| var_local_{prefix}{member.name}.resize({count_name}); |
| local_{prefix}{member.name} = var_local_{prefix}{member.name}.data(); |
| for (uint32_t {index} = 0; {index} < {count_name}; ++{index}) {{ |
| local_{prefix}{member.name}[{index}] = Unwrap({member.name}[{index}]);''' |
| else: |
| pre_code += f''' |
| for (uint32_t {index} = 0; {index} < {count_name}; ++{index}) {{ |
| {prefix}{member.name}[{index}] = Unwrap({prefix}{member.name}[{index}]);''' |
| pre_code += '}\n' |
| pre_code += '}\n' |
| else: |
| if topLevel: |
| if not isDestroy: |
| pre_code += f'{member.name} = Unwrap({member.name});\n' |
| else: |
| # Make temp copy of this var with the 'local' removed. It may be better to not pass in 'local_' |
| # as part of the string and explicitly print it |
| fix = str(prefix).strip('local_') |
| pre_code += f''' |
| if ({fix}{member.name}) {{ |
| {prefix}{member.name} = Unwrap({fix}{member.name}); |
| }}''' |
| # Handle Structs that contain NDOs at some level |
| elif member.type in self.vk.structs: |
| struct = self.vk.structs[member.type] |
| process_pnext = struct.extendedBy and any(x in self.ndo_extension_structs for x in struct.extendedBy) |
| # Structs at first level will have an NDO, OR, we need a safe_struct for the pnext chain |
| if self.containsNonDispatchableObject(member.type) or process_pnext: |
| safe_type = 'vku::safe_' + member.type if any(x.pointer for x in struct.members) else member.type |
| # Struct Array |
| if member.length is not None: |
| # Check if this function can be deferred. |
| deferred_name = next((x.name for x in members if x.type == 'VkDeferredOperationKHR'), None) |
| # Update struct prefix |
| if topLevel: |
| new_prefix = f'local_{member.name}' |
| # Declare vku::safe_VkVarType for struct |
| if not deferred_name: |
| decls += f'small_vector<{safe_type}, DISPATCH_MAX_STACK_ALLOCATIONS> var_{new_prefix};\n' |
| decls += f'{safe_type} *{new_prefix} = nullptr;\n' |
| |
| else: |
| new_prefix = f'{prefix}{member.name}' |
| pre_code += f'if ({prefix}{member.name}) {{\n' |
| if topLevel: |
| if deferred_name: |
| pre_code += f'{new_prefix} = new {safe_type}[{member.length}];\n' |
| else: |
| pre_code += f'var_{new_prefix}.resize({member.length});\n' |
| pre_code += f'{new_prefix} = var_{new_prefix}.data();\n' |
| pre_code += f'for (uint32_t {index} = 0; {index} < {prefix}{member.length}; ++{index}) {{\n' |
| if topLevel: |
| if safe_type.startswith('vku::safe'): |
| # Handle special initialize function for VkAccelerationStructureBuildGeometryInfoKHR |
| if member.type == "VkAccelerationStructureBuildGeometryInfoKHR": |
| pre_code += f'{new_prefix}[{index}].initialize(&{member.name}[{index}], false, nullptr);\n' |
| else: |
| pre_code += f'{new_prefix}[{index}].initialize(&{member.name}[{index}]);\n' |
| else: |
| pre_code += f'{new_prefix}[{index}] = {member.name}[{index}];\n' |
| if process_pnext: |
| pre_code += f'UnwrapPnextChainHandles({new_prefix}[{index}].pNext);\n' |
| local_prefix = f'{new_prefix}[{index}].' |
| # Process sub-structs in this struct |
| (tmp_decl, tmp_pre, tmp_post) = self.uniquifyMembers(struct.members, local_prefix, arrayIndex, isCreate, isDestroy, False) |
| decls += tmp_decl |
| pre_code += tmp_pre |
| post_code += tmp_post |
| pre_code += '}\n' |
| pre_code += '}\n' |
| if topLevel: |
| post_code += self.cleanUpLocalDeclarations(prefix, member.name, member.length, deferred_name) |
| # Single Struct |
| elif member.pointer: |
| # Check if this function can be deferred. |
| deferred_name = next((x.name for x in members if x.type == 'VkDeferredOperationKHR'), None) |
| # Update struct prefix |
| if topLevel: |
| new_prefix = f'local_{member.name}->' |
| if deferred_name is None: |
| decls += f'{safe_type} var_local_{prefix}{member.name};\n' |
| decls += f'{safe_type} *local_{prefix}{member.name} = nullptr;\n' |
| else: |
| new_prefix = f'{prefix}{member.name}->' |
| # Declare safe_VarType for struct |
| pre_code += f'if ({prefix}{member.name}) {{\n' |
| if topLevel: |
| if deferred_name is None: |
| pre_code += f'local_{prefix}{member.name} = &var_local_{prefix}{member.name};\n' |
| else: |
| pre_code += f'local_{member.name} = new {safe_type};\n' |
| if safe_type.startswith('vku::safe'): |
| # Handle special initialize function for VkAccelerationStructureBuildGeometryInfoKHR |
| if member.type == "VkAccelerationStructureBuildGeometryInfoKHR": |
| pre_code += f'local_{prefix}{member.name}->initialize({member.name}, false, nullptr);\n' |
| else: |
| pre_code += f'local_{prefix}{member.name}->initialize({member.name});\n' |
| else: |
| pre_code += f'*local_{prefix}{member.name} = *{member.name};\n' |
| # Process sub-structs in this struct |
| (tmp_decl, tmp_pre, tmp_post) = self.uniquifyMembers(struct.members, new_prefix, arrayIndex, isCreate, isDestroy, False) |
| decls += tmp_decl |
| pre_code += tmp_pre |
| post_code += tmp_post |
| if process_pnext: |
| pre_code += f'UnwrapPnextChainHandles({new_prefix}pNext);\n' |
| pre_code += '}\n' |
| if topLevel: |
| post_code += self.cleanUpLocalDeclarations(prefix, member.name, member.length, deferred_name) |
| else: |
| # Update struct prefix |
| if topLevel: |
| sys.exit(1) |
| else: |
| new_prefix = f'{prefix}{member.name}.' |
| # Process sub-structs in this struct |
| (tmp_decl, tmp_pre, tmp_post) = self.uniquifyMembers(struct.members, new_prefix, arrayIndex, isCreate, isDestroy, False) |
| decls += tmp_decl |
| pre_code += tmp_pre |
| post_code += tmp_post |
| if process_pnext: |
| pre_code += f'UnwrapPnextChainHandles({prefix}{member.name}.pNext);\n' |
| elif member.type == 'VkObjectType' and member.name == 'objectType' and any(m.name == 'objectHandle' for m in members): |
| pre_code += ''' |
| if (NotDispatchableHandle(objectType)) { |
| objectHandle = Unwrap(objectHandle); |
| } |
| ''' |
| return decls, pre_code, post_code |