| #!/usr/bin/python3 -i |
| # |
| # Copyright (c) 2020-2024 The Khronos Group 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. |
| |
| import sys |
| import os |
| import json |
| from generators.vulkan_object import SpirvEnables |
| from generators.base_generator import BaseGenerator |
| from generators.generator_utils import IsNonVulkanSprivCapability |
| |
| # |
| # Generate SPIR-V validation for SPIR-V extensions and capabilities |
| class SpirvValidationHelperOutputGenerator(BaseGenerator): |
| def __init__(self, grammar): |
| BaseGenerator.__init__(self) |
| |
| # Sometimes the Vulkan-Headers XML will mention new SPIR-V capability or extensions |
| # that require an update of the SPIRV-Headers which might not be ready to pull in. |
| # Get the list of safe enum values to use from the SPIR-V grammar |
| self.capabilityList = [] |
| self.provisionalList = [] |
| with open(grammar) as grammar_file: |
| grammar_dict = json.load(grammar_file) |
| for kind in grammar_dict['operand_kinds']: |
| if kind['kind'] == 'Capability': |
| for enum in kind['enumerants']: |
| if not IsNonVulkanSprivCapability(enum['enumerant']): |
| self.capabilityList.append(enum['enumerant']) |
| if 'provisional' in enum: |
| self.provisionalList.append(enum['enumerant']) |
| break |
| |
| # Promoted features structure in state_tracker.cpp are put in the VkPhysicalDeviceVulkan*Features structs |
| # but the XML can still list them. This list all promoted structs to ignore since they are aliased. |
| # Tried to generate these, but no reliable way from vk.xml |
| self.promotedFeatures = [ |
| # 1.1 |
| "VkPhysicalDevice16BitStorageFeatures", |
| "VkPhysicalDeviceMultiviewFeatures", |
| "VkPhysicalDeviceVariablePointersFeatures", |
| "VkPhysicalDeviceProtectedMemoryFeatures", |
| "VkPhysicalDeviceSamplerYcbcrConversionFeatures", |
| "VkPhysicalDeviceShaderDrawParametersFeatures", |
| # 1.2 |
| "VkPhysicalDevice8BitStorageFeatures", |
| "VkPhysicalDeviceShaderFloat16Int8Features", |
| "VkPhysicalDeviceDescriptorIndexingFeatures", |
| "VkPhysicalDeviceScalarBlockLayoutFeatures", |
| "VkPhysicalDeviceImagelessFramebufferFeatures", |
| "VkPhysicalDeviceUniformBufferStandardLayoutFeatures", |
| "VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures", |
| "VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures", |
| "VkPhysicalDeviceTimelineSemaphoreFeatures", |
| "VkPhysicalDeviceBufferDeviceAddressFeatures", |
| "VkPhysicalDeviceShaderAtomicInt64Features", |
| "VkPhysicalDeviceVulkanMemoryModelFeatures", |
| # 1.3 |
| "VkPhysicalDeviceShaderDemoteToHelperInvocationFeatures", |
| "VkPhysicalDeviceShaderIntegerDotProductFeatures", |
| ] |
| |
| # Properties are harder to handle genearted without generating a template for every property struct type |
| # The simpler solution is create strings that will be printed out as static comparisons at compile time |
| # The Map is used to map Vulkan property structs with the state tracker variable name |
| self.propertyInfo = dict() |
| self.propertyMap = { |
| 'VkPhysicalDeviceVulkan11Properties' : 'phys_dev_props_core11', |
| 'VkPhysicalDeviceVulkan12Properties' : 'phys_dev_props_core12', |
| 'VkPhysicalDeviceVulkan13Properties' : 'phys_dev_props_core13', # Not used, but left for grep searching |
| } |
| |
| # |
| # Creates the value of the struct declared in RequiredSpirvInfo |
| def createMapValue(self, name: str, enable: SpirvEnables, isExtension: bool) -> str: |
| out = [] |
| if enable.version is not None: |
| # Version should be VK_VERSION_x_x as defined in header but need to get as VK_API_VERSION_x_x |
| version = enable.version.replace('VK_VERSION', 'VK_API_VERSION') |
| out.append(f'{{{version}, nullptr, nullptr, ""}}') |
| elif enable.feature is not None: |
| out.append(f'{{0, &DeviceFeatures::{enable.feature}, nullptr, ""}}') |
| elif enable.extension is not None: |
| # All fields in DeviceExtensions should just be the extension name lowercase |
| out.append(f'{{0, nullptr, &DeviceExtensions::{enable.extension.lower()}, ""}}') |
| elif enable.property is not None: |
| propertyStruct = enable.property |
| # Need to make sure to return a boolean value to prevent compiler warning for implicit conversions |
| propertyLogic = f'({propertyStruct}::{enable.member} & {enable.value}) != 0' |
| # Property might have multiple items per capability/extension |
| if name not in self.propertyInfo: |
| self.propertyInfo[name] = [] |
| # Save info later to be printed out |
| self.propertyInfo[name].append({ |
| "logic" : propertyLogic, |
| "struct" : propertyStruct, |
| "isExtension" : isExtension |
| }) |
| # For properties, this string is just for human readableness |
| out.append(f'{{0, nullptr, nullptr, "{propertyLogic}"}}') |
| else: |
| out.append('{0, nullptr, nullptr, ""}') |
| return "".join(out) |
| |
| def generate(self): |
| out = [] |
| out.append(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT *** |
| // See {os.path.basename(__file__)} for modifications |
| |
| /*************************************************************************** |
| * |
| * Copyright (c) 2020-2024 The Khronos Group 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. |
| * |
| * This file is related to anything that is found in the Vulkan XML related |
| * to SPIR-V. Anything related to the SPIR-V grammar belongs in spirv_grammar_helper |
| * |
| ****************************************************************************/ |
| ''') |
| out.append('// NOLINTBEGIN') # Wrap for clang-tidy to ignore |
| out.append(''' |
| #include <string> |
| #include <string_view> |
| #include <functional> |
| #include <spirv/unified1/spirv.hpp> |
| #include "vk_extension_helper.h" |
| #include "state_tracker/shader_instruction.h" |
| #include "core_checks/core_validation.h" |
| ''') |
| |
| # |
| # Creates the FeaturePointer struct to map features with those in the layers state tracker |
| out.append(''' |
| struct FeaturePointer { |
| // Callable object to test if this feature is enabled in the given aggregate feature struct |
| const std::function<bool(const DeviceFeatures &)> IsEnabled; |
| |
| // Test if feature pointer is populated |
| explicit operator bool() const { return static_cast<bool>(IsEnabled); } |
| |
| // Default and nullptr constructor to create an empty FeaturePointer |
| FeaturePointer() : IsEnabled(nullptr) {} |
| FeaturePointer(std::nullptr_t ptr) : IsEnabled(nullptr) {} |
| FeaturePointer(bool DeviceFeatures::*ptr) |
| : IsEnabled([=](const DeviceFeatures &features) { return features.*ptr; }) {} |
| }; |
| ''') |
| |
| out.append(''' |
| // Each instance of the struct will only have a singel field non-null |
| struct RequiredSpirvInfo { |
| uint32_t version; |
| FeaturePointer feature; |
| ExtEnabled DeviceExtensions::*extension; |
| const char* property; // For human readability and make some capabilities unique |
| }; |
| |
| ''') |
| |
| # |
| # Build the struct with all the requirments for the spirv capabilities |
| out.append('const std::unordered_multimap<uint32_t, RequiredSpirvInfo>& GetSpirvCapabilites() {\n') |
| out.append('// clang-format off\n') |
| out.append(' static const std::unordered_multimap<uint32_t, RequiredSpirvInfo> spirv_capabilities = {') |
| for spirv in [x for x in self.vk.spirv if x.capability]: |
| for enable in [x for x in spirv.enable if x.struct is None or x.struct not in self.promotedFeatures]: |
| if spirv.name not in self.capabilityList: |
| out.append('\n // Not found in current SPIR-V Headers\n // ') |
| elif spirv.name in self.provisionalList: |
| out.append('\n#ifdef VK_ENABLE_BETA_EXTENSIONS\n ') |
| else: |
| out.append('\n ') |
| out.append(f'{{spv::Capability{spirv.name}, {self.createMapValue(spirv.name, enable, False)}}},') |
| |
| out.append('\n#endif') if spirv.name in self.provisionalList else None |
| out.append('\n };\n') |
| out.append('// clang-format on\n') |
| out.append(' return spirv_capabilities;\n') |
| out.append('};\n') |
| out.append('\n') |
| |
| # |
| # Build the struct with all the requirments for the spirv extensions |
| out.append('const std::unordered_multimap<std::string_view, RequiredSpirvInfo>& GetSpirvExtensions() {\n') |
| out.append('// clang-format off\n') |
| out.append(' static const std::unordered_multimap<std::string_view, RequiredSpirvInfo> spirv_extensions = {') |
| for spirv in [x for x in self.vk.spirv if x.extension]: |
| for enable in spirv.enable: |
| out.append(f'\n {{"{spirv.name}", {self.createMapValue(spirv.name, enable, True)}}},') |
| out.append('\n };\n') |
| out.append('// clang-format on\n') |
| out.append(' return spirv_extensions;\n') |
| out.append('}\n') |
| out.append('\n') |
| |
| # |
| # Creates the Enum string helpers for better error messages. Same idea of vk_enum_string_helper.h but for SPIR-V |
| out.append('static inline const char* string_SpvCapability(uint32_t input_value) {\n') |
| out.append(' switch ((spv::Capability)input_value) {\n') |
| for name in self.capabilityList: |
| out.append('#ifdef VK_ENABLE_BETA_EXTENSIONS\n') if name in self.provisionalList else None |
| out.append(f' case spv::Capability{name}:\n') |
| out.append(f' return "{name}";\n') |
| out.append('#endif\n') if name in self.provisionalList else None |
| out.append(' default:\n') |
| out.append(' return \"Unhandled OpCapability\";\n') |
| out.append(' };\n') |
| out.append('}\n') |
| # |
| # Creates SPIR-V image format helper |
| out.append(''' |
| // Will return the Vulkan format for a given SPIR-V image format value |
| // Note: will return VK_FORMAT_UNDEFINED if non valid input |
| // This was in vk_format_utils but the SPIR-V Header dependency was an issue |
| // see https://github.com/KhronosGroup/Vulkan-ValidationLayers/pull/4647 |
| VkFormat CoreChecks::CompatibleSpirvImageFormat(uint32_t spirv_image_format) const { |
| switch (spirv_image_format) { |
| ''') |
| for format in [x for x in self.vk.formats.values() if x.spirvImageFormat]: |
| out.append(f' case spv::ImageFormat{format.spirvImageFormat}:\n') |
| out.append(f' return {format.name};\n') |
| out.append(' default:\n') |
| out.append(' return VK_FORMAT_UNDEFINED;\n') |
| out.append(' };\n') |
| out.append('}\n') |
| |
| |
| out.append(''' |
| // clang-format off |
| static inline const char* SpvCapabilityRequirements(uint32_t capability) { |
| static const vvl::unordered_map<uint32_t, std::string_view> table { |
| ''') |
| for spirv in [x for x in self.vk.spirv if x.capability and x.name in self.capabilityList]: |
| requirment = '' |
| for index, enable in enumerate([x for x in spirv.enable if x.struct is None or x.struct not in self.promotedFeatures]): |
| requirment += ' OR ' if (index != 0) else '' |
| if enable.version is not None: |
| requirment += enable.version |
| elif enable.feature is not None: |
| requirment += f'{enable.struct}::{enable.feature}' |
| elif enable.extension is not None: |
| requirment += enable.extension |
| elif enable.property is not None: |
| requirment += f'({enable.property}::{enable.member} == {enable.value})' |
| |
| out.append('#ifdef VK_ENABLE_BETA_EXTENSIONS\n') if spirv.name in self.provisionalList else None |
| out.append(f' {{spv::Capability{spirv.name}, "{requirment}"}},\n') |
| out.append('#endif // VK_ENABLE_BETA_EXTENSIONS\n') if spirv.name in self.provisionalList else None |
| out.append(''' }; |
| |
| // VUs before catch unknown capabilities |
| const auto entry = table.find(capability); |
| return entry->second.data(); |
| } |
| // clang-format on |
| ''') |
| |
| out.append(''' |
| // clang-format off |
| static inline std::string SpvExtensionRequirments(std::string_view extension) { |
| static const vvl::unordered_map<std::string_view, vvl::Requirements> table { |
| ''') |
| for spirv in [x for x in self.vk.spirv if x.extension]: |
| requirment = '' |
| for index, enable in enumerate(spirv.enable): |
| requirment += ', ' if (index != 0) else '' |
| if enable.version is not None: |
| requirment += f'{{vvl::Version::_{enable.version}}}' |
| elif enable.extension is not None: |
| requirment += f'{{vvl::Extension::_{enable.extension}}}' |
| elif enable.feature is not None or enable.property is not None: |
| print("Need to add support for feature/properties in spirv extensions") |
| sys.exit(1) |
| out.append(f' {{"{spirv.name}", {{{requirment}}}}},\n') |
| out.append(''' }; |
| |
| // VUs before catch unknown extensions |
| const auto entry = table.find(extension); |
| return String(entry->second); |
| } |
| // clang-format on |
| ''') |
| |
| # The chance of an SPIR-V extension having a property as a requirement is low |
| # Instead of writting complex (and more confusing) code, just go back match what |
| # we do for capabilities if one is ever added to the XML |
| if len([infos for infos in self.propertyInfo.values() if infos[0]['isExtension']]) > 0: |
| print("Error: XML has added a property requirement to a SPIR-V Extension") |
| sys.exit(1) |
| |
| # |
| # The main function to validate all the extensions and capabilities |
| out.append(''' |
| bool CoreChecks::ValidateShaderCapabilitiesAndExtensions(const spirv::Instruction &insn, const Location& loc) const { |
| bool skip = false; |
| const bool pipeline = loc.function != vvl::Func::vkCreateShadersEXT; |
| |
| if (insn.Opcode() == spv::OpCapability) { |
| // All capabilities are generated so if it is not in the list it is not supported by Vulkan |
| if (GetSpirvCapabilites().count(insn.Word(1)) == 0) { |
| const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08739" : "VUID-VkShaderCreateInfoEXT-pCode-08739"; |
| skip |= LogError(vuid, device, loc, |
| "SPIR-V has Capability (%s) declared, but this is not supported by Vulkan.", string_SpvCapability(insn.Word(1))); |
| return skip; // no known capability to validate |
| } |
| |
| // Each capability has one or more requirements to check |
| // Only one item has to be satisfied and an error only occurs |
| // when all are not satisfied |
| auto caps = GetSpirvCapabilites().equal_range(insn.Word(1)); |
| bool has_support = false; |
| for (auto it = caps.first; (it != caps.second) && (has_support == false); ++it) { |
| if (it->second.version) { |
| if (api_version >= it->second.version) { |
| has_support = true; |
| } |
| } else if (it->second.feature) { |
| if (it->second.feature.IsEnabled(enabled_features)) { |
| has_support = true; |
| } |
| } else if (it->second.extension) { |
| // kEnabledByApiLevel is not valid as some extension are promoted with feature bits to be used. |
| // If the new Api Level gives support, it will be caught in the "it->second.version" check instead. |
| if (IsExtEnabledByCreateinfo(device_extensions.*(it->second.extension))) { |
| has_support = true; |
| } |
| } else if (it->second.property) { |
| // support is or'ed as only one has to be supported (if applicable) |
| switch (insn.Word(1)) {''') |
| |
| for name, infos in sorted(self.propertyInfo.items()): |
| # Only capabilities here (all items in array are the same) |
| if infos[0]['isExtension']: |
| continue |
| |
| # use triple-tick syntax to keep tab alignment for generated code |
| out.append(f''' |
| case spv::Capability{name}:''') |
| for info in infos: |
| # Need to string replace property string to create valid C++ logic |
| logic = info['logic'].replace('::', '.') |
| logic = logic.replace(info['struct'], self.propertyMap[info['struct']]) |
| out.append(f''' |
| has_support |= ({logic});''') |
| out.append(''' |
| break;''') |
| |
| out.append(''' |
| default: |
| break; |
| } |
| } |
| } |
| ''') |
| |
| out.append(''' |
| if (has_support == false) { |
| const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08740" : "VUID-VkShaderCreateInfoEXT-pCode-08740"; |
| skip |= LogError(vuid, device, loc, |
| "SPIR-V Capability %s was declared, but one of the following requirements is required (%s).", string_SpvCapability(insn.Word(1)), SpvCapabilityRequirements(insn.Word(1))); |
| } |
| |
| // Portability checks |
| if (IsExtEnabled(device_extensions.vk_khr_portability_subset)) { |
| if ((VK_FALSE == enabled_features.shaderSampleRateInterpolationFunctions) && |
| (spv::CapabilityInterpolationFunction == insn.Word(1))) { |
| skip |= LogError("VUID-RuntimeSpirv-shaderSampleRateInterpolationFunctions-06325", device, loc, |
| "SPIR-V (portability error) InterpolationFunction Capability are not supported " |
| "by this platform"); |
| } |
| } |
| } else if (insn.Opcode() == spv::OpExtension) { |
| static const std::string spv_prefix = "SPV_"; |
| std::string extension_name = insn.GetAsString(1); |
| |
| if (0 == extension_name.compare(0, spv_prefix.size(), spv_prefix)) { |
| if (GetSpirvExtensions().count(extension_name) == 0) { |
| const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08741" : "VUID-VkShaderCreateInfoEXT-pCode-08741"; |
| skip |= LogError(vuid, device,loc, |
| "SPIR-V Extension %s was declared, but that is not supported by Vulkan.", extension_name.c_str()); |
| return skip; // no known extension to validate |
| } |
| } else { |
| const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08741" : "VUID-VkShaderCreateInfoEXT-pCode-08741"; |
| skip |= LogError( vuid, device,loc, |
| "SPIR-V Extension %s was declared, but this is not a SPIR-V extension. Please use a SPIR-V" |
| " extension (https://github.com/KhronosGroup/SPIRV-Registry) for OpExtension instructions. Non-SPIR-V extensions can be" |
| " recorded in SPIR-V using the OpSourceExtension instruction.", extension_name.c_str()); |
| return skip; // no known extension to validate |
| } |
| |
| // Each SPIR-V Extension has one or more requirements to check |
| // Only one item has to be satisfied and an error only occurs |
| // when all are not satisfied |
| auto ext = GetSpirvExtensions().equal_range(extension_name); |
| bool has_support = false; |
| for (auto it = ext.first; (it != ext.second) && (has_support == false); ++it) { |
| if (it->second.version) { |
| if (api_version >= it->second.version) { |
| has_support = true; |
| } |
| } else if (it->second.feature) { |
| if (it->second.feature.IsEnabled(enabled_features)) { |
| has_support = true; |
| } |
| } else if (it->second.extension) { |
| if (IsExtEnabled(device_extensions.*(it->second.extension))) { |
| has_support = true; |
| } |
| } |
| } |
| |
| if (has_support == false) { |
| const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08742" : "VUID-VkShaderCreateInfoEXT-pCode-08742"; |
| skip |= LogError(vuid, device, loc, |
| "SPIR-V Extension %s was declared, but one of the following requirements is required (%s).", extension_name.c_str(), SpvExtensionRequirments(extension_name).c_str()); |
| } |
| } //spv::OpExtension |
| return skip; |
| } |
| ''') |
| out.append('// NOLINTEND') # Wrap for clang-tidy to ignore |
| self.write("".join(out)) |