| #!/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) 2023-2025 RasterGrid Kft. |
| # |
| # 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 os |
| import re |
| from generators.generator_utils import buildListVUID, PlatformGuardHelper |
| from generators.vulkan_object import Member, Struct |
| from generators.base_generator import BaseGenerator |
| |
| # 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: |
| # Generates custom validation for a function parameter or returns None |
| @staticmethod |
| def genCustomValidation(targetApiName: str, funcName: str, member) -> list[str]: |
| match targetApiName: |
| |
| # Vulkan specific custom validation (currently none) |
| case 'vulkan': |
| return None |
| |
| def isDeviceStruct(struct: Struct): |
| for extension in struct.extensions: |
| if not extension.device: |
| return False |
| return True |
| |
| class StatelessValidationHelperOutputGenerator(BaseGenerator): |
| def __init__(self, |
| valid_usage_file): |
| BaseGenerator.__init__(self) |
| self.valid_vuids = buildListVUID(valid_usage_file) |
| |
| # These functions have additional, custom-written checks in the utils cpp file. CodeGen will automatically add a call |
| # to those functions of the form 'bool manual_PreCallValidateAPIName', where the 'vk' is dropped. |
| # see 'manual_PreCallValidateCreateGraphicsPipelines' as an example. |
| self.functionsWithManualChecks = [ |
| 'vkCreateInstance', |
| 'vkCreateDevice', |
| 'vkCreateQueryPool', |
| 'vkCreateRenderPass', |
| 'vkCreateRenderPass2', |
| 'vkCreateBuffer', |
| 'vkCreateImage', |
| 'vkCreateShaderModule', |
| 'vkCreatePipelineLayout', |
| 'vkCreateGraphicsPipelines', |
| 'vkCreateComputePipelines', |
| 'vkCreateRayTracingPipelinesNV', |
| 'vkCreateRayTracingPipelinesKHR', |
| 'vkCreateSampler', |
| 'vkCreateDescriptorSetLayout', |
| 'vkGetDescriptorSetLayoutSupport', |
| 'vkCreateBufferView', |
| 'vkCreateSemaphore', |
| 'vkCreateEvent', |
| 'vkFreeDescriptorSets', |
| 'vkUpdateDescriptorSets', |
| 'vkBeginCommandBuffer', |
| 'vkFreeCommandBuffers', |
| 'vkCmdSetViewport', |
| 'vkCmdSetScissor', |
| 'vkCmdSetLineWidth', |
| 'vkCmdClearAttachments', |
| 'vkCmdBindIndexBuffer', |
| 'vkCmdBindIndexBuffer2', |
| 'vkCmdCopyBuffer', |
| 'vkCmdUpdateBuffer', |
| 'vkCmdFillBuffer', |
| 'vkCreateSwapchainKHR', |
| 'vkCreateSharedSwapchainsKHR', |
| 'vkQueuePresentKHR', |
| 'vkCreateDescriptorPool', |
| 'vkCmdPushDescriptorSet', |
| 'vkCmdPushDescriptorSet2', |
| 'vkGetDescriptorEXT', |
| 'vkCmdSetDescriptorBufferOffsets2EXT', |
| 'vkCmdBindDescriptorBufferEmbeddedSamplers2EXT', |
| 'vkCmdPushDescriptorSetWithTemplate2', |
| 'vkCmdBindDescriptorSets2', |
| 'vkCreateIndirectExecutionSetEXT', |
| 'vkCreateIndirectCommandsLayoutEXT', |
| 'vkCmdPreprocessGeneratedCommandsEXT', |
| 'vkCmdExecuteGeneratedCommandsEXT', |
| 'vkCmdSetExclusiveScissorNV', |
| 'vkCmdSetViewportShadingRatePaletteNV', |
| 'vkCmdSetCoarseSampleOrderNV', |
| 'vkAllocateMemory', |
| 'vkCreateAccelerationStructureNV', |
| 'vkCreateAccelerationStructureKHR', |
| 'vkDestroyAccelerationStructureKHR', |
| 'vkGetAccelerationStructureHandleNV', |
| 'vkGetPhysicalDeviceImageFormatProperties', |
| 'vkGetPhysicalDeviceImageFormatProperties2', |
| 'vkGetPhysicalDeviceProperties2', |
| 'vkCmdBuildAccelerationStructureNV', |
| 'vkCmdTraceRaysKHR', |
| 'vkCmdTraceRaysIndirectKHR', |
| 'vkCmdTraceRaysIndirect2KHR', |
| 'vkCreateFramebuffer', |
| 'vkCmdSetLineStipple', |
| 'vkSetDebugUtilsObjectNameEXT', |
| 'vkSetDebugUtilsObjectTagEXT', |
| 'vkCmdSetViewportWScalingNV', |
| 'vkCmdSetDepthClampRangeEXT', |
| 'vkAcquireNextImageKHR', |
| 'vkAcquireNextImage2KHR', |
| 'vkCmdBindTransformFeedbackBuffersEXT', |
| 'vkCmdBeginTransformFeedbackEXT', |
| 'vkCmdEndTransformFeedbackEXT', |
| 'vkCreateSamplerYcbcrConversion', |
| 'vkGetMemoryFdKHR', |
| 'vkImportSemaphoreFdKHR', |
| 'vkGetSemaphoreFdKHR', |
| 'vkImportFenceFdKHR', |
| 'vkGetFenceFdKHR', |
| 'vkGetMemoryWin32HandleKHR', |
| 'vkImportFenceWin32HandleKHR', |
| 'vkGetFenceWin32HandleKHR', |
| 'vkImportSemaphoreWin32HandleKHR', |
| 'vkGetSemaphoreWin32HandleKHR', |
| 'vkGetMemoryHostPointerPropertiesEXT', |
| 'vkCmdBindVertexBuffers', |
| 'vkCreateImageView', |
| 'vkCopyAccelerationStructureToMemoryKHR', |
| 'vkCmdCopyAccelerationStructureToMemoryKHR', |
| 'vkCopyAccelerationStructureKHR', |
| 'vkCmdCopyAccelerationStructureKHR', |
| 'vkCopyMemoryToAccelerationStructureKHR', |
| 'vkCmdCopyMemoryToAccelerationStructureKHR', |
| 'vkCmdWriteAccelerationStructuresPropertiesKHR', |
| 'vkWriteAccelerationStructuresPropertiesKHR', |
| 'vkGetRayTracingCaptureReplayShaderGroupHandlesKHR', |
| 'vkCmdBuildAccelerationStructureIndirectKHR', |
| 'vkGetDeviceAccelerationStructureCompatibilityKHR', |
| 'vkCmdSetViewportWithCount', |
| 'vkCmdSetScissorWithCount', |
| 'vkCmdBindVertexBuffers2', |
| 'vkCmdCopyBuffer2', |
| 'vkCmdBuildAccelerationStructuresKHR', |
| 'vkCmdBuildAccelerationStructuresIndirectKHR', |
| 'vkBuildAccelerationStructuresKHR', |
| 'vkGetAccelerationStructureBuildSizesKHR', |
| 'vkCmdWriteAccelerationStructuresPropertiesNV', |
| 'vkCreateDisplayModeKHR', |
| 'vkCmdSetVertexInputEXT', |
| 'vkCmdPushConstants', |
| 'vkCmdPushConstants2', |
| 'vkCreatePipelineCache', |
| 'vkMergePipelineCaches', |
| 'vkCmdClearColorImage', |
| 'vkCmdBeginRenderPass', |
| 'vkCmdBeginRenderPass2', |
| 'vkCmdBeginRendering', |
| 'vkCmdSetDiscardRectangleEXT', |
| 'vkGetQueryPoolResults', |
| 'vkCmdBeginConditionalRenderingEXT', |
| 'vkGetDeviceImageMemoryRequirements', |
| 'vkGetDeviceImageSparseMemoryRequirements', |
| 'vkCreateAndroidSurfaceKHR', |
| 'vkCreateWin32SurfaceKHR', |
| 'vkCreateWaylandSurfaceKHR', |
| 'vkCreateXcbSurfaceKHR', |
| 'vkCreateXlibSurfaceKHR', |
| 'vkGetPhysicalDeviceSurfaceFormatsKHR', |
| 'vkGetPhysicalDeviceSurfacePresentModesKHR', |
| 'vkGetPhysicalDeviceSurfaceCapabilities2KHR', |
| 'vkGetPhysicalDeviceSurfaceFormats2KHR', |
| 'vkGetPhysicalDeviceSurfacePresentModes2EXT', |
| 'vkCmdSetDiscardRectangleEnableEXT', |
| 'vkCmdSetDiscardRectangleModeEXT', |
| 'vkCmdSetExclusiveScissorEnableNV', |
| 'vkGetMemoryWin32HandlePropertiesKHR', |
| 'vkGetMemoryFdPropertiesKHR', |
| 'vkCreateShadersEXT', |
| 'vkGetShaderBinaryDataEXT', |
| 'vkSetDeviceMemoryPriorityEXT', |
| 'vkGetDeviceImageSubresourceLayout', |
| 'vkQueueBindSparse', |
| 'vkCmdBindDescriptorBuffersEXT', |
| 'vkGetPhysicalDeviceExternalBufferProperties', |
| 'vkGetPipelinePropertiesEXT', |
| 'vkBuildMicromapsEXT', |
| 'vkCmdBuildMicromapsEXT', |
| 'vkCmdCopyMemoryToMicromapEXT', |
| 'vkCmdCopyMicromapEXT', |
| 'vkCmdCopyMicromapToMemoryEXT', |
| 'vkCmdWriteMicromapsPropertiesEXT', |
| 'vkCopyMemoryToMicromapEXT', |
| 'vkCopyMicromapEXT', |
| 'vkCopyMicromapToMemoryEXT', |
| 'vkCreateMicromapEXT', |
| 'vkDestroyMicromapEXT', |
| 'vkGetDeviceMicromapCompatibilityEXT', |
| 'vkGetMicromapBuildSizesEXT', |
| 'vkWriteMicromapsPropertiesEXT', |
| ] |
| |
| # Commands to ignore |
| self.blacklist = [ |
| 'vkGetInstanceProcAddr', |
| 'vkGetDeviceProcAddr', |
| 'vkEnumerateInstanceVersion', |
| 'vkEnumerateInstanceLayerProperties', |
| 'vkEnumerateInstanceExtensionProperties', |
| 'vkEnumerateDeviceLayerProperties', |
| 'vkEnumerateDeviceExtensionProperties', |
| 'vkGetDeviceGroupSurfacePresentModes2EXT' |
| ] |
| |
| # Very rare case when structs are needed prior to setting up everything |
| self.structsWithManualChecks = [ |
| 'VkLayerSettingsCreateInfoEXT' |
| ] |
| |
| # Validation conditions for some special case struct members that are conditionally validated |
| self.structMemberValidationConditions = [ |
| { |
| 'struct' : 'VkSubpassDependency2', |
| 'field' : 'VkPipelineStageFlagBits', |
| 'condition' : '!vku::FindStructInPNextChain<VkMemoryBarrier2>(pCreateInfo->pDependencies[dependencyIndex].pNext)' |
| }, |
| { |
| 'struct' : 'VkSubpassDependency2', |
| 'field' : 'VkAccessFlagBits', |
| 'condition' : '!vku::FindStructInPNextChain<VkMemoryBarrier2>(pCreateInfo->pDependencies[dependencyIndex].pNext)' |
| } |
| ] |
| |
| # Will create a validate function for the struct to be called by non-generated code. |
| # There are cases where `noautovalidity` and `optional` are both true, but really are just |
| # guarded by other conditions. |
| # |
| # Example: pViewportState should always be validated, when not ignored. The logic of when it |
| # isn't ignored gets complex and best done by hand in sl_pipeline.cpp |
| self.generateStructHelper = [ |
| # Graphic Pipeline states that can be ignored |
| 'VkPipelineViewportStateCreateInfo', |
| 'VkPipelineTessellationStateCreateInfo', |
| 'VkPipelineVertexInputStateCreateInfo', |
| 'VkPipelineMultisampleStateCreateInfo', |
| 'VkPipelineColorBlendStateCreateInfo', |
| 'VkPipelineDepthStencilStateCreateInfo', |
| 'VkPipelineInputAssemblyStateCreateInfo', |
| 'VkPipelineRasterizationStateCreateInfo', |
| 'VkPipelineShaderStageCreateInfo', |
| |
| # Ignored if not secondary command buffer |
| 'VkCommandBufferInheritanceInfo', |
| |
| # Uses an enum to decide which struct in a union to generate |
| 'VkDescriptorAddressInfoEXT', |
| 'VkAccelerationStructureGeometryTrianglesDataKHR', |
| 'VkAccelerationStructureGeometryInstancesDataKHR', |
| 'VkAccelerationStructureGeometryAabbsDataKHR', |
| 'VkIndirectExecutionSetPipelineInfoEXT', # VkIndirectExecutionSetShaderInfoEXT is done manually |
| ] |
| |
| # Map of structs type names to generated validation code for that struct type |
| self.validatedStructs = dict() |
| # Map of flags typenames |
| self.flags = set() |
| # Map of flag bits typename to list of values |
| self.flagBits = dict() |
| |
| self.stype_version_dict = dict() |
| |
| 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 == 'stateless_validation_helper.h': |
| self.generateHeader() |
| elif self.filename == 'stateless_validation_helper.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 generateHeader(self): |
| out = [] |
| out.append('#pragma once\n') |
| |
| out.append('\nstatic inline bool IsDuplicatePnext(VkStructureType input_value) {\n') |
| out.append(' switch (input_value) {\n') |
| for struct in [x for x in self.vk.structs.values() if x.allowDuplicate and x.sType is not None]: |
| # The sType will always be first member of struct |
| out.append(f' case {struct.sType}:\n') |
| out.append(' return true;\n') |
| out.append(' default:\n') |
| out.append(' return false;\n') |
| out.append(' }\n') |
| out.append('}\n') |
| out.append('\n') |
| |
| guard_helper = PlatformGuardHelper() |
| for command in [x for x in self.vk.commands.values() if x.name not in self.blacklist]: |
| out.extend(guard_helper.add_guard(command.protect)) |
| prototype = command.cPrototype.split('VKAPI_CALL ')[1] |
| prototype = f'bool PreCallValidate{prototype[2:]}' |
| prototype = prototype.replace(');', ') const override;\n') |
| if 'ValidationCache' in command.name: |
| prototype = prototype.replace('const override', 'const') |
| prototype = prototype.replace(')', ',\n const ErrorObject& error_obj)') |
| out.append(prototype) |
| out.extend(guard_helper.add_guard(None)) |
| |
| for struct_name in self.generateStructHelper: |
| out.append(f'bool Validate{struct_name[2:]}(const {struct_name} &info, const Location &loc) const;') |
| |
| self.write("".join(out)) |
| |
| def generateSource(self): |
| # Structure fields to ignore |
| structMemberBlacklist = { |
| 'VkWriteDescriptorSet' : ['dstSet'], |
| 'VkAccelerationStructureGeometryKHR' :['geometry'], |
| 'VkDescriptorDataEXT' :['pSampler'] |
| } |
| for struct in [x for x in self.vk.structs.values() if x.name in structMemberBlacklist]: |
| for member in [x for x in struct.members if x.name in structMemberBlacklist[struct.name]]: |
| member.noAutoValidity = True |
| |
| # TODO - We should not need this with VulkanObject, but the following are casuing issues |
| # being "promoted" |
| # VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT |
| # VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_2_PLANE_444_FORMATS_FEATURES_EXT |
| # VK_STRUCTURE_TYPE_QUEUE_FAMILY_CHECKPOINT_PROPERTIES_2_NV |
| # VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT |
| # VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT |
| # VK_STRUCTURE_TYPE_CHECKPOINT_DATA_2_NV |
| # VK_STRUCTURE_TYPE_MULTIVIEW_PER_VIEW_ATTRIBUTES_INFO_NVX |
| # VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR |
| # VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_4444_FORMATS_FEATURES_EXT |
| # VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_DENSITY_MAP_ATTACHMENT_INFO_EXT |
| root = self.registry.reg |
| |
| extToPromotedExtDict = dict() |
| for extensions in root.findall('extensions'): |
| for extension in extensions.findall('extension'): |
| extension_name = extension.get('name') |
| if extension_name not in extToPromotedExtDict.keys(): |
| extToPromotedExtDict[extension_name] = set() |
| promotedTo = extension.get('promotedto') |
| if promotedTo is not None: |
| extToPromotedExtDict[extension_name] = promotedTo |
| else: |
| extToPromotedExtDict[extension_name] = None |
| |
| for extensions in root.findall('extensions'): |
| for extension in extensions.findall('extension'): |
| extension_name = extension.get('name') |
| promoted_ext = extToPromotedExtDict[extension_name] |
| while promoted_ext is not None and not 'VK_VERSION' in promoted_ext: |
| promoted_ext = extToPromotedExtDict[promoted_ext] |
| # TODO Issue 5103 - this is being used to remove false positive currently |
| promoted_to_core = promoted_ext is not None and 'VK_VERSION' in promoted_ext |
| |
| for entry in extension.iterfind('require/enum[@extends="VkStructureType"]'): |
| if (entry.get('comment') is None or 'typo' not in entry.get('comment')): |
| alias = entry.get('alias') |
| if (alias is not None and promoted_to_core): |
| if (alias not in self.stype_version_dict.keys()): |
| self.stype_version_dict[alias] = set() |
| self.stype_version_dict[alias].add(extension_name) |
| |
| # Generate the struct member checking code from the captured data |
| for struct in self.vk.structs.values(): |
| # The string returned will be nested in an if check for a NULL pointer, so needs its indent incremented |
| lines = self.genFuncBody(self.vk.structs[struct.name].members, '{funcName}', '{errorLoc}', '{valuePrefix}', '{displayNamePrefix}', struct.name, False) |
| if lines: |
| self.validatedStructs[struct.name] = lines |
| |
| out = [] |
| out.append(''' |
| #include "stateless/stateless_validation.h" |
| #include "generated/enum_flag_bits.h" |
| #include "generated/dispatch_functions.h" |
| ''') |
| |
| # The reason we split this up into Feature and Properties struct is before be had a 450 case, 10k line function that broke MSVC |
| # reference: https://www.asawicki.info/news_1617_how_code_refactoring_can_fix_stack_overflow_error |
| extended_structs = [x for x in self.vk.structs.values() if x.extends] |
| feature_structs = [x for x in extended_structs if x.extends == ["VkPhysicalDeviceFeatures2", "VkDeviceCreateInfo"]] |
| property_structs = [x for x in extended_structs if x.extends == ["VkPhysicalDeviceProperties2"]] |
| other_structs = [x for x in extended_structs if x not in feature_structs and x not in property_structs and x.name not in self.structsWithManualChecks] |
| |
| out.append(''' |
| bool StatelessValidation::ValidatePnextFeatureStructContents(const Location& loc, |
| const VkBaseOutStructure* header, const char *pnext_vuid, |
| const VkPhysicalDevice physicalDevice, bool is_const_param) const { |
| bool skip = false; |
| const bool is_physdev_api = physicalDevice != VK_NULL_HANDLE; |
| switch(header->sType) { |
| ''') |
| guard_helper = PlatformGuardHelper() |
| for struct in feature_structs: |
| out.extend(guard_helper.add_guard(struct.protect)) |
| out.extend(self.genStructBody(struct, False)) |
| out.extend(guard_helper.add_guard(None)) |
| out.append(''' |
| default: |
| skip = false; |
| } |
| return skip; |
| } |
| |
| ''') |
| |
| out.append(''' |
| bool StatelessValidation::ValidatePnextPropertyStructContents(const Location& loc, |
| const VkBaseOutStructure* header, const char *pnext_vuid, |
| const VkPhysicalDevice physicalDevice, bool is_const_param) const { |
| bool skip = false; |
| const bool is_physdev_api = physicalDevice != VK_NULL_HANDLE; |
| switch(header->sType) { |
| ''') |
| guard_helper = PlatformGuardHelper() |
| for struct in property_structs: |
| out.extend(guard_helper.add_guard(struct.protect)) |
| out.extend(self.genStructBody(struct, False)) |
| out.extend(guard_helper.add_guard(None)) |
| out.append(''' |
| default: |
| skip = false; |
| } |
| return skip; |
| } |
| |
| ''') |
| |
| out.append(''' |
| // All structs that are not a Feature or Property struct |
| bool StatelessValidation::ValidatePnextStructContents(const Location& loc, |
| const VkBaseOutStructure* header, const char *pnext_vuid, |
| const VkPhysicalDevice physicalDevice, bool is_const_param) const { |
| bool skip = false; |
| const bool is_physdev_api = physicalDevice != VK_NULL_HANDLE; |
| switch(header->sType) { |
| ''') |
| guard_helper = PlatformGuardHelper() |
| for struct in other_structs: |
| out.extend(guard_helper.add_guard(struct.protect)) |
| out.extend(self.genStructBody(struct, True)) |
| out.extend(guard_helper.add_guard(None)) |
| out.append(''' |
| default: |
| skip = false; |
| } |
| return skip; |
| } |
| |
| ''') |
| |
| # Some extensions are alias from EXT->KHR but are not promoted, example |
| # vkGetImageSubresourceLayout2EXT (VK_EXT_host_image_copy) |
| # vkGetImageSubresourceLayout2KHR (VK_KHR_maintenance5) |
| alias_but_not_core = [] |
| for command in [x for x in self.vk.commands.values() if x.name not in self.blacklist and x.alias and x.alias in self.vk.commands]: |
| aliasCommand = self.vk.commands[command.alias] |
| if aliasCommand.version is None: |
| alias_but_not_core.append(aliasCommand.name) |
| |
| # Generate the command parameter checking code from the captured data |
| for command in [x for x in self.vk.commands.values() if x.name not in self.blacklist]: |
| out.extend(guard_helper.add_guard(command.protect, extra_newline=True)) |
| |
| prototype = (command.cPrototype.split('VKAPI_CALL ')[1])[2:-1] |
| prototype = prototype.replace(')', ', const ErrorObject& error_obj)') |
| out.append(f'bool StatelessValidation::PreCallValidate{prototype} const {{\n') |
| out.append(' bool skip = false;\n') |
| |
| # Create a copy here to make the logic simpler passing into ValidatePnextStructContents |
| out.append(' [[maybe_unused]] const Location loc = error_obj.location;\n') |
| |
| # Cannot validate extension dependencies for device extension APIs having a physical device as their dispatchable object |
| if command.extensions and (not any(x.device for x in command.extensions) or command.params[0].type != 'VkPhysicalDevice'): |
| cExpression = [] |
| outExpression = [] |
| for extension in command.extensions: |
| outExpression.append(f'vvl::Extension::_{extension.name}') |
| if extension.instance: |
| cExpression.append(f'IsExtEnabled(instance_extensions.{extension.name.lower()})') |
| else: |
| cExpression.append(f'IsExtEnabled(device_extensions.{extension.name.lower()})') |
| |
| cExpression = " || ".join(cExpression) |
| if len(outExpression) > 1: |
| cExpression = f'({cExpression})' |
| |
| if command.name in alias_but_not_core: |
| cExpression += f' && loc.function == vvl::Func::{command.name}' |
| out.append(f'if (!{cExpression}) skip |= OutputExtensionError(loc, {{{", ".join(outExpression)}}});\n') |
| |
| if command.alias and command.alias in self.vk.commands: |
| # For alias that are promoted, just point to new function, ErrorObject will allow us to distinguish the caller |
| # Note that we can only do this if the promoted version is part of the target API |
| paramList = [param.name for param in command.params] |
| paramList.append('error_obj') |
| params = ', '.join(paramList) |
| out.append(f'skip |= PreCallValidate{command.alias[2:]}({params});') |
| else: |
| # Skip first parameter if it is a dispatch handle (everything except vkCreateInstance) |
| startIndex = 0 if command.name == 'vkCreateInstance' else 1 |
| lines = self.genFuncBody(command.params[startIndex:], command.name, 'loc', '', '', None, isPhysDevice = command.params[0].type == 'VkPhysicalDevice') |
| |
| if command.instance and command.version: |
| # check function name so KHR version doesn't trigger flase positive |
| out.append(f'if (loc.function == vvl::Func::{command.name} && CheckPromotedApiAgainstVulkanVersion({command.params[0].name}, loc, {command.version.nameApi})) return true;\n') |
| |
| for line in lines: |
| if isinstance(line, list): |
| for sub in line: |
| out.append(sub) |
| else: |
| out.append(line) |
| # Insert call to custom-written function if present |
| if command.name in self.functionsWithManualChecks: |
| manualCheckCmd = command.name |
| # We also have to consider aliases here as the promoted version may not be part of the target API |
| elif command.alias in self.functionsWithManualChecks: |
| manualCheckCmd = command.alias |
| else: |
| manualCheckCmd = None |
| if manualCheckCmd: |
| # Generate parameter list for manual fcn and down-chain calls |
| params_text = ', '.join([x.name for x in command.params]) + ', error_obj' |
| out.append(f' if (!skip) skip |= manual_PreCallValidate{manualCheckCmd[2:]}({params_text});\n') |
| out.append('return skip;\n') |
| out.append('}\n') |
| out.extend(guard_helper.add_guard(None, extra_newline=True)) |
| |
| for struct_name in self.generateStructHelper: |
| out.append(f'bool StatelessValidation::Validate{struct_name[2:]}(const {struct_name} &info, const Location &loc) const {{\n') |
| out.append(' bool skip = false;\n') |
| # Only generate validation code if the structure actually exists in the target API |
| if struct_name in self.vk.structs: |
| out.extend(self.expandStructCode(struct_name, struct_name, 'loc', 'info.', '', [])) |
| out.append(' return skip;\n') |
| out.append('}\n') |
| |
| self.write("".join(out)) |
| |
| def genType(self, typeinfo, name, alias): |
| BaseGenerator.genType(self, typeinfo, name, alias) |
| if (typeinfo.elem.get('category') == 'bitmask' and not alias): |
| self.flags.add(name) |
| |
| def genGroup(self, groupinfo, groupName, alias): |
| BaseGenerator.genGroup(self, groupinfo, groupName, alias) |
| if 'FlagBits' in groupName and groupName != 'VkStructureType': |
| bits = [] |
| for elem in groupinfo.elem.findall('enum'): |
| if elem.get('supported') != 'disabled' and elem.get('alias') is None: |
| bits.append(elem.get('name')) |
| if bits: |
| self.flagBits[groupName] = bits |
| |
| def isHandleOptional(self, member: Member, lengthMember: Member) -> bool : |
| # Simple, if it's optional, return true |
| if member.optional or member.optionalPointer: |
| return True |
| # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes. |
| if member.noAutoValidity: |
| return True |
| # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional |
| if lengthMember and lengthMember.optional: |
| return True |
| return |
| |
| # Get VUID identifier from implicit VUID tag |
| def GetVuid(self, name, suffix): |
| vuid_string = f'VUID-{name}-{suffix}' |
| vuid = "kVUIDUndefined" |
| if '->' in vuid_string: |
| return vuid |
| if vuid_string in self.valid_vuids: |
| vuid = f'"{vuid_string}"' |
| elif name in self.vk.commands: |
| # Only commands have alias to worry about |
| alias_string = f'VUID-{self.vk.commands[name].alias}-{suffix}' |
| if alias_string in self.valid_vuids: |
| vuid = f'"{alias_string}"' |
| return vuid |
| |
| # Generate the pointer check string |
| def makePointerCheck(self, valuePrefix, member: Member, lengthMember: Member, errorLoc, arrayRequired, counValueRequired, counPtrRequired, funcName, structTypeName): |
| checkExpr = [] |
| callerName = structTypeName if structTypeName else funcName |
| if lengthMember: |
| length_deref = '->' in member.length |
| countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength") |
| arrayRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter") |
| countPtrRequiredVuid = self.GetVuid(callerName, f"{member.length}-parameter") |
| # This is an array with a pointer to a count value |
| if lengthMember.pointer and not length_deref: |
| # If count and array parameters are optional, there will be no validation |
| if arrayRequired == 'true' or counPtrRequired == 'true' or counValueRequired == 'true': |
| # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required |
| checkExpr.append(f'skip |= ValidatePointerArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, &{valuePrefix}{member.name}, {counPtrRequired}, {counValueRequired}, {arrayRequired},{countPtrRequiredVuid}, {countRequiredVuid}, {arrayRequiredVuid});\n') |
| # This is an array with an integer count value |
| else: |
| # Can't check if a non-null pointer is a valid pointer in a layer |
| unimplementable = member.optional and (lengthMember.optional or lengthMember.optionalPointer) |
| # If count and array parameters are optional, there will be no validation |
| if (arrayRequired == 'true' or counValueRequired == 'true') and not unimplementable: |
| if member.type == 'char': |
| # Arrays of strings receive special processing |
| checkExpr.append(f'skip |= ValidateStringArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n') |
| else: |
| # A valid VU can't use '->' in the middle so the generated VUID from the spec uses '::' instead |
| countRequiredVuid = self.GetVuid(callerName, f"{member.length.replace('->', '::')}-arraylength") |
| if structTypeName == 'VkShaderModuleCreateInfo' and member.name == 'pCode': |
| countRequiredVuid = '"VUID-VkShaderModuleCreateInfo-codeSize-01085"' # exception due to unique lenValue |
| |
| # TODO - some length have unhandled symbols |
| count_loc = f'{errorLoc}.dot(Field::{member.length})' |
| if '->' in member.length: |
| count_loc = f'{errorLoc}.dot(Field::{member.length.split("->")[0]}).dot(Field::{member.length.split("->")[1]})' |
| elif ' + ' in member.length: |
| # hardcoded only instance for now |
| if 'samples' in member.length: # "(samples + 31) / 32" |
| count_loc = f'{errorLoc}.dot(Field::samples)' |
| elif 'rasterizationSamples' in member.length: # "(rasterizationSamples + 31) / 32" |
| count_loc = f'{errorLoc}.dot(Field::rasterizationSamples)' |
| member.length = 'rasterizationSamples' |
| elif ' / ' in member.length: |
| count_loc = f'{errorLoc}.dot(Field::{member.length.split(" / ")[0]})' |
| checkExpr.append(f'skip |= ValidateArray({count_loc}, {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, &{valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n') |
| if checkExpr and lengthMember and length_deref and member.length.count('->'): |
| # Add checks to ensure the validation call does not dereference a NULL pointer to obtain the count |
| count = member.length.count('->') |
| checkedExpr = [] |
| elements = member.length.split('->') |
| # Open the if expression blocks |
| for i in range(0, count): |
| checkedExpr.append(f'if ({"->".join(elements[0:i+1])} != nullptr) {{\n') |
| # Add the validation expression |
| for expr in checkExpr: |
| checkedExpr.append(expr) |
| # Close the if blocks |
| for i in range(0, count): |
| checkedExpr.append('}\n') |
| checkExpr = [checkedExpr] |
| # This is an individual struct that is not allowed to be NULL |
| elif not (member.optional or member.fixedSizeArray): |
| # Function pointers need a reinterpret_cast to void* |
| ptrRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter") |
| if member.type.startswith('PFN_'): |
| checkExpr.append(f'skip |= ValidateRequiredPointer({errorLoc}.dot(Field::{member.name}), reinterpret_cast<const void*>({valuePrefix}{member.name}), {ptrRequiredVuid});\n') |
| else: |
| checkExpr.append(f'skip |= ValidateRequiredPointer({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name}, {ptrRequiredVuid});\n') |
| return checkExpr |
| |
| # Process struct member validation code, performing name substitution if required |
| def processStructMemberCode(self, line, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix): |
| # Build format specifier list |
| kwargs = {} |
| if '{funcName}' in line: |
| kwargs['funcName'] = funcName |
| if '{errorLoc}' in line: |
| kwargs['errorLoc'] = errorLoc |
| if '{valuePrefix}' in line: |
| kwargs['valuePrefix'] = memberNamePrefix |
| if '{displayNamePrefix}' in line: |
| # Check for a tuple that includes a format string and format parameters to be used with the ParameterName class |
| if type(memberDisplayNamePrefix) is tuple: |
| kwargs['displayNamePrefix'] = memberDisplayNamePrefix[0] |
| else: |
| kwargs['displayNamePrefix'] = memberDisplayNamePrefix |
| |
| if kwargs: |
| # Need to escape the C++ curly braces |
| return line.format(**kwargs) |
| return line |
| |
| # Process struct member validation code, stripping metadata |
| def ScrubStructCode(self, code): |
| scrubbed_lines = '' |
| for line in code: |
| if 'xml-driven validation' in line: |
| continue |
| line = line.replace('{funcName}', '') |
| line = line.replace('{errorLoc}', '') |
| line = line.replace('{valuePrefix}', '') |
| line = line.replace('{displayNamePrefix}', '') |
| scrubbed_lines += line |
| return scrubbed_lines |
| |
| # Process struct validation code for inclusion in function or parent struct validation code |
| def expandStructCode(self, item_type, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix, output): |
| lines = self.validatedStructs[item_type] |
| for line in lines: |
| if output: |
| output[-1] += '\n' |
| if isinstance(line, list): |
| for sub in line: |
| output.append(self.processStructMemberCode(sub, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix)) |
| else: |
| output.append(self.processStructMemberCode(line, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix)) |
| return output |
| |
| # Generate the parameter checking code |
| def genFuncBody(self, members: list[Member], funcName, errorLoc, valuePrefix, displayNamePrefix, structTypeName, isPhysDevice): |
| struct = self.vk.structs[structTypeName] if structTypeName in self.vk.structs else None |
| callerName = structTypeName if structTypeName else funcName |
| lines = [] # Generated lines of code |
| duplicateCountVuid = [] # prevent duplicate VUs being generated |
| |
| # TODO Using a regex in this context is not ideal. Would be nicer if usedLines were a list of objects with "settings" (such as "isPhysDevice") |
| validatePNextRegex = re.compile(r'(.*ValidateStructPnext\(.*)(\).*\n*)', re.M) |
| |
| # Special struct since lots of functions have this, but it can be all combined to the same call (since it is always from the top level of a funciton) |
| if structTypeName == 'VkAllocationCallbacks' : |
| lines.append('skip |= ValidateAllocationCallbacks(*pAllocator, pAllocator_loc);') |
| return lines |
| |
| # Returnedonly structs should have most of their members ignored -- on entry, we only care about validating the sType and |
| # pNext members. Everything else will be overwritten by the callee. |
| for member in [x for x in members if not struct or not struct.returnedOnly or (x.name in ('sType', 'pNext'))]: |
| usedLines = [] |
| lengthMember = None |
| condition = None |
| # |
| # Generate the full name of the value, which will be printed in the error message, by adding the variable prefix to the value name |
| valueDisplayName = f'{displayNamePrefix}{member.name}' |
| # |
| # Check for NULL pointers, ignore the in-out count parameters that |
| # will be validated with their associated array |
| if (member.pointer or member.fixedSizeArray) and not [x for x in members if x.length and member.name == x.length]: |
| # Parameters for function argument generation |
| arrayRequired = 'true' # Parameter cannot be NULL |
| counPtrRequired = 'true' # Count pointer cannot be NULL |
| counValueRequired = 'true' # Count value cannot be 0 |
| countRequiredVuid = None # If there is a count required VUID to check |
| # Generate required/optional parameter strings for the pointer and count values |
| if member.optional or member.optionalPointer: |
| arrayRequired = 'false' |
| if member.length: |
| # The parameter is an array with an explicit count parameter |
| # Find a named parameter in a parameter list |
| lengthMember = next((x for x in members if x.name == member.length), None) |
| |
| # First check if any element of params matches length exactly |
| if not lengthMember: |
| # Otherwise, look for any elements of params that appear within length |
| candidates = [p for p in members if re.search(r'\b{}\b'.format(p.name), member.length)] |
| # 0 or 1 matches are expected, >1 would require a special case and/or explicit validation |
| if len(candidates) == 0: |
| lengthMember = None |
| elif len(candidates) == 1: |
| lengthMember = candidates[0] |
| |
| if lengthMember: |
| if lengthMember.pointer: |
| counPtrRequired = 'false' if lengthMember.optional else counPtrRequired |
| counValueRequired = 'false' if lengthMember.optionalPointer else counValueRequired |
| # In case of count as field in another struct, look up field to see if count is optional. |
| len_deref = member.length.split('->') |
| if len(len_deref) == 2: |
| lenMembers = next((x.members for x in self.vk.structs.values() if x.name == lengthMember.type), None) |
| if lenMembers and next((x for x in lenMembers if x.name == len_deref[1] and x.optional), None): |
| counValueRequired = 'false' |
| else: |
| vuidName = self.GetVuid(callerName, f"{lengthMember.name}-arraylength") |
| # This VUID is considered special, as it is the only one whose names ends in "-arraylength" but has special conditions allowing bindingCount to be 0. |
| arrayVuidExceptions = ['"VUID-vkCmdBindVertexBuffers2-bindingCount-arraylength"'] |
| if vuidName in arrayVuidExceptions: |
| continue |
| if lengthMember.optional: |
| counValueRequired = 'false' |
| elif member.noAutoValidity: |
| # Handle edge case where XML expresses a non-optional non-pointer value length with noautovalidity |
| # ex: <param noautovalidity="true"len="commandBufferCount"> |
| countRequiredVuid = self.GetVuid(callerName, f"{lengthMember.name}-arraylength") |
| if countRequiredVuid in duplicateCountVuid: |
| countRequiredVuid = None |
| else: |
| duplicateCountVuid.append(countRequiredVuid) |
| else: |
| # Do not generate length checks for constant sized arrays |
| counPtrRequired = 'false' |
| counValueRequired = 'false' |
| |
| # |
| # The parameter will not be processed when tagged as 'noautovalidity' |
| # For the pointer to struct case, the struct pointer will not be validated, but any |
| # members not tagged as 'noautovalidity' will be validated |
| # We special-case the custom allocator checks, as they are explicit but can be auto-generated. |
| AllocatorFunctions = ['PFN_vkAllocationFunction', 'PFN_vkReallocationFunction', 'PFN_vkFreeFunction', 'PFN_vkInternalAllocationNotification', 'PFN_vkInternalFreeNotification'] |
| apiSpecificCustomValidation = APISpecific.genCustomValidation(self.targetApiName, funcName, member) |
| if apiSpecificCustomValidation is not None: |
| usedLines.extend(apiSpecificCustomValidation) |
| elif member.noAutoValidity and member.type not in AllocatorFunctions and not countRequiredVuid: |
| # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually |
| self.logMsg('diag', f'ParameterValidation: No validation for {callerName} {member.name}') |
| elif countRequiredVuid: |
| usedLines.append(f'skip |= ValidateArray({errorLoc}.dot(Field::{member.length}), loc, {valuePrefix}{member.length}, &{valuePrefix}{member.name}, true, false, {countRequiredVuid}, kVUIDUndefined);\n') |
| else: |
| if member.type in self.vk.structs and self.vk.structs[member.type].sType: |
| # If this is a pointer to a struct with an sType field, verify the type |
| struct = self.vk.structs[member.type] |
| sTypeVuid = self.GetVuid(member.type, "sType-sType") |
| paramVuid = self.GetVuid(callerName, f"{member.name}-parameter") |
| if lengthMember: |
| countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength") |
| # There is no way to test checking a non-null pointer to be valid, so don't falsly print the VUID out |
| if paramVuid != 'kVUIDUndefined' and arrayRequired == 'false': |
| paramVuid = 'kVUIDUndefined' |
| # This is an array of struct pointers |
| if member.cDeclaration.count('*') == 2: |
| usedLines.append(f'skip |= ValidateStructPointerTypeArray({errorLoc}.dot(Field::{lengthMember.name}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{lengthMember.name}, {valuePrefix}{member.name}, {struct.sType}, {counValueRequired}, {arrayRequired}, {sTypeVuid}, {paramVuid}, {countRequiredVuid});\n') |
| # This is an array with a pointer to a count value |
| elif lengthMember.pointer: |
| # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required |
| countPtrRequiredVuid = self.GetVuid(callerName, f"{member.length}-parameter") |
| usedLines.append(f'skip |= ValidateStructTypeArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {struct.sType}, {counPtrRequired}, {counValueRequired}, {arrayRequired}, {sTypeVuid}, {paramVuid}, {countPtrRequiredVuid}, {countRequiredVuid});\n') |
| # This is an array with an integer count value |
| else: |
| usedLines.append(f'skip |= ValidateStructTypeArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {struct.sType}, {counValueRequired}, {arrayRequired}, {sTypeVuid}, {paramVuid}, {countRequiredVuid});\n') |
| # This is an individual struct |
| else: |
| usedLines.append(f'skip |= ValidateStructType({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name}, {struct.sType}, {arrayRequired}, {paramVuid}, {sTypeVuid});\n') |
| # If this is an input handle array that is not allowed to contain NULL handles, verify that none of the handles are VK_NULL_HANDLE |
| elif member.type in self.vk.handles and member.const and not self.isHandleOptional(member, lengthMember): |
| if not lengthMember: |
| # This is assumed to be an output handle pointer |
| raise Exception('Unsupported parameter validation case: Output handles are not NULL checked') |
| elif lengthMember.pointer: |
| # This is assumed to be an output array with a pointer to a count value |
| raise Exception('Unsupported parameter validation case: Output handle array elements are not NULL checked') |
| countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength") |
| # This is an array with an integer count value |
| usedLines.append(f'skip |= ValidateHandleArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid});\n') |
| elif member.type in self.flags and member.const: |
| # Generate check string for an array of VkFlags values |
| flagBitsName = member.type.replace('Flags', 'FlagBits') |
| if flagBitsName not in self.vk.bitmasks: |
| raise Exception('Unsupported parameter validation case: array of reserved VkFlags') |
| allFlags = 'All' + flagBitsName |
| countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength") |
| arrayRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter") |
| usedLines.append(f'skip |= ValidateFlagsArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), vvl::FlagBitmask::{flagBitsName}, {allFlags}, {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n') |
| elif member.type == 'VkBool32' and member.const: |
| countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength") |
| arrayRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter") |
| usedLines.append(f'skip |= ValidateBool32Array({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n') |
| elif member.type in self.vk.enums and member.const: |
| lenLoc = 'loc' if member.fixedSizeArray else f'{errorLoc}.dot(Field::{member.length})' |
| countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength") |
| arrayRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter") |
| usedLines.append(f'skip |= ValidateRangedEnumArray({lenLoc}, {errorLoc}.dot(Field::{member.name}), vvl::Enum::{member.type}, {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n') |
| elif member.name == 'pNext': |
| # Generate an array of acceptable VkStructureType values for pNext |
| allowedTypeCount = 0 |
| allowedTypes = 'nullptr' |
| pNextVuid = self.GetVuid(structTypeName, "pNext-pNext") |
| sTypeVuid = self.GetVuid(structTypeName, "sType-unique") |
| # If no VUIDs we will only be potentially giving false positives |
| if pNextVuid != 'kVUIDUndefined' or sTypeVuid != 'kVUIDUndefined': |
| struct = self.vk.structs[structTypeName] |
| if struct.extendedBy: |
| allowedStructName = f'allowed_structs_{structTypeName}' |
| allowedTypeCount = f'{allowedStructName}.size()' |
| allowedTypes = f'{allowedStructName}.data()' |
| extendedBy = ", ".join([self.vk.structs[x].sType for x in struct.extendedBy]) |
| usedLines.append(f'constexpr std::array {allowedStructName} = {{ {extendedBy} }};\n') |
| |
| usedLines.append(f'skip |= ValidateStructPnext({errorLoc}, {valuePrefix}{member.name}, {allowedTypeCount}, {allowedTypes}, GeneratedVulkanHeaderVersion, {pNextVuid}, {sTypeVuid});\n') |
| else: |
| usedLines += self.makePointerCheck(valuePrefix, member, lengthMember, errorLoc, arrayRequired, counValueRequired, counPtrRequired, funcName, structTypeName) |
| |
| # Feature structs are large and just wasting time checking booleans that are almost impossible to be invalid |
| isFeatureStruct = member.type in ['VkPhysicalDeviceFeatures', 'VkPhysicalDeviceFeatures2'] |
| # If this is a pointer to a struct (input), see if it contains members that need to be checked |
| if member.type in self.validatedStructs and (member.const or self.vk.structs[member.type].returnedOnly or member.pointer) and not isFeatureStruct: |
| # Process struct pointer/array validation code, performing name substitution if required |
| expr = [] |
| expr.append(f'if ({valuePrefix}{member.name} != nullptr)\n') |
| expr.append('{\n') |
| newErrorLoc = f'{member.name}_loc' |
| if lengthMember: |
| # Need to process all elements in the array |
| length = member.length.split(',')[0] |
| indexName = length.replace('Count', 'Index') |
| # If the length value is a pointer, de-reference it for the count. |
| deref = '*' if lengthMember.pointer else '' |
| expr.append(f'for (uint32_t {indexName} = 0; {indexName} < {deref}{valuePrefix}{length}; ++{indexName})\n') |
| expr.append('{\n') |
| expr.append(f'[[maybe_unused]] const Location {newErrorLoc} = {errorLoc}.dot(Field::{member.name}, {indexName});') |
| # Prefix for value name to display in error message |
| connector = '->' if member.cDeclaration.count('*') == 2 else '.' |
| memberNamePrefix = f'{valuePrefix}{member.name}[{indexName}]{connector}' |
| memberDisplayNamePrefix = (f'{valueDisplayName}[%i]{connector}', indexName) |
| else: |
| expr.append(f'[[maybe_unused]] const Location {newErrorLoc} = {errorLoc}.dot(Field::{member.name});') |
| memberNamePrefix = f'{valuePrefix}{member.name}->' |
| memberDisplayNamePrefix = f'{valueDisplayName}->' |
| |
| # Expand the struct validation lines |
| expr = self.expandStructCode(member.type, funcName, newErrorLoc, memberNamePrefix, memberDisplayNamePrefix, expr) |
| # If only 4 lines and no "skip" then this is an empty check |
| hasChecks = len(expr) > 4 or 'skip' in expr[3] |
| hasChecks = hasChecks if member.type != 'VkRect2D' else False # exception that doesn't have check actually |
| |
| if lengthMember: |
| expr.append('}\n') |
| expr.append('}\n') |
| |
| if hasChecks: |
| usedLines.append(expr) |
| |
| isConstStr = 'true' if member.const else 'false' |
| isPhysDevice_str = 'physicalDevice' if isPhysDevice else 'VK_NULL_HANDLE' |
| for setter, _, elem in multi_string_iter(usedLines): |
| elem = re.sub(r', (true|false|physicalDevice|VK_NULL_HANDLE)', '', elem) |
| m = validatePNextRegex.match(elem) |
| if m is not None: |
| setter(f'{m.group(1)}, {isPhysDevice_str}, {isConstStr}{m.group(2)}') |
| |
| # Non-pointer types |
| else: |
| # The parameter will not be processes when tagged as 'noautovalidity' |
| # For the struct case, the struct type will not be validated, but any |
| # members not tagged as 'noautovalidity' will be validated |
| if member.noAutoValidity: |
| # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually |
| self.logMsg('diag', f'ParameterValidation: No validation for {callerName} {member.name}') |
| else: |
| physicalLevel = "VK_NULL_HANDLE" # maintenance5 allows Physical-device-level functions to pass in out of range values |
| if (isPhysDevice): |
| physicalLevel = "physicalDevice" |
| elif structTypeName and 'PhysicalDevice' in structTypeName: |
| physicalLevel = "physicalDevice" |
| |
| if member.type in self.vk.structs and self.vk.structs[member.type].sType: |
| sTypeVuid = self.GetVuid(member.type, "sType-sType") |
| sType = self.vk.structs[member.type].sType |
| usedLines.append(f'skip |= ValidateStructType({errorLoc}.dot(Field::{member.name}), &({valuePrefix}{member.name}), {sType}, false, kVUIDUndefined, {sTypeVuid});\n') |
| elif member.name == 'sType' and structTypeName in self.generateStructHelper: |
| # TODO - This workaround is because this is shared by other pipeline calls that don't need generateStructHelper |
| if structTypeName != 'VkPipelineShaderStageCreateInfo': |
| # special case when dealing with isolated struct helper functions. |
| sTypeVuid = self.GetVuid(struct.name, "sType-sType") |
| usedLines.append(f'skip |= ValidateStructType(loc, &info, {struct.sType}, false, kVUIDUndefined, {sTypeVuid});\n') |
| elif member.type in self.vk.handles: |
| if not member.optional: |
| usedLines.append(f'skip |= ValidateRequiredHandle({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name});\n') |
| elif member.type in self.flags and member.type.replace('Flags', 'FlagBits') not in self.flagBits: |
| vuid = self.GetVuid(callerName, f"{member.name}-zerobitmask") |
| usedLines.append(f'skip |= ValidateReservedFlags({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name}, {vuid});\n') |
| elif member.type in self.flags or member.type in self.flagBits: |
| if member.type in self.flags: |
| flagBitsName = member.type.replace('Flags', 'FlagBits') |
| flagsType = 'kOptionalFlags' if member.optional else 'kRequiredFlags' |
| invalidVuid = self.GetVuid(callerName, f"{member.name}-parameter") |
| zeroVuid = self.GetVuid(callerName, f"{member.name}-requiredbitmask") |
| elif member.type in self.flagBits: |
| flagBitsName = member.type |
| flagsType = 'kOptionalSingleBit' if member.optional else 'kRequiredSingleBit' |
| invalidVuid = self.GetVuid(callerName, f"{member.name}-parameter") |
| zeroVuid = invalidVuid |
| # Bad workaround, but this whole file will be refactored soon |
| if flagBitsName == 'VkBuildAccelerationStructureFlagBitsNV': |
| flagBitsName = 'VkBuildAccelerationStructureFlagBitsKHR' |
| allFlagsName = 'All' + flagBitsName |
| zeroVuidArg = '' if member.optional else ', ' + zeroVuid |
| condition = [item for item in self.structMemberValidationConditions if (item['struct'] == structTypeName and item['field'] == flagBitsName)] |
| usedLines.append(f'skip |= ValidateFlags({errorLoc}.dot(Field::{member.name}), vvl::FlagBitmask::{flagBitsName}, {allFlagsName}, {valuePrefix}{member.name}, {flagsType}, {physicalLevel}, {invalidVuid}{zeroVuidArg});\n') |
| elif member.type == 'VkBool32': |
| usedLines.append(f'skip |= ValidateBool32({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name});\n') |
| elif member.type in self.vk.enums and member.type != 'VkStructureType': |
| vuid = self.GetVuid(callerName, f"{member.name}-parameter") |
| usedLines.append(f'skip |= ValidateRangedEnum({errorLoc}.dot(Field::{member.name}), vvl::Enum::{member.type}, {valuePrefix}{member.name}, {vuid}, {physicalLevel});\n') |
| # If this is a struct, see if it contains members that need to be checked |
| if member.type in self.validatedStructs: |
| memberNamePrefix = f'{valuePrefix}{member.name}.' |
| memberDisplayNamePrefix = f'{valueDisplayName}.' |
| usedLines.append(self.expandStructCode(member.type, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix, [])) |
| # Append the parameter check to the function body for the current command |
| if usedLines: |
| # Apply special conditional checks |
| if condition: |
| # Generate code to check for a specific condition before executing validation code |
| checkedExpr = [] |
| checkedExpr.append(f'if ({condition[0]["condition"]}) {{\n') |
| for expr in usedLines: |
| checkedExpr.append(expr) |
| checkedExpr.append('}\n') |
| usedLines = [checkedExpr] |
| |
| lines += usedLines |
| if not lines: |
| lines.append('// No xml-driven validation\n') |
| return lines |
| |
| # Joins strings in English fashion |
| # TODO: move to some utility library |
| def englishJoin(self, strings, conjunction: str): |
| stringsList = list(strings) |
| if len(stringsList) <= 1: |
| return stringsList[0] |
| else: # len > 1 |
| return f'{", ".join(stringsList[:-1])}, {conjunction} {stringsList[-1]}' |
| |
| |
| # This logic was broken into its own function because we need to fill multiple functions with these structs |
| def genStructBody(self, struct: Struct, nonPropFeature: bool): |
| pNextCase = '\n' |
| pNextCheck = '' |
| |
| pNextCase += f' // Validation code for {struct.name} structure members\n' |
| pNextCase += f' case {struct.sType}: {{ // Covers VUID-{struct.name}-sType-sType\n' |
| |
| # TODO - https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/9185 |
| if struct.name == 'VkPhysicalDeviceLayeredApiPropertiesListKHR': |
| return "" |
| |
| if struct.sType and struct.version and all(not x.promotedTo for x in struct.extensions): |
| pNextCheck += f''' |
| if (is_physdev_api) {{ |
| VkPhysicalDeviceProperties device_properties = {{}}; |
| DispatchGetPhysicalDeviceProperties(physicalDevice, &device_properties); |
| if (device_properties.apiVersion < {struct.version.nameApi}) {{ |
| APIVersion device_api_version(static_cast<uint32_t>(device_properties.apiVersion)); |
| skip |= LogError( |
| pnext_vuid, instance, loc.dot(Field::pNext), |
| "includes a pointer to a VkStructureType ({struct.sType}) which was added in {struct.version.nameApi} but the " |
| "current effective API version is %s.", StringAPIVersion(device_api_version).c_str()); |
| }} |
| }} |
| ''' |
| |
| elif struct.sType in self.stype_version_dict.keys(): |
| extNames = self.stype_version_dict[struct.sType] |
| |
| # Skip extensions that are not in the target API |
| # This check is needed because parts of the base generator code bypass the |
| # dependency resolution logic in the registry tooling and thus the generator |
| # may attempt to generate code for extensions which are not supported in the |
| # target API variant, thus this check needs to happen even if any specific |
| # target API variant may not specifically need it |
| extNames.intersection(self.vk.extensions.keys()) |
| if len(extNames) == 0: |
| return "" |
| |
| extNames = sorted(list(extNames)) # make the order deterministic |
| |
| # Dependent on enabled extension |
| extension_conditinals = list() |
| for ext_name in extNames: |
| extension = self.vk.extensions[ext_name] |
| if extension.device: |
| extension_conditinals.append( f'((is_physdev_api && !SupportedByPdev(physical_device, vvl::Extension::_{extension.name})) || (!is_physdev_api && !IsExtEnabled(device_extensions.{extension.name.lower()})))' ) |
| else: |
| extension_conditinals.append( f'!IsExtEnabled(instance_extensions.{extension.name.lower()})' ) |
| if len(extension_conditinals) == 1 and extension_conditinals[0][0] == '(' and extension_conditinals[0][-1] == ')': |
| extension_conditinals[0] = extension_conditinals[0][1:-1]# strip extraneous parentheses |
| |
| extension_check = f'if ({" && ".join(extension_conditinals)}) {{' |
| pNextCheck += f''' |
| {extension_check} |
| skip |= LogError( |
| pnext_vuid, instance, loc.dot(Field::pNext), |
| "includes a pointer to a VkStructureType ({struct.sType}), but its parent extension " |
| "{self.englishJoin(extNames, "or")} has not been enabled."); |
| }} |
| ''' |
| |
| expr = self.expandStructCode(struct.name, struct.name, 'pNext_loc', 'structure->', '', []) |
| structValidationSource = self.ScrubStructCode(expr) |
| if structValidationSource != '': |
| # Only reasonable to validate content of structs if const as otherwise the date inside has not been writen to yet |
| # https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/3122 |
| if struct.name in ['VkPhysicalDeviceLayeredApiPropertiesListKHR']: |
| pNextCheck += 'if (true /* exception where we do not want to check for is_const_param */) {\n' |
| else: |
| pNextCheck += 'if (is_const_param) {\n' |
| |
| pNextCheck += f'[[maybe_unused]] const Location pNext_loc = loc.pNext(Struct::{struct.name});\n' |
| |
| # Can have a struct from a device extension be extended by an instance extension struct |
| # https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/7803 |
| # This is true already for all Properties/Features so exclude them here |
| check_for_instance = False |
| if nonPropFeature and isDeviceStruct(struct): |
| for extend in struct.extends: |
| if not isDeviceStruct(self.vk.structs[extend]): |
| check_for_instance = True |
| |
| # We do the extension checking here at the pNext chaining because if the struct is only used in a new extended command, |
| # using that command will always trigger a "missing extension" VU |
| checkExpression = [] |
| resultExpression = [] |
| for extension in [x.name for x in struct.extensions if x.device]: |
| if check_for_instance: |
| checkExpression.append(f'(!is_physdev_api && !IsExtEnabled(device_extensions.{extension.lower()}))') |
| else: |
| checkExpression.append(f'!IsExtEnabled(device_extensions.{extension.lower()})') |
| resultExpression.append(extension) |
| # TODO - Video session creation checks will fail tests if no extensions are found (need to fix test logic) |
| if len(checkExpression) > 0 and 'Video' not in struct.name: |
| # Special message for device features |
| if 'VkPhysicalDeviceFeatures2' in struct.extends and 'VkDeviceCreateInfo' in struct.extends: |
| pNextCheck += f'''if ({" && ".join(checkExpression)}){{ |
| skip |= LogError(pnext_vuid, instance, pNext_loc, |
| "includes a pointer to a {struct.name}, but when creating VkDevice, the parent extension " |
| "({" or ".join(resultExpression)}) was not included in ppEnabledExtensionNames."); |
| }} |
| ''' |
| else: |
| pNextCheck += f'''if ({" && ".join(checkExpression)}){{ |
| skip |= LogError(pnext_vuid, instance, pNext_loc, "extended struct requires the extensions {" or ".join(resultExpression)}"); |
| }} |
| ''' |
| |
| structValidationSource = f'{struct.name} *structure = ({struct.name} *) header;\n{structValidationSource}' |
| structValidationSource += '}\n' |
| pNextCase += f'{pNextCheck}{structValidationSource}' |
| pNextCase += '} break;\n' |
| # Skip functions containing no validation |
| if structValidationSource or pNextCheck != '': |
| return pNextCase |
| else: |
| return f'\n // No Validation code for {struct.name} structure members -- Covers VUID-{struct.name}-sType-sType\n' |
| |
| # Helper for iterating over a list where each element is possibly a single element or another 1-dimensional list |
| # Generates (setter, deleter, element) for each element where: |
| # - element = the next element in the list |
| # - setter(x) = a function that will set the entry in `lines` corresponding to `element` to `x` |
| # - deleter() = a function that will delete the entry corresponding to `element` in `lines` |
| def multi_string_iter(lines): |
| for i, ul in enumerate(lines): |
| if not isinstance(ul, list): |
| def setter(x): lines[i] = x |
| def deleter(): del(lines[i]) |
| yield (setter, deleter, ul) |
| else: |
| for j, k in enumerate(lines[i]): |
| def setter(x): lines[i][j] = x |
| def deleter(): del(lines[i][j]) |
| yield (setter, deleter, k) |