| /* Copyright (c) 2023-2026 The Khronos Group Inc. |
| * Copyright (c) 2023-2026 Valve Corporation |
| * Copyright (c) 2023-2026 LunarG, 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. |
| */ |
| |
| #include "descriptor_validator.h" |
| #include <vulkan/vulkan_core.h> |
| #include <vulkan/utility/vk_format_utils.h> |
| #include <sstream> |
| #include "generated/spirv_grammar_helper.h" |
| #include "generated/spirv_validation_helper.h" |
| #include "state_tracker/shader_instruction.h" |
| #include "state_tracker/shader_stage_state.h" |
| #include "error_message/error_strings.h" |
| #include "error_message/spirv_logging.h" |
| #include "state_tracker/descriptor_sets.h" |
| #include "state_tracker/cmd_buffer_state.h" |
| #include "state_tracker/pipeline_state.h" |
| #include "state_tracker/image_state.h" |
| #include "state_tracker/buffer_state.h" |
| #include "state_tracker/render_pass_state.h" |
| #include "state_tracker/ray_tracing_state.h" |
| #include "state_tracker/shader_module.h" |
| #include "state_tracker/tensor_state.h" |
| #include "drawdispatch/drawdispatch_vuids.h" |
| #include "utils/action_command_utils.h" |
| #include "utils/image_utils.h" |
| |
| namespace vvl { |
| |
| // This seems like it could be useful elsewhere, but until find another spot, just keep here. |
| static const char *GetActionType(Func command) { |
| if (IsCommandDispatch(command)) { |
| return "dispatch"; |
| } else if (IsCommandTraceRays(command)) { |
| return "trace rays"; |
| } else { |
| return "draw"; |
| } |
| } |
| |
| std::string DescriptorValidator::DescribeDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, uint32_t index, |
| VkDescriptorType type) const { |
| std::ostringstream ss; |
| switch (type) { |
| case VK_DESCRIPTOR_TYPE_SAMPLER: |
| ss << "sampler "; |
| break; |
| case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: |
| ss << "combined image sampler "; |
| break; |
| case VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM: |
| ss << "sampled weight image "; |
| break; |
| case VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM: |
| ss << "block match image "; |
| break; |
| case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: |
| ss << "sampled image "; |
| break; |
| case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: |
| ss << "storage image "; |
| break; |
| case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: |
| case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: |
| ss << "uniform buffer "; |
| break; |
| case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: |
| ss << "uniform texel buffer "; |
| break; |
| case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: |
| case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: |
| ss << "storage buffer "; |
| break; |
| case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: |
| ss << "storage texel buffer "; |
| break; |
| case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: |
| ss << "input attachment "; |
| break; |
| case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK: |
| ss << "inline buffer "; |
| break; |
| case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR: |
| case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV: |
| ss << "acceleration structure "; |
| break; |
| case VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV: |
| ss << "partitioned acceleration structure "; |
| break; |
| case VK_DESCRIPTOR_TYPE_TENSOR_ARM: |
| ss << "tensor "; |
| break; |
| case VK_DESCRIPTOR_TYPE_MUTABLE_EXT: |
| case VK_DESCRIPTOR_TYPE_MAX_ENUM: |
| break; |
| } |
| |
| ss << "descriptor ["; |
| if (!descriptor_set.IsPushDescriptor()) { |
| ss << FormatHandle(descriptor_set.Handle()) << ", "; |
| } |
| ss << "Set " << set_index << ", Binding " << resource_variable.decorations.binding << ", Index " << index; |
| |
| // If multiple variables tied to a binding, don't attempt to detect which one |
| if (!resource_variable.debug_name.empty()) { |
| ss << ", variable \"" << resource_variable.debug_name << "\""; |
| } |
| ss << "]"; |
| return ss.str(); |
| } |
| |
| std::string DescriptorValidator::DescribeInstruction() const { |
| std::ostringstream ss; |
| // This will not work for the static version because there we care there is an OpVariable defined (not if it is used). |
| if (original_spirv) { |
| ss << '\n'; |
| ::spirv::FindShaderSource(ss, *original_spirv, instruction_position_offset, false); |
| } |
| return ss.str(); |
| } |
| |
| DescriptorValidator::DescriptorValidator(vvl::DeviceProxy &dev, CommandBuffer &cb_state, DescriptorSet &descriptor_set, |
| uint32_t set_index, VkFramebuffer framebuffer, const LogObjectList *objlist, |
| const Location &loc) |
| : Logger(dev.debug_report), |
| dev_proxy(dev), |
| is_gpu_av(dev.container_type == LayerObjectTypeGpuAssisted), |
| cb_state(cb_state), |
| descriptor_set(descriptor_set), |
| framebuffer(framebuffer), |
| loc(loc), |
| vuids(&GetDrawDispatchVuid(loc.function)), |
| original_spirv(nullptr), // chance might not find |
| instruction_position_offset(0), |
| set_index(set_index), |
| objlist(objlist) {} |
| |
| void DescriptorValidator::SetLocationForGpuAv(const Location &gpuav_loc) { |
| loc = LocationCapture(gpuav_loc); |
| vuids = &GetDrawDispatchVuid(gpuav_loc.function); |
| } |
| |
| template <typename T> |
| bool DescriptorValidator::ValidateDescriptorsStatic(const spirv::ResourceInterfaceVariable &resource_variable, |
| const T &binding) const { |
| bool skip = false; |
| |
| // If there is a descriptor array, we care about the size of the array statically used in the shader, not what was declared in |
| // the pipeline layout more info https://gitlab.khronos.org/vulkan/vulkan/-/issues/4383 |
| uint32_t count = binding.count; |
| // if using VARIABLE_DESCRIPTOR_COUNT, we will still use binding.count as that will be the adjusted value |
| if (resource_variable.IsArray() && descriptor_set.GetVariableDescriptorCount() == 0) { |
| count = resource_variable.array_length; |
| } |
| |
| for (uint32_t index = 0; !skip && index < count; index++) { |
| const auto &descriptor = binding.descriptors[index]; |
| |
| if (!binding.updated[index]) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= |
| LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is being used in %s but has never been updated via vkUpdateDescriptorSets() or a similar call.", |
| DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_MAX_ENUM).c_str(), |
| GetActionType(loc.Get().function)); |
| return skip; // early return if invalid |
| } |
| skip |= ValidateDescriptor(resource_variable, index, binding.type, descriptor); |
| } |
| return skip; |
| } |
| |
| bool DescriptorValidator::ValidateBindingStatic(const spirv::ResourceInterfaceVariable &resource_variable, |
| const DescriptorBinding &binding) const { |
| bool skip = false; |
| switch (binding.descriptor_class) { |
| case DescriptorClass::GeneralBuffer: |
| skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const BufferBinding &>(binding)); |
| break; |
| case DescriptorClass::ImageSampler: |
| skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const ImageSamplerBinding &>(binding)); |
| break; |
| case DescriptorClass::Image: |
| skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const ImageBinding &>(binding)); |
| break; |
| case DescriptorClass::PlainSampler: |
| skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const SamplerBinding &>(binding)); |
| break; |
| case DescriptorClass::TexelBuffer: |
| skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const TexelBinding &>(binding)); |
| break; |
| case DescriptorClass::AccelerationStructure: |
| skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const AccelerationStructureBinding &>(binding)); |
| break; |
| case DescriptorClass::Tensor: |
| skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const TensorBinding &>(binding)); |
| break; |
| case DescriptorClass::InlineUniform: |
| break; // Can't validate the descriptor because it may not have been updated. |
| case DescriptorClass::Mutable: |
| break; // TODO - is there anything that can be checked here? |
| case DescriptorClass::Invalid: |
| assert(false); |
| break; |
| } |
| return skip; |
| } |
| |
| template <typename T> |
| bool DescriptorValidator::ValidateDescriptorsDynamic(const spirv::ResourceInterfaceVariable &resource_variable, const T &binding, |
| const uint32_t index) { |
| bool skip = false; |
| const auto &descriptor = binding.descriptors[index]; |
| |
| if (!binding.updated[index]) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is being used in %s but has never been updated via vkUpdateDescriptorSets() or a similar call.%s", |
| DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_MAX_ENUM).c_str(), |
| GetActionType(loc.Get().function), DescribeInstruction().c_str()); |
| return skip; // early return if invalid |
| } |
| skip |= ValidateDescriptor(resource_variable, index, binding.type, descriptor); |
| return skip; |
| } |
| |
| bool DescriptorValidator::ValidateBindingDynamic(const spirv::ResourceInterfaceVariable &resource_variable, |
| DescriptorBinding &binding, const uint32_t index) { |
| bool skip = false; |
| |
| switch (binding.descriptor_class) { |
| case DescriptorClass::GeneralBuffer: |
| skip |= ValidateDescriptorsDynamic(resource_variable, static_cast<const BufferBinding &>(binding), index); |
| break; |
| case DescriptorClass::ImageSampler: { |
| auto &img_sampler_binding = static_cast<ImageSamplerBinding &>(binding); |
| if (dev_proxy.gpuav_settings.validate_image_layout) { |
| auto &descriptor = img_sampler_binding.descriptors[index]; |
| descriptor.UpdateImageLayoutDrawState(cb_state); |
| } |
| skip |= ValidateDescriptorsDynamic(resource_variable, img_sampler_binding, index); |
| break; |
| } |
| case DescriptorClass::Image: { |
| auto &img_binding = static_cast<ImageBinding &>(binding); |
| if (dev_proxy.gpuav_settings.validate_image_layout) { |
| auto &descriptor = img_binding.descriptors[index]; |
| descriptor.UpdateImageLayoutDrawState(cb_state); |
| } |
| skip |= ValidateDescriptorsDynamic(resource_variable, img_binding, index); |
| break; |
| } |
| case DescriptorClass::PlainSampler: |
| skip |= ValidateDescriptorsDynamic(resource_variable, static_cast<const SamplerBinding &>(binding), index); |
| break; |
| case DescriptorClass::TexelBuffer: |
| skip |= ValidateDescriptorsDynamic(resource_variable, static_cast<const TexelBinding &>(binding), index); |
| break; |
| case DescriptorClass::AccelerationStructure: |
| skip |= |
| ValidateDescriptorsDynamic(resource_variable, static_cast<const AccelerationStructureBinding &>(binding), index); |
| break; |
| case DescriptorClass::Tensor: |
| skip |= ValidateDescriptorsDynamic(resource_variable, static_cast<const TensorBinding &>(binding), index); |
| break; |
| case DescriptorClass::InlineUniform: |
| break; // TODO - Can give warning if reading uninitialized data |
| case DescriptorClass::Mutable: |
| break; // TODO - is there anything that can be checked here? |
| case DescriptorClass::Invalid: |
| assert(false); |
| break; |
| } |
| return skip; |
| } |
| |
| bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, const uint32_t index, |
| VkDescriptorType descriptor_type, const BufferDescriptor &descriptor) const { |
| bool skip = false; |
| // Verify that buffers are valid |
| const VkBuffer buffer = descriptor.GetBuffer(); |
| auto buffer_node = descriptor.GetBufferState(); |
| if ((!buffer_node && !dev_proxy.enabled_features.nullDescriptor) || (buffer_node && buffer_node->Destroyed())) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using buffer %s that is invalid or has been destroyed.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer).c_str(), |
| DescribeInstruction().c_str()); |
| // early return if no valid |
| return skip; |
| } |
| |
| // Buffer could be null via nullDescriptor and accessing it is legal |
| if (buffer == VK_NULL_HANDLE) { |
| return skip; |
| } |
| if (buffer_node /* && !buffer_node->sparse*/) { |
| for (const auto &binding : buffer_node->GetInvalidMemory()) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using buffer %s that references invalid memory %s.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer).c_str(), |
| FormatHandle(binding->Handle()).c_str(), DescribeInstruction().c_str()); |
| } |
| if (dev_proxy.enabled_features.tileMemoryHeap && resource_variable.IsAccessed()) { |
| skip |= dev_proxy.ValidateBoundTileMemory(*descriptor.GetBufferState(), cb_state, *vuids); |
| } |
| } |
| if (dev_proxy.enabled_features.protectedMemory == VK_TRUE) { |
| skip |= dev_proxy.ValidateProtectedBuffer(cb_state, *buffer_node, loc.Get(), vuids->unprotected_command_buffer_02707, |
| " (Buffer is in a descriptorSet)"); |
| if (resource_variable.IsWrittenTo()) { |
| skip |= dev_proxy.ValidateUnprotectedBuffer(cb_state, *buffer_node, loc.Get(), vuids->protected_command_buffer_02712, |
| " (Buffer is in a descriptorSet)"); |
| } |
| } |
| return skip; |
| } |
| |
| static const char *SuggestImageViewType(spv::Dim dim, bool is_image_array) { |
| VkImageViewType suggest = VK_IMAGE_VIEW_TYPE_MAX_ENUM; |
| if (dim == spv::Dim1D) { |
| suggest = is_image_array ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D; |
| } else if (dim == spv::Dim2D) { |
| suggest = is_image_array ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; |
| } else if (dim == spv::Dim3D) { |
| suggest = VK_IMAGE_VIEW_TYPE_3D; |
| } else if (dim == spv::DimCube) { |
| suggest = is_image_array ? VK_IMAGE_VIEW_TYPE_CUBE_ARRAY : VK_IMAGE_VIEW_TYPE_CUBE; |
| } else { |
| return "(No matching VkImageViewTyp)"; |
| } |
| |
| return string_VkImageViewType(suggest); |
| } |
| |
| // 'index' is the index into the descriptor |
| bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, const uint32_t index, |
| VkDescriptorType descriptor_type, const ImageDescriptor &image_descriptor) const { |
| // We skip various parts of checks for core check to prevent false positive when we don't know the index |
| bool skip = false; |
| std::vector<const Sampler *> sampler_states; |
| const VkImageView image_view = image_descriptor.GetImageView(); |
| const ImageView *image_view_state = image_descriptor.GetImageViewState(); |
| |
| if (image_descriptor.GetClass() == DescriptorClass::ImageSampler) { |
| sampler_states.emplace_back(static_cast<const ImageSamplerDescriptor &>(image_descriptor).GetSamplerState()); |
| } else if (is_gpu_av) { |
| // TODO - This will skip for GPU-AV because we don't currently capture array of samplers with array of sampled images |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8922 |
| // To not give false positve, we will skip all Sampler related checks since sampler_states will be empty |
| } else { |
| if (index < resource_variable.samplers_used_by_image.size()) { |
| for (const auto &sampler_used : resource_variable.samplers_used_by_image[index]) { |
| const auto *descriptor = |
| descriptor_set.GetDescriptorFromBinding(sampler_used.sampler_slot.binding, sampler_used.sampler_index); |
| // TODO: This check _shouldn't_ be necessary due to the checks made in ResourceInterfaceVariable() in |
| // shader_validation.cpp. However, without this check some traces still crash. |
| // The issue is we set dynamic image index to zero in samplers_used_by_image so will fail GPU-AV case |
| if (descriptor && (descriptor->GetClass() == DescriptorClass::PlainSampler)) { |
| const auto *sampler_state = static_cast<const SamplerDescriptor *>(descriptor)->GetSamplerState(); |
| if (sampler_state) sampler_states.emplace_back(sampler_state); |
| } |
| } |
| } |
| } |
| |
| if ((!image_view_state && !dev_proxy.enabled_features.nullDescriptor) || (image_view_state && image_view_state->Destroyed())) { |
| // Image view must have been destroyed since initial update. Could potentially flag the descriptor |
| // as "invalid" (updated = false) at DestroyImageView() time and detect this error at bind time |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using imageView %s that is invalid or has been destroyed.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(image_view).c_str(), |
| DescribeInstruction().c_str()); |
| return skip; // if invalid, end early |
| } |
| |
| // ImageView could be null via nullDescriptor and accessing it is legal |
| if (image_view == VK_NULL_HANDLE) { |
| return skip; |
| } |
| if (!resource_variable.IsAccessed()) return skip; |
| |
| // If there is an non-null imageView, the image inside should be valid |
| const auto image_state = image_view_state->image_state.get(); |
| ASSERT_AND_RETURN_SKIP(image_state); |
| |
| const auto &image_view_ci = image_view_state->create_info; |
| |
| const spv::Dim dim = resource_variable.info.image_dim; |
| const bool is_image_array = resource_variable.info.is_image_array; |
| |
| // For Descriptor Indexing, we want the exact image access, not the combination of all accesses to the OpVariable |
| spirv::ImageInstruction image_insn = (is_gpu_av && original_spirv) |
| ? spirv::ImageInstruction(&(*original_spirv)[instruction_position_offset]) |
| : resource_variable.info.image_insn; |
| |
| // if combined sampler, this variable might not be a OpTypeImage |
| // SubpassData gets validated elsewhere |
| if (resource_variable.IsImage() && dim != spv::DimSubpassData) { |
| bool valid_dim = true; |
| // From vkspec.html#textures-operation-validation |
| switch (image_view_ci.viewType) { |
| case VK_IMAGE_VIEW_TYPE_1D: |
| valid_dim = (dim == spv::Dim1D) && !is_image_array; |
| break; |
| case VK_IMAGE_VIEW_TYPE_2D: |
| valid_dim = (dim == spv::Dim2D) && !is_image_array; |
| break; |
| case VK_IMAGE_VIEW_TYPE_3D: |
| valid_dim = (dim == spv::Dim3D) && !is_image_array; |
| break; |
| case VK_IMAGE_VIEW_TYPE_CUBE: |
| valid_dim = (dim == spv::DimCube) && !is_image_array; |
| break; |
| case VK_IMAGE_VIEW_TYPE_1D_ARRAY: |
| valid_dim = (dim == spv::Dim1D) && is_image_array; |
| break; |
| case VK_IMAGE_VIEW_TYPE_2D_ARRAY: |
| valid_dim = (dim == spv::Dim2D) && is_image_array; |
| break; |
| case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: |
| valid_dim = (dim == spv::DimCube) && is_image_array; |
| break; |
| default: |
| break; // incase a new VkImageViewType is added, let it be valid by default |
| } |
| if (!valid_dim) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError( |
| vuids->image_view_dim_07752, objlist, loc.Get(), |
| "the %s VkImageViewType is %s but the OpTypeImage has (Dim = %s) and (Arrayed = %" PRIu32 |
| ").\nEither fix in shader or update the VkImageViewType to %s%s%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| string_VkImageViewType(image_view_ci.viewType), string_SpvDim(dim), is_image_array, |
| SuggestImageViewType(dim, is_image_array), |
| (is_gpu_av && resource_variable.IsArray()) |
| ? "\nAdvice: The dimension is tied to the descriptor variable, so for descriptor indexing, you might need to " |
| "express two different arrays of different types that share the same descriptor binding." |
| : "", |
| DescribeInstruction().c_str()); |
| } |
| |
| const uint32_t view_numeric_type = spirv::GetFormatType(image_view_ci.format); |
| const uint32_t variable_numeric_type = resource_variable.info.numeric_type; |
| // When the image has a external format the views format must be VK_FORMAT_UNDEFINED and it is required to use a sampler |
| // Ycbcr conversion. Thus we can't extract any meaningful information from the format parameter. |
| if (image_view_ci.format != VK_FORMAT_UNDEFINED && ((variable_numeric_type & view_numeric_type) == 0)) { |
| const bool signed_override = ((variable_numeric_type & spirv::NumericTypeUint) && image_insn.is_sign_extended); |
| const bool unsigned_override = ((variable_numeric_type & spirv::NumericTypeSint) && image_insn.is_zero_extended); |
| if (!signed_override && !unsigned_override) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError(vuids->image_view_numeric_format_07753, objlist, loc.Get(), |
| "the %s requires %s component type, but bound descriptor format is %s.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| spirv::string_NumericType(variable_numeric_type), string_VkFormat(image_view_ci.format), |
| DescribeInstruction().c_str()); |
| } |
| } |
| |
| // Check for only_report_errors last (to save a nesting), this string building is expensive if being spammed constantly |
| if (image_view_ci.format != VK_FORMAT_UNDEFINED && resource_variable.info.vk_format != VK_FORMAT_UNDEFINED && |
| image_view_ci.format != resource_variable.info.vk_format && !dev_proxy.global_settings.only_report_errors) { |
| // This warning was added after being discussed in https://gitlab.khronos.org/vulkan/vulkan/-/issues/4128 |
| auto set = descriptor_set.Handle(); |
| const LogObjectList objlist(this->objlist, set, image_view); |
| std::ostringstream msg; |
| msg << "the " << DescribeDescriptor(resource_variable, index, descriptor_type) |
| << " is accessed by a OpTypeImage that has a Format operand " |
| << string_SpirvImageFormat(resource_variable.info.vk_format) << " (equivalent to " |
| << string_VkFormat(resource_variable.info.vk_format) << ") which doesn't match the " << FormatHandle(image_view) |
| << " format (" << string_VkFormat(image_view_ci.format) |
| << "). Any loads or stores with the variable will produce undefined values to the whole image (not just the texel " |
| "being accessed)."; |
| if (vkuFormatCompatibilityClass(image_view_ci.format) == |
| vkuFormatCompatibilityClass(resource_variable.info.vk_format)) { |
| msg << " While the formats are compatible, Storage Images must exactly match. Few ways to resolve this are\n"; |
| if (vkuFormatComponentCount(image_view_ci.format) == vkuFormatComponentCount(resource_variable.info.vk_format)) { |
| msg << "1. Set your ImageView to " << string_VkFormat(resource_variable.info.vk_format) |
| << " and swizzle the values in the shader to match the desired results.\n"; |
| } else { |
| const char *suggested_format = string_SpirvImageFormat(image_view_ci.format); |
| if (strncmp(suggested_format, "Unknown", 7) != 0) { |
| msg << "1. Change your shader to use " << suggested_format << " instead as that matches " |
| << string_VkFormat(image_view_ci.format) << "\n"; |
| } else { |
| msg << "1. Find an SPIR-V Image format that can be mapped to a desired VkImageView format " |
| "https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#spirvenv-image-formats\n"; |
| } |
| } |
| msg << "2. Use the Unknown format in your shader (will need the widely supported " |
| "shaderStorageImageWriteWithoutFormat feature)"; |
| } |
| msg << "\nSpec information at https://docs.vulkan.org/spec/latest/chapters/textures.html#textures-format-validation"; |
| msg << DescribeInstruction(); |
| skip |= LogUndefinedValue("Undefined-Value-StorageImage-FormatMismatch-ImageView", objlist, loc.Get(), "%s", |
| msg.str().c_str()); |
| } |
| |
| const bool image_format_width_64 = vkuFormatHasComponentSize(image_view_ci.format, 64); |
| if (image_format_width_64) { |
| if (resource_variable.info.bit_width != 64) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError(vuids->image_view_access_64_04470, objlist, loc.Get(), |
| "the %s has a 64-bit component ImageView format (%s) but the OpTypeImage's " |
| "Sampled Type has a width of %" PRIu32 ".%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| string_VkFormat(image_view_ci.format), resource_variable.info.bit_width, |
| DescribeInstruction().c_str()); |
| } else if (!dev_proxy.enabled_features.sparseImageInt64Atomics && image_state->sparse_residency) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, image_state->Handle()); |
| skip |= |
| LogError(vuids->image_view_sparse_64_04474, objlist, loc.Get(), |
| "the %s has a OpTypeImage's Sampled Type has a width of 64 backed by a sparse Image, but " |
| "sparseImageInt64Atomics is not enabled.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), DescribeInstruction().c_str()); |
| } |
| } else if (!image_format_width_64 && resource_variable.info.bit_width != 32) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= |
| LogError(vuids->image_view_access_32_04471, objlist, loc.Get(), |
| "the %s has a 32-bit component ImageView format (%s) but the OpTypeImage's " |
| "Sampled Type has a width of %" PRIu32 ".%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| string_VkFormat(image_view_ci.format), resource_variable.info.bit_width, DescribeInstruction().c_str()); |
| } |
| } |
| |
| if (!dev_proxy.disabled[image_layout_validation]) { |
| if (const auto image_layout_map = cb_state.GetImageLayoutMap(image_state->VkHandle())) { |
| auto describe_descriptor_callback = [this, &resource_variable, index, descriptor_type]() { |
| std::ostringstream ss; |
| ss << DescribeDescriptor(resource_variable, index, descriptor_type); |
| if (descriptor_set.IsPushDescriptor()) { |
| ss << " updated by vkCmdPushDescriptorSet"; |
| } |
| return ss.str(); |
| }; |
| const VkImageLayout image_layout = image_descriptor.GetImageLayout(); |
| const VkImageAspectFlags aspect_flags = image_view_state->create_info.subresourceRange.aspectMask; |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_state->Handle(), |
| VulkanTypedHandle(image_view, kVulkanObjectTypeImageView)); |
| |
| skip |= dev_proxy.ValidateDescriptorImageLayout(objlist, *image_state, aspect_flags, image_layout, *image_layout_map, |
| subresource_adapter::RangeGenerator(image_view_state->range_generator), |
| *vuids, describe_descriptor_callback); |
| } |
| } |
| |
| // Verify Sample counts |
| if (resource_variable.IsImage()) { |
| if (!resource_variable.info.is_multisampled && image_view_state->samples != VK_SAMPLE_COUNT_1_BIT) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError("VUID-RuntimeSpirv-samples-08725", objlist, loc.Get(), |
| "the %s has %s created with %s, but OpTypeImage has marked it as single-sampled.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_state->Handle()).c_str(), string_VkSampleCountFlagBits(image_view_state->samples), |
| DescribeInstruction().c_str()); |
| } else if (resource_variable.info.is_multisampled && image_view_state->samples == VK_SAMPLE_COUNT_1_BIT) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError("VUID-RuntimeSpirv-samples-08726", objlist, loc.Get(), |
| "the %s has %s created with VK_SAMPLE_COUNT_1_BIT, but OpTypeImage has marked it as multisampled.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_state->Handle()).c_str(), DescribeInstruction().c_str()); |
| } |
| } |
| |
| // Verify VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT |
| if (resource_variable.IsAtomic() && (descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) && |
| !(image_view_state->format_features & VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT)) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError(vuids->imageview_atomic_02691, objlist, loc.Get(), |
| "the %s has %s with format of %s which doesn't support VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT.\n" |
| "(supported features: %s).%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(image_view).c_str(), |
| string_VkFormat(image_view_ci.format), |
| string_VkFormatFeatureFlags2(image_view_state->format_features).c_str(), DescribeInstruction().c_str()); |
| } |
| |
| // When KHR_format_feature_flags2 is supported, the read/write without |
| // format support is reported per format rather than a single physical |
| // device feature. |
| if (dev_proxy.device_state->special_supported.vk_khr_format_feature_flags2) { |
| const VkFormatFeatureFlags2 format_features = image_view_state->format_features; |
| |
| if (descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) { |
| if ((resource_variable.info.is_read_without_format) && |
| !(format_features & VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT)) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError(vuids->storage_image_read_without_format_07028, objlist, loc.Get(), |
| "the %s has %s with format of %s which doesn't support " |
| "VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT.\n" |
| "(supported features: %s).%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), string_VkFormat(image_view_ci.format), |
| string_VkFormatFeatureFlags2(format_features).c_str(), DescribeInstruction().c_str()); |
| } else if ((resource_variable.info.is_write_without_format) && |
| !(format_features & VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT)) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError(vuids->storage_image_write_without_format_07027, objlist, loc.Get(), |
| "the %s has %s with format of %s which doesn't support " |
| "VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT.\n" |
| "(supported features: %s).%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), string_VkFormat(image_view_ci.format), |
| string_VkFormatFeatureFlags2(format_features).c_str(), DescribeInstruction().c_str()); |
| } |
| } |
| |
| if ((image_insn.is_dref) && !(format_features & VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT)) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError(vuids->depth_compare_sample_06479, objlist, loc.Get(), |
| "the %s has %s with format of %s which doesn't support " |
| "VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT.\n" |
| "(supported features: %s).%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), string_VkFormat(image_view_ci.format), |
| string_VkFormatFeatureFlags2(format_features).c_str(), DescribeInstruction().c_str()); |
| } |
| } |
| |
| const uint32_t binding_index = resource_variable.decorations.binding; |
| // Verify if attachments are used in DescriptorSet |
| if (!cb_state.active_attachments.empty() && !cb_state.active_subpasses.empty() && |
| (descriptor_type != VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT)) { |
| for (uint32_t att_index = 0; att_index < cb_state.active_attachments.size(); ++att_index) { |
| const auto &attachment_info = cb_state.active_attachments[att_index]; |
| const auto *view_state = attachment_info.image_view; |
| if (!view_state || view_state->Destroyed()) { |
| continue; |
| } |
| const bool same_view = view_state->VkHandle() == image_view; |
| const bool overlapping_view = image_view_state->OverlapSubresource(*view_state); |
| if (!same_view && !overlapping_view) { |
| continue; |
| } |
| |
| bool descriptor_written_to = false; |
| const auto pipeline = cb_state.GetLastBoundGraphics().pipeline_state; |
| for (const auto &stage : pipeline->stage_states) { |
| if (!stage.entrypoint) continue; |
| for (const auto &interface_variable : stage.entrypoint->resource_interface_variables) { |
| if (interface_variable.decorations.set == set_index && |
| interface_variable.decorations.binding == binding_index) { |
| descriptor_written_to |= interface_variable.IsWrittenTo(); |
| break; // only one set/binding will match |
| } |
| } |
| } |
| |
| const SubpassInfo &subpass = cb_state.active_subpasses[att_index]; |
| const bool layout_read_only = IsImageLayoutReadOnly(attachment_info.layout); |
| const bool read_attachment = (subpass.usage & (VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT)) != 0; |
| if (read_attachment && descriptor_written_to) { |
| const char* vuid = attachment_info.IsDepth() ? vuids->image_subresources_subpass_depth_12339 |
| : attachment_info.IsStencil() ? vuids->image_subresources_subpass_stencil_12340 |
| : vuids->image_subresources_subpass_color_12338; |
| if (same_view) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, framebuffer); |
| skip |= LogError( |
| vuid, objlist, loc.Get(), "the %s has %s which will be read from as %s attachment %" PRIu32 ".%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(image_view).c_str(), |
| FormatHandle(framebuffer).c_str(), att_index, DescribeInstruction().c_str()); |
| } else if (overlapping_view) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, framebuffer, |
| view_state->Handle()); |
| skip |= LogError(vuid, objlist, loc.Get(), |
| "the %s has %s which will be overlap read from as %s in %s attachment %" PRIu32 " overlap.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), FormatHandle(view_state->Handle()).c_str(), |
| FormatHandle(framebuffer).c_str(), att_index, DescribeInstruction().c_str()); |
| } |
| } |
| |
| if (descriptor_written_to && !layout_read_only) { |
| if (same_view) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, framebuffer); |
| skip |= LogError(vuids->image_subresources_render_pass_write_06537, objlist, loc.Get(), |
| "the %s has %s which is written to but is also %s attachment %" PRIu32 ".%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), FormatHandle(framebuffer).c_str(), att_index, |
| DescribeInstruction().c_str()); |
| } else if (overlapping_view) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, framebuffer, |
| view_state->Handle()); |
| skip |= LogError(vuids->image_subresources_render_pass_write_06537, objlist, loc.Get(), |
| "the %s has %s which overlaps writes to %s but is also %s attachment %" PRIu32 ".%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), FormatHandle(view_state->Handle()).c_str(), |
| FormatHandle(framebuffer).c_str(), att_index, DescribeInstruction().c_str()); |
| } |
| } |
| } |
| } |
| |
| if (dev_proxy.enabled_features.protectedMemory == VK_TRUE) { |
| skip |= dev_proxy.ValidateProtectedImage(cb_state, *image_state, loc.Get(), vuids->unprotected_command_buffer_02707, |
| " (Image is in a descriptorSet)"); |
| if (resource_variable.IsWrittenTo()) { |
| skip |= dev_proxy.ValidateUnprotectedImage(cb_state, *image_state, loc.Get(), vuids->protected_command_buffer_02712, |
| " (Image is in a descriptorSet)"); |
| } |
| } |
| |
| if (dev_proxy.enabled_features.tileMemoryHeap) { |
| skip |= dev_proxy.ValidateBoundTileMemory(*image_view_state->image_state, cb_state, *vuids); |
| } |
| |
| // If the Image View is invalid, the combined sampler mayb have the same issue |
| if (skip) return skip; |
| |
| const VkFormat image_view_format = image_view_state->create_info.format; |
| for (const auto *sampler_state : sampler_states) { |
| if (!sampler_state || sampler_state->Destroyed()) { |
| continue; |
| } |
| |
| // TODO: Validate 04015 for DescriptorClass::PlainSampler |
| if ((sampler_state->create_info.borderColor == VK_BORDER_COLOR_INT_CUSTOM_EXT || |
| sampler_state->create_info.borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT) && |
| (sampler_state->customCreateInfo.format == VK_FORMAT_UNDEFINED)) { |
| if (image_view_format == VK_FORMAT_B4G4R4A4_UNORM_PACK16 || image_view_format == VK_FORMAT_B5G6R5_UNORM_PACK16 || |
| image_view_format == VK_FORMAT_B5G5R5A1_UNORM_PACK16 || image_view_format == VK_FORMAT_A1B5G5R5_UNORM_PACK16) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), |
| image_view_state->Handle()); |
| skip |= LogError("VUID-VkSamplerCustomBorderColorCreateInfoEXT-format-04015", objlist, loc.Get(), |
| "the %s has %s which has a custom border color with format = " |
| "VK_FORMAT_UNDEFINED and is used to sample an image " |
| "view %s with format %s.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), FormatHandle(image_view_state->Handle()).c_str(), |
| string_VkFormat(image_view_format), DescribeInstruction().c_str()); |
| } |
| } |
| const VkFilter sampler_mag_filter = sampler_state->create_info.magFilter; |
| const VkFilter sampler_min_filter = sampler_state->create_info.minFilter; |
| const bool sampler_compare_enable = sampler_state->create_info.compareEnable == VK_TRUE; |
| const auto sampler_reduction = |
| vku::FindStructInPNextChain<VkSamplerReductionModeCreateInfo>(sampler_state->create_info.pNext); |
| // The VU is wording is a bit misleading, if there is no VkSamplerReductionModeCreateInfo we still need to check for linear |
| // tiling feature |
| const bool is_weighted_average = |
| !sampler_reduction || sampler_reduction->reductionMode == VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE; |
| if (!sampler_compare_enable && is_weighted_average && |
| !(image_view_state->format_features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { |
| if (sampler_mag_filter == VK_FILTER_LINEAR || sampler_min_filter == VK_FILTER_LINEAR) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), |
| image_view_state->Handle()); |
| skip |= LogError(vuids->linear_filter_sampler_04553, objlist, loc.Get(), |
| "the %s has %s which is set to use VK_FILTER_LINEAR with compareEnable is set " |
| "to VK_FALSE, but image view's (%s) format (%s) does not contain " |
| "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT in its format features.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), FormatHandle(image_view_state->Handle()).c_str(), |
| string_VkFormat(image_view_format), DescribeInstruction().c_str()); |
| } else if (sampler_state->create_info.mipmapMode == VK_SAMPLER_MIPMAP_MODE_LINEAR) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), |
| image_view_state->Handle()); |
| skip |= LogError(vuids->linear_mipmap_sampler_04770, objlist, loc.Get(), |
| "the %s has %s which is set to use VK_SAMPLER_MIPMAP_MODE_LINEAR with " |
| "compareEnable is set to VK_FALSE, but image view's (%s) format (%s) does not contain " |
| "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT in its format features.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), FormatHandle(image_view_state->Handle()).c_str(), |
| string_VkFormat(image_view_format), DescribeInstruction().c_str()); |
| } |
| } |
| |
| const bool is_minmax = sampler_reduction && (sampler_reduction->reductionMode == VK_SAMPLER_REDUCTION_MODE_MIN || |
| sampler_reduction->reductionMode == VK_SAMPLER_REDUCTION_MODE_MAX); |
| if (is_minmax && !(image_view_state->format_features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT)) { |
| if (sampler_mag_filter == VK_FILTER_LINEAR || sampler_min_filter == VK_FILTER_LINEAR) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), |
| image_view_state->Handle()); |
| skip |= LogError(vuids->linear_filter_sampler_09598, objlist, loc.Get(), |
| "the %s has %s which is set to use VK_FILTER_LINEAR with reductionMode is set " |
| "to %s, but image view's (%s) format (%s) does not contain " |
| "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT in its format features.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), |
| string_VkSamplerReductionMode(sampler_reduction->reductionMode), |
| FormatHandle(image_view_state->Handle()).c_str(), string_VkFormat(image_view_format), |
| DescribeInstruction().c_str()); |
| } else if (sampler_state->create_info.mipmapMode == VK_SAMPLER_MIPMAP_MODE_LINEAR) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), |
| image_view_state->Handle()); |
| skip |= LogError(vuids->linear_mipmap_sampler_09599, objlist, loc.Get(), |
| "the %s has %s which is set to use VK_SAMPLER_MIPMAP_MODE_LINEAR with " |
| "reductionMode is set to %s, but image view's (%s) format (%s) does not contain " |
| "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT in its format features.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), |
| string_VkSamplerReductionMode(sampler_reduction->reductionMode), |
| FormatHandle(image_view_state->Handle()).c_str(), string_VkFormat(image_view_format), |
| DescribeInstruction().c_str()); |
| } |
| } |
| |
| if (sampler_mag_filter == VK_FILTER_CUBIC_EXT || sampler_min_filter == VK_FILTER_CUBIC_EXT) { |
| if (!(image_view_state->format_features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_EXT)) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), |
| image_view_state->Handle()); |
| skip |= LogError(vuids->cubic_sampler_02692, objlist, loc.Get(), |
| "the %s has %s which is set to use VK_FILTER_CUBIC_EXT, then image view's (%s) format (%s) " |
| "MUST contain VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_EXT in its format features.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), FormatHandle(image_view_state->Handle()).c_str(), |
| string_VkFormat(image_view_state->create_info.format), DescribeInstruction().c_str()); |
| } |
| |
| if (IsExtEnabled(dev_proxy.extensions.vk_ext_filter_cubic)) { |
| const auto reduction_mode_info = |
| vku::FindStructInPNextChain<VkSamplerReductionModeCreateInfo>(sampler_state->create_info.pNext); |
| if (reduction_mode_info && |
| (reduction_mode_info->reductionMode == VK_SAMPLER_REDUCTION_MODE_MIN || |
| reduction_mode_info->reductionMode == VK_SAMPLER_REDUCTION_MODE_MAX) && |
| !image_view_state->filter_cubic_props.filterCubicMinmax) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), |
| image_view_state->Handle()); |
| skip |= LogError(vuids->filter_cubic_min_max_02695, objlist, loc.Get(), |
| "the %s has %s which is set to use VK_FILTER_CUBIC_EXT & %s, but image view " |
| "(%s) doesn't support filterCubicMinmax.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), |
| string_VkSamplerReductionMode(reduction_mode_info->reductionMode), |
| FormatHandle(image_view_state->Handle()).c_str(), DescribeInstruction().c_str()); |
| } else if (!image_view_state->filter_cubic_props.filterCubic) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), |
| image_view_state->Handle()); |
| skip |= LogError(vuids->filter_cubic_02694, objlist, loc.Get(), |
| "the %s has %s which is set to use VK_FILTER_CUBIC_EXT, but image view (%s) " |
| "doesn't support filterCubic.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), |
| FormatHandle(image_view_state->Handle()).c_str(), DescribeInstruction().c_str()); |
| } |
| } |
| |
| if (IsExtEnabled(dev_proxy.extensions.vk_img_filter_cubic)) { |
| if (image_view_state->create_info.viewType == VK_IMAGE_VIEW_TYPE_3D || |
| image_view_state->create_info.viewType == VK_IMAGE_VIEW_TYPE_CUBE || |
| image_view_state->create_info.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), |
| image_view_state->Handle()); |
| skip |= |
| LogError(vuids->img_filter_cubic_02693, objlist, loc.Get(), |
| "the %s has %s which is set to use VK_FILTER_CUBIC_EXT while the VK_IMG_filter_cubic " |
| "extension is enabled, but image view (%s) has an invalid imageViewType (%s).%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), FormatHandle(image_view_state->Handle()).c_str(), |
| string_VkImageViewType(image_view_state->create_info.viewType), DescribeInstruction().c_str()); |
| } |
| } |
| } |
| if ((image_state->create_info.flags & VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV) && |
| (sampler_state->create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE || |
| sampler_state->create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE || |
| sampler_state->create_info.addressModeW != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)) { |
| std::string address_mode_letter = |
| (sampler_state->create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) ? "U" |
| : (sampler_state->create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) ? "V" |
| : "W"; |
| VkSamplerAddressMode address_mode = (sampler_state->create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) |
| ? sampler_state->create_info.addressModeU |
| : (sampler_state->create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) |
| ? sampler_state->create_info.addressModeV |
| : sampler_state->create_info.addressModeW; |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state->Handle(), image_state->Handle(), |
| image_view_state->Handle()); |
| skip |= LogError(vuids->corner_sampled_address_mode_02696, objlist, loc.Get(), |
| "the %s image (%s) in image view (%s) is created with flag " |
| "VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV and can only be sampled using " |
| "VK_SAMPLER_ADDRESS_MODE_CLAMP_EDGE, but sampler (%s) has " |
| "pCreateInfo->addressMode%s set to %s.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_state->Handle()).c_str(), FormatHandle(image_view_state->Handle()).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), address_mode_letter.c_str(), |
| string_VkSamplerAddressMode(address_mode), DescribeInstruction().c_str()); |
| } |
| |
| // UnnormalizedCoordinates sampler validations |
| // only check if sampled as could have a texelFetch on a combined image sampler |
| if (sampler_state->create_info.unnormalizedCoordinates && image_insn.is_sampler_sampled) { |
| const auto &subresource_range = image_view_state->normalized_subresource_range; |
| |
| // If ImageView is used by a unnormalizedCoordinates sampler, it needs to check ImageView type |
| if (image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_3D || image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_CUBE || |
| image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY || image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY || |
| image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, sampler_state->Handle()); |
| skip |= LogError(vuids->sampler_imageview_type_08609, objlist, loc.Get(), |
| "the %s (%s) was created with %s, but %s was created with unnormalizedCoordinates.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), string_VkImageViewType(image_view_ci.viewType), |
| FormatHandle(sampler_state->Handle()).c_str(), DescribeInstruction().c_str()); |
| } else if (subresource_range.levelCount != 1) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, sampler_state->Handle()); |
| skip |= LogError(vuids->unnormalized_coordinates_09635, objlist, loc.Get(), |
| "the %s (%s) was created with levelCount of %s, but %s was created with " |
| "unnormalizedCoordinates.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), |
| string_LevelCount(image_state->create_info, image_view_ci.subresourceRange).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), DescribeInstruction().c_str()); |
| } else if (subresource_range.layerCount != 1) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, sampler_state->Handle()); |
| skip |= LogError(vuids->unnormalized_coordinates_09635, objlist, loc.Get(), |
| "the %s (%s) was created with layerCount of %s, but %s was created with " |
| "unnormalizedCoordinates.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), |
| string_LayerCount(image_state->create_info, image_view_ci.subresourceRange).c_str(), |
| FormatHandle(sampler_state->Handle()).c_str(), DescribeInstruction().c_str()); |
| } else if (image_insn.is_sampler_implicitLod_dref_proj) { |
| // sampler must not be used with any of the SPIR-V OpImageSample* or OpImageSparseSample* |
| // instructions with ImplicitLod, Dref or Proj in their name |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, sampler_state->Handle()); |
| skip |= LogError(vuids->sampler_implicitLod_dref_proj_08610, objlist, loc.Get(), |
| "the %s (%s) is used by %s that uses invalid operator.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), FormatHandle(sampler_state->Handle()).c_str(), |
| DescribeInstruction().c_str()); |
| } else if (image_insn.is_sampler_bias_offset) { |
| // sampler must not be used with any of the SPIR-V OpImageSample* or OpImageSparseSample* |
| // instructions that includes a LOD bias or any offset values |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view, sampler_state->Handle()); |
| skip |= LogError(vuids->sampler_bias_offset_08611, objlist, loc.Get(), |
| "the %s (%s) is used by %s that uses invalid bias or offset operator.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), FormatHandle(sampler_state->Handle()).c_str(), |
| DescribeInstruction().c_str()); |
| } |
| } |
| } |
| |
| for (const uint32_t texel_component_count : resource_variable.write_without_formats_component_count_list) { |
| const uint32_t format_component_count = vkuFormatComponentCount(image_view_format); |
| if (image_view_format == VK_FORMAT_A8_UNORM) { |
| if (texel_component_count != 4) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError(vuids->storage_image_write_texel_count_08796, objlist, loc.Get(), |
| "the %s (%s) is mapped to a OpImage format of VK_FORMAT_A8_UNORM, " |
| "but the OpImageWrite Texel " |
| "operand only contains %" PRIu32 " components.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), texel_component_count, DescribeInstruction().c_str()); |
| } |
| } else if (texel_component_count < format_component_count) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view); |
| skip |= LogError(vuids->storage_image_write_texel_count_08795, objlist, loc.Get(), |
| "the %s (%s) is mapped to a OpImage format of %s which has %" PRIu32 |
| " components, but the OpImageWrite Texel operand only contains %" PRIu32 " components.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(image_view).c_str(), string_VkFormat(image_view_format), format_component_count, |
| texel_component_count, DescribeInstruction().c_str()); |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, const uint32_t index, |
| VkDescriptorType descriptor_type, const ImageSamplerDescriptor &descriptor) const { |
| bool skip = false; |
| skip |= ValidateDescriptor(resource_variable, index, descriptor_type, static_cast<const ImageDescriptor &>(descriptor)); |
| if (skip) { |
| return skip; |
| } |
| skip |= ValidateSamplerDescriptor(resource_variable, index, descriptor.GetSampler(), descriptor.IsImmutableSampler(), |
| descriptor.GetSamplerState()); |
| return skip; |
| } |
| |
| bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, const uint32_t index, |
| VkDescriptorType descriptor_type, const TexelDescriptor &texel_descriptor) const { |
| bool skip = false; |
| const VkBufferView buffer_view = texel_descriptor.GetBufferView(); |
| auto buffer_view_state = texel_descriptor.GetBufferViewState(); |
| if ((!buffer_view_state && !dev_proxy.enabled_features.nullDescriptor) || |
| (buffer_view_state && buffer_view_state->Destroyed())) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using bufferView %s that is invalid or has been destroyed.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer_view).c_str(), |
| DescribeInstruction().c_str()); |
| return skip; // early return if invalid |
| } |
| |
| // BufferView could be null via nullDescriptor and accessing it is legal |
| if (buffer_view == VK_NULL_HANDLE) { |
| return skip; |
| } |
| if (!resource_variable.IsAccessed()) return skip; |
| |
| auto buffer = buffer_view_state->create_info.buffer; |
| const auto *buffer_state = buffer_view_state->buffer_state.get(); |
| if (!buffer_state || buffer_state->Destroyed()) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using buffer %s that has been destroyed.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer).c_str(), |
| DescribeInstruction().c_str()); |
| return skip; // early return if invalid |
| } |
| |
| const VkFormat buffer_view_format = buffer_view_state->create_info.format; |
| const uint32_t view_numeric_type = spirv::GetFormatType(buffer_view_format); |
| const uint32_t variable_numeric_type = resource_variable.info.numeric_type; |
| if ((variable_numeric_type & view_numeric_type) == 0) { |
| spirv::ImageInstruction image_insn = (is_gpu_av && original_spirv) |
| ? spirv::ImageInstruction(&(*original_spirv)[instruction_position_offset]) |
| : resource_variable.info.image_insn; |
| |
| const bool signed_override = ((variable_numeric_type & spirv::NumericTypeUint) && image_insn.is_sign_extended); |
| const bool unsigned_override = ((variable_numeric_type & spirv::NumericTypeSint) && image_insn.is_zero_extended); |
| if (!signed_override && !unsigned_override) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view); |
| skip |= LogError(vuids->image_view_numeric_format_07753, objlist, loc.Get(), |
| "the %s requires %s component type, but bound descriptor format is %s.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| spirv::string_NumericType(variable_numeric_type), string_VkFormat(buffer_view_format), |
| DescribeInstruction().c_str()); |
| } |
| } |
| |
| // Check for only_report_errors last (to save a nesting), this string building is expensive if being spammed constantly |
| if (buffer_view_format != VK_FORMAT_UNDEFINED && resource_variable.info.vk_format != VK_FORMAT_UNDEFINED && |
| buffer_view_format != resource_variable.info.vk_format && !dev_proxy.global_settings.only_report_errors) { |
| // This warning was added after being discussed in https://gitlab.khronos.org/vulkan/vulkan/-/issues/4128 |
| auto set = descriptor_set.Handle(); |
| const LogObjectList objlist(this->objlist, set, buffer_view); |
| std::ostringstream msg; |
| msg << "the " << DescribeDescriptor(resource_variable, index, descriptor_type) |
| << " is accessed by a OpTypeImage that has a Format operand " |
| << string_SpirvImageFormat(resource_variable.info.vk_format) << " (equivalent to " |
| << string_VkFormat(resource_variable.info.vk_format) << ") which doesn't match the " << FormatHandle(buffer_view) |
| << " format (" << string_VkFormat(buffer_view_format) |
| << "). Any loads or stores with the variable will produce undefined values to the whole image (not just the texel " |
| "being accessed)."; |
| if (vkuFormatCompatibilityClass(buffer_view_format) == vkuFormatCompatibilityClass(resource_variable.info.vk_format)) { |
| msg << " While the formats are compatible, Texel Buffers must exactly match. Few ways to resolve this are\n"; |
| if (vkuFormatComponentCount(buffer_view_format) == vkuFormatComponentCount(resource_variable.info.vk_format)) { |
| msg << "1. Set your BuffereView to " << string_VkFormat(resource_variable.info.vk_format) |
| << " and swizzle the values in the shader to match the desired results.\n"; |
| } else { |
| const char *suggested_format = string_SpirvImageFormat(buffer_view_format); |
| if (strncmp(suggested_format, "Unknown", 7) != 0) { |
| msg << "1. Change your shader to use " << suggested_format << " instead as that matches " |
| << string_VkFormat(buffer_view_format) << "\n"; |
| } else { |
| msg << "1. Find an SPIR-V Image format that can be mapped to a desired VkBuffereView format " |
| "https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#spirvenv-image-formats\n"; |
| } |
| } |
| msg << "2. Use the Unknown format in your shader"; |
| } |
| msg << "\nSpec information at https://docs.vulkan.org/spec/latest/chapters/textures.html#textures-format-validation"; |
| msg << DescribeInstruction(); |
| skip |= LogUndefinedValue("Undefined-Value-StorageImage-FormatMismatch-BufferView", objlist, loc.Get(), "%s", |
| msg.str().c_str()); |
| } |
| |
| const bool buffer_format_width_64 = vkuFormatHasComponentSize(buffer_view_format, 64); |
| if (buffer_format_width_64 && resource_variable.info.bit_width != 64) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view); |
| skip |= LogError(vuids->buffer_view_access_64_04472, objlist, loc.Get(), |
| "the %s has a 64-bit component BufferView format (%s) but the OpTypeImage's Sampled " |
| "Type has a width of %" PRIu32 ".%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), string_VkFormat(buffer_view_format), |
| resource_variable.info.bit_width, DescribeInstruction().c_str()); |
| } else if (!buffer_format_width_64 && resource_variable.info.bit_width != 32) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view); |
| skip |= LogError(vuids->buffer_view_access_32_04473, objlist, loc.Get(), |
| "the %s has a 32-bit component BufferView format (%s) but the OpTypeImage's Sampled " |
| "Type has a width of %" PRIu32 ".%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), string_VkFormat(buffer_view_format), |
| resource_variable.info.bit_width, DescribeInstruction().c_str()); |
| } |
| |
| const VkFormatFeatureFlags2 buffer_format_features = buffer_view_state->buffer_format_features; |
| |
| // Verify VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT |
| if ((resource_variable.IsAtomic()) && (descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) && |
| !(buffer_format_features & VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT)) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view); |
| skip |= LogError(vuids->bufferview_atomic_07888, objlist, loc.Get(), |
| "the %s has %s with format of %s which doesn't support VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT.\n" |
| "(supported features: %s).%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer_view).c_str(), |
| string_VkFormat(buffer_view_format), string_VkFormatFeatureFlags2(buffer_format_features).c_str(), |
| DescribeInstruction().c_str()); |
| } |
| |
| // When KHR_format_feature_flags2 is supported, the read/write without |
| // format support is reported per format rather than a single physical |
| // device feature. |
| if (dev_proxy.device_state->special_supported.vk_khr_format_feature_flags2) { |
| if (descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) { |
| if ((resource_variable.info.is_read_without_format) && |
| !(buffer_format_features & VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT_KHR)) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view); |
| skip |= LogError(vuids->storage_texel_buffer_read_without_format_07030, objlist, loc.Get(), |
| "the %s has %s with format of %s which doesn't support " |
| "VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT_KHR.\n" |
| "(supported features: %s).%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(buffer_view).c_str(), string_VkFormat(buffer_view_format), |
| string_VkFormatFeatureFlags2(buffer_format_features).c_str(), DescribeInstruction().c_str()); |
| } else if ((resource_variable.info.is_write_without_format) && |
| !(buffer_format_features & VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT)) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view); |
| skip |= LogError(vuids->storage_texel_buffer_write_without_format_07029, objlist, loc.Get(), |
| "the %s has %s with format of %s which doesn't support " |
| "VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT.\n" |
| "(supported features: %s).%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(buffer_view).c_str(), string_VkFormat(buffer_view_format), |
| string_VkFormatFeatureFlags2(buffer_format_features).c_str(), DescribeInstruction().c_str()); |
| } |
| } |
| } |
| |
| if (dev_proxy.enabled_features.protectedMemory == VK_TRUE && buffer_view_state->buffer_state) { |
| skip |= dev_proxy.ValidateProtectedBuffer(cb_state, *buffer_view_state->buffer_state, loc.Get(), |
| vuids->unprotected_command_buffer_02707, " (Buffer is in a descriptorSet)"); |
| if (resource_variable.IsWrittenTo()) { |
| skip |= dev_proxy.ValidateUnprotectedBuffer(cb_state, *buffer_view_state->buffer_state, loc.Get(), |
| vuids->protected_command_buffer_02712, " (Buffer is in a descriptorSet)"); |
| } |
| } |
| |
| for (const uint32_t texel_component_count : resource_variable.write_without_formats_component_count_list) { |
| const uint32_t format_component_count = vkuFormatComponentCount(buffer_view_format); |
| if (texel_component_count < format_component_count) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view); |
| skip |= LogError(vuids->storage_texel_buffer_write_texel_count_04469, objlist, loc.Get(), |
| "the %s (%s) is mapped to a OpImage format of %s which has %" PRIu32 |
| " components, but the OpImageWrite Texel operand only contains %" PRIu32 " components.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(buffer_view).c_str(), string_VkFormat(buffer_view_format), format_component_count, |
| texel_component_count, DescribeInstruction().c_str()); |
| } |
| } |
| |
| if (dev_proxy.enabled_features.tileMemoryHeap) { |
| skip |= dev_proxy.ValidateBoundTileMemory(*buffer_view_state->buffer_state, cb_state, *vuids); |
| } |
| |
| return skip; |
| } |
| |
| bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, const uint32_t index, |
| VkDescriptorType descriptor_type, |
| const AccelerationStructureDescriptor &descriptor) const { |
| bool skip = false; |
| // Verify that acceleration structures are valid |
| if (descriptor.IsKHR()) { |
| auto acc = descriptor.GetAccelerationStructure(); |
| auto acc_node = descriptor.GetAccelerationStructureStateKHR(); |
| if (!acc_node || acc_node->Destroyed()) { |
| // the AccelerationStructure could be null via nullDescriptor and accessing it is legal |
| if (acc != VK_NULL_HANDLE || !dev_proxy.enabled_features.nullDescriptor) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using acceleration structure %s that is invalid or has been destroyed.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(acc).c_str(), |
| DescribeInstruction().c_str()); |
| } |
| } else if (acc_node->buffer_state) { |
| for (const auto &mem_binding : acc_node->buffer_state->GetInvalidMemory()) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using acceleration structure %s that references invalid memory %s.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(acc).c_str(), |
| FormatHandle(mem_binding->Handle()).c_str(), DescribeInstruction().c_str()); |
| } |
| } |
| } else { |
| auto acc = descriptor.GetAccelerationStructureNV(); |
| auto acc_node = descriptor.GetAccelerationStructureStateNV(); |
| if (!acc_node || acc_node->Destroyed()) { |
| // the AccelerationStructure could be null via nullDescriptor and accessing it is legal |
| if (acc != VK_NULL_HANDLE || !dev_proxy.enabled_features.nullDescriptor) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using acceleration structure %s that is invalid or has been destroyed.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(acc).c_str(), |
| DescribeInstruction().c_str()); |
| } |
| } else { |
| for (const auto &mem_binding : acc_node->GetInvalidMemory()) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using acceleration structure %s that references invalid memory %s.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(acc).c_str(), |
| FormatHandle(mem_binding->Handle()).c_str(), DescribeInstruction().c_str()); |
| } |
| } |
| } |
| return skip; |
| } |
| |
| // If the validation is related to both of image and sampler, |
| // please leave it in (descriptor_class == DescriptorClass::ImageSampler || descriptor_class == |
| // DescriptorClass::Image) Here is to validate for only sampler. |
| bool DescriptorValidator::ValidateSamplerDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, uint32_t index, |
| VkSampler sampler, bool is_immutable, const Sampler *sampler_state) const { |
| bool skip = false; |
| |
| // maintenance4 specifies that pipeline layout and its children (set layout, immutable samplers) |
| // are not accessed after they were used to create another object and can be destroyed. |
| const bool can_be_destroyed = is_immutable && dev_proxy.enabled_features.maintenance4; |
| |
| // Verify Sampler still valid |
| if (!sampler_state || (sampler_state->Destroyed() && !can_be_destroyed)) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using sampler %s that is invalid or has been destroyed.%s", |
| DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_SAMPLER).c_str(), |
| FormatHandle(sampler).c_str(), DescribeInstruction().c_str()); |
| } else if (sampler_state->sampler_conversion && !is_immutable) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s sampler (%s) contains a YCBCR conversion (%s), but the sampler is not an " |
| "immutable sampler.%s", |
| DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_SAMPLER).c_str(), |
| FormatHandle(sampler).c_str(), FormatHandle(sampler_state->sampler_conversion).c_str(), |
| DescribeInstruction().c_str()); |
| } |
| return skip; |
| } |
| |
| bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, const uint32_t index, |
| VkDescriptorType descriptor_type, const SamplerDescriptor &descriptor) const { |
| return ValidateSamplerDescriptor(resource_variable, index, descriptor.GetSampler(), descriptor.IsImmutableSampler(), |
| descriptor.GetSamplerState()); |
| } |
| |
| bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, uint32_t index, |
| VkDescriptorType descriptor_type, const vvl::TensorDescriptor &descriptor) const { |
| bool skip = false; |
| |
| const vvl::TensorView *tensor_view_state = descriptor.GetTensorViewState(); |
| ASSERT_AND_RETURN_SKIP(tensor_view_state); |
| if (tensor_view_state->Destroyed()) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using tensor view %s that is invalid or has been destroyed.%s", |
| DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_TENSOR_ARM).c_str(), |
| FormatHandle(tensor_view_state->Handle()).c_str(), DescribeInstruction().c_str()); |
| return skip; |
| } |
| const vvl::Tensor *tensor_state = descriptor.GetTensorState(); |
| ASSERT_AND_RETURN_SKIP(tensor_state); |
| if (tensor_state->Destroyed()) { |
| const LogObjectList objlist(this->objlist, descriptor_set.Handle()); |
| skip |= LogError(vuids->descriptor_buffer_bit_set_08114, objlist, loc.Get(), |
| "the %s is using tensor %s that is invalid or has been destroyed.%s", |
| DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_TENSOR_ARM).c_str(), |
| FormatHandle(tensor_state->Handle()).c_str(), DescribeInstruction().c_str()); |
| return skip; |
| } |
| |
| if (tensor_state->unprotected) { |
| skip |= dev_proxy.ValidateUnprotectedTensor(cb_state, *tensor_state, loc.Get(), vuids->protected_command_buffer_02712); |
| } else { |
| skip |= dev_proxy.ValidateProtectedTensor(cb_state, *tensor_state, loc.Get(), vuids->unprotected_command_buffer_02707); |
| } |
| |
| // These VUs are only for tensors used in _shaders_. For use in _datagraphs_, VU 9923 applies. |
| // https://gitlab.khronos.org/vulkan/vulkan/-/issues/4482#note_564658 |
| if (loc.Get().function != Func::vkCmdDispatchDataGraphARM) { |
| if (resource_variable.info.tensor_rank != tensor_state->create_info.pDescription->dimensionCount) { |
| const LogObjectList objlist(cb_state.Handle(), this->objlist, descriptor_set.Handle(), tensor_state->Handle()); |
| skip |= LogError( |
| vuids->tensorARM_dimensionCount_09905, objlist, loc.Get(), |
| "the %s is using tensor %s created with dimensionCount %" PRIu32 ", but the corresponding OpTypeTensorARM has rank %" PRIu32 ".%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(tensor_state->Handle()).c_str(), |
| tensor_state->create_info.pDescription->dimensionCount, resource_variable.info.tensor_rank, DescribeInstruction().c_str()); |
| } |
| if (resource_variable.info.vk_format != tensor_view_state->create_info.format) { |
| const LogObjectList objlist(cb_state.Handle(), this->objlist, descriptor_set.Handle(), tensor_view_state->Handle()); |
| skip |= LogError( |
| vuids->spirv_OpTypeTensorARM_09906, objlist, loc.Get(), |
| "the %s is using tensor %s that was created with %s but doesn't match the OpTypeTensorARM of type %s and width %" PRIu32 |
| " (equivalent to %s).%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(tensor_view_state->Handle()).c_str(), string_VkFormat(tensor_view_state->create_info.format), |
| spirv::string_NumericType(resource_variable.info.numeric_type), resource_variable.info.bit_width, |
| string_VkFormat(resource_variable.info.vk_format), DescribeInstruction().c_str()); |
| } |
| } |
| |
| VkTensorUsageFlagBitsARM usage_flag = (loc.Get().function == Func::vkCmdDispatchDataGraphARM) |
| ? VK_TENSOR_USAGE_DATA_GRAPH_BIT_ARM |
| : VK_TENSOR_USAGE_SHADER_BIT_ARM; |
| if ((tensor_state->create_info.pDescription->usage & usage_flag) == 0) { |
| const LogObjectList objlist(cb_state.Handle(), this->objlist, descriptor_set.Handle(), tensor_state->Handle()); |
| skip |= LogError(vuids->tensorARM_pDescription_09900, objlist, loc.Get(), |
| "the %s is using tensor %s created with usage %s, which doesn't include %s.%s", |
| DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), |
| FormatHandle(tensor_state->Handle()).c_str(), |
| string_VkTensorUsageFlagBitsARM(tensor_state->create_info.pDescription->usage), |
| string_VkTensorUsageFlagBitsARM(usage_flag), DescribeInstruction().c_str()); |
| } |
| |
| return skip; |
| } |
| |
| } // namespace vvl |