blob: a4c19935c1e31dd02abde4b503542b934fb2e0fb [file] [log] [blame]
#!/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