blob: b66e0afb441238d3213106ee0b8461e1b1069864 [file]
/* Copyright (c) 2015-2026 The Khronos Group Inc.
* Copyright (c) 2015-2026 Valve Corporation
* Copyright (c) 2015-2026 LunarG, Inc.
* Copyright (C) 2015-2026 Google Inc.
* Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved.
*
* 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 "utils/descriptor_utils.h"
#include <vulkan/vulkan_core.h>
#include "containers/container_utils.h"
#include "containers/custom_containers.h"
#include "generated/dispatch_functions.h"
#include "generated/error_location_helper.h"
#include "generated/spirv_grammar_helper.h"
#include "generated/vk_extension_helper.h"
#include "state_tracker/shader_module.h"
#include "state_tracker/state_tracker.h"
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
uint32_t CountDescriptorHeapEmbeddedSamplers(const void* pNext) {
const VkShaderDescriptorSetAndBindingMappingInfoEXT* mapping_info =
vku::FindStructInPNextChain<VkShaderDescriptorSetAndBindingMappingInfoEXT>(pNext);
uint32_t count = 0;
if (mapping_info) {
for (uint32_t i = 0; i < mapping_info->mappingCount; ++i) {
const VkDescriptorSetAndBindingMappingEXT& mapping = mapping_info->pMappings[i];
if (GetEmbeddedSampler(mapping) != nullptr) {
count++;
}
}
}
return count;
}
vvl::Field DescriptorBufferSizeField(bool robust, VkDescriptorType type) {
switch (type) {
case VK_DESCRIPTOR_TYPE_SAMPLER:
return vvl::Field::samplerDescriptorSize;
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
return vvl::Field::combinedImageSamplerDescriptorSize;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
return vvl::Field::sampledImageDescriptorSize;
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
return vvl::Field::storageImageDescriptorSize;
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
return robust ? vvl::Field::robustUniformTexelBufferDescriptorSize : vvl::Field::uniformTexelBufferDescriptorSize;
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
return robust ? vvl::Field::robustStorageTexelBufferDescriptorSize : vvl::Field::storageTexelBufferDescriptorSize;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
return robust ? vvl::Field::robustUniformBufferDescriptorSize : vvl::Field::uniformBufferDescriptorSize;
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
return robust ? vvl::Field::robustStorageBufferDescriptorSize : vvl::Field::storageBufferDescriptorSize;
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
return vvl::Field::inputAttachmentDescriptorSize;
case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR:
return vvl::Field::accelerationStructureDescriptorSize;
default:
break;
}
return vvl::Field::Empty;
}
bool IsResourceVaribleInMapping(const VkDescriptorSetAndBindingMappingEXT& mapping,
const spirv::ResourceInterfaceVariable& resource_variable) {
const uint32_t descriptor_set = resource_variable.decorations.set;
const uint32_t descriptor_binding = resource_variable.decorations.binding;
// bindingCount could be UINT32_MAX
const uint64_t last_binding = mapping.firstBinding + uint64_t(mapping.bindingCount);
return (mapping.descriptorSet == descriptor_set && descriptor_binding >= mapping.firstBinding &&
descriptor_binding < last_binding && ResourceTypeMatchesBinding(mapping.resourceMask, resource_variable));
}
bool ResourceTypeMatchesBinding(VkSpirvResourceTypeFlagsEXT resource_type,
const spirv::ResourceInterfaceVariable& resource_variable) {
if (resource_type == VK_SPIRV_RESOURCE_TYPE_ALL_EXT) {
return true;
}
const uint32_t opcode = resource_variable.base_type.Opcode();
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT) != 0 && opcode == spv::OpTypeSampler) {
return true;
}
if (opcode == spv::OpTypeImage) {
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT) != 0 && resource_variable.base_type.Word(7) == 1) {
return true;
}
// NonWritable must be on the OpVariable, not the OpTypeStruct
// https://gitlab.khronos.org/vulkan/vulkan/-/issues/4789
const bool nonwritable = resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit);
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_ONLY_IMAGE_BIT_EXT) != 0 && resource_variable.base_type.Word(7) == 2 &&
nonwritable) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_WRITE_IMAGE_BIT_EXT) != 0 && resource_variable.base_type.Word(7) == 2 &&
!nonwritable) {
return true;
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_COMBINED_SAMPLED_IMAGE_BIT_EXT) != 0 &&
resource_variable.is_combined_image_sampler) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT) != 0 && resource_variable.is_uniform_buffer) {
return true;
}
if (resource_variable.is_storage_buffer) {
// NonWritable must be on the OpVariable, not the OpTypeStruct
// https://gitlab.khronos.org/vulkan/vulkan/-/issues/4789
const bool nonwritable = resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit);
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_ONLY_STORAGE_BUFFER_BIT_EXT) != 0 && nonwritable) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_WRITE_STORAGE_BUFFER_BIT_EXT) != 0 && !nonwritable) {
return true;
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_ACCELERATION_STRUCTURE_BIT_EXT) != 0 &&
opcode == spv::OpTypeAccelerationStructureKHR) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_TENSOR_BIT_ARM) != 0 && opcode == spv::OpTypeTensorARM) {
return true;
}
return false;
}
// Only currently want to report the first mismatch
std::string DescribeResourceTypeMismatch(VkSpirvResourceTypeFlagsEXT resource_type,
const spirv::ResourceInterfaceVariable& resource_variable) {
const uint32_t opcode = resource_variable.base_type.Opcode();
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT) != 0 && opcode != spv::OpTypeSampler) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeSampler";
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT) != 0) {
if (opcode != spv::OpTypeImage) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeImage";
} else if (resource_variable.base_type.Word(7) != 1) {
return "OpTypeImage Sampled is " + std::to_string(resource_variable.base_type.Word(7)) + ", not 1";
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_ONLY_IMAGE_BIT_EXT) != 0) {
if (opcode != spv::OpTypeImage) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeImage";
} else if (resource_variable.base_type.Word(7) != 2) {
return "OpTypeImage Sampled is " + std::to_string(resource_variable.base_type.Word(7)) + ", not 2";
} else if (!resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
if (resource_variable.HasInMember(spirv::DecorationSet::nonwritable_bit)) {
return "is not decorated with NonWritable on the OpVariable, but is in the OpTypeStruct. This likely is hitting a "
"known DXC code generation bug (DirectXShaderCompiler/issues/8492). Simple work around is to just use "
"VK_SPIRV_RESOURCE_TYPE_ALL_EXT here as that will give the desired behavior.";
} else {
return "is not decorated with NonWritable";
}
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_WRITE_IMAGE_BIT_EXT) != 0) {
if (opcode != spv::OpTypeImage) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeImage";
} else if (resource_variable.base_type.Word(7) != 2) {
return "OpTypeImage Sampled is " + std::to_string(resource_variable.base_type.Word(7)) + ", not 2";
} else if (resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return "is decorated with NonWritable";
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_COMBINED_SAMPLED_IMAGE_BIT_EXT) != 0 &&
!resource_variable.is_combined_image_sampler) {
return "is not OpTypeSampledImage";
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT) != 0 && !resource_variable.is_uniform_buffer) {
if (resource_variable.storage_class != spv::StorageClassUniform) {
return "is not Uniform StorageClass";
} else if (!resource_variable.type_struct_info) {
return "is not a OpTypeStruct";
} else if (!resource_variable.type_struct_info->decorations.Has(spirv::DecorationSet::block_bit)) {
return "is not decorated with Block";
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_ONLY_STORAGE_BUFFER_BIT_EXT) != 0) {
if (!resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
if (resource_variable.HasInMember(spirv::DecorationSet::nonwritable_bit)) {
return "is not decorated with NonWritable on the OpVariable, but is in the OpTypeStruct. This likely is hitting a "
"known DXC code generation bug (DirectXShaderCompiler/issues/8492). Simple work around is to just use "
"VK_SPIRV_RESOURCE_TYPE_ALL_EXT here as that will give the desired behavior.";
} else {
return "is not decorated with NonWritable";
}
} else if (!resource_variable.type_struct_info) {
return "is not a OpTypeStruct";
} else if (!resource_variable.is_storage_buffer) {
return "is not a storage buffer"; // simplified
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_WRITE_STORAGE_BUFFER_BIT_EXT) != 0) {
if (!resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return "is decorated with NonWritable";
} else if (!resource_variable.type_struct_info) {
return "is not a OpTypeStruct";
} else if (!resource_variable.is_storage_buffer) {
return "is not a storage buffer"; // simplified
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_ACCELERATION_STRUCTURE_BIT_EXT) != 0 &&
opcode == spv::OpTypeAccelerationStructureKHR) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeAccelerationStructureKHR";
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_TENSOR_BIT_ARM) != 0 && opcode == spv::OpTypeTensorARM) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeTensorARM";
}
return "[UNKNOWN]";
}
VkDeviceSize GetDescriptorHeapAlignment(const VkPhysicalDeviceDescriptorHeapPropertiesEXT& props, VkDescriptorType type) {
switch (type) {
case VK_DESCRIPTOR_TYPE_SAMPLER:
return props.samplerDescriptorAlignment;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR:
case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV:
case VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV:
return props.bufferDescriptorAlignment;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: // not valid, but handle in case
return props.imageDescriptorAlignment;
case VK_DESCRIPTOR_TYPE_TENSOR_ARM:
case VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM:
case VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM:
return 0; // TODO - vendors handle their own types
default:
assert(false);
break;
}
return 0;
}
vvl::Field GetDescriptorHeapAlignmentField(VkDescriptorType type) {
if (type == VK_DESCRIPTOR_TYPE_TENSOR_ARM) {
return vvl::Field::tensorDescriptorAlignment;
} else if (type == VK_DESCRIPTOR_TYPE_SAMPLER) {
return vvl::Field::samplerDescriptorAlignment;
} else if (IsValueIn(type,
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER})) {
return vvl::Field::imageDescriptorAlignment;
} else if (IsValueIn(type, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR})) {
return vvl::Field::bufferDescriptorAlignment;
}
assert(false);
return vvl::Field::Empty;
}
// Assumes you already know this is a combined image sampler
bool HasCombinedImageSamplerIndex(const VkDescriptorSetAndBindingMappingEXT& mapping) {
if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_PUSH_INDEX_EXT) {
return !mapping.sourceData.pushIndex.pEmbeddedSampler && mapping.sourceData.pushIndex.useCombinedImageSamplerIndex;
} else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_EXT) {
return !mapping.sourceData.indirectIndex.pEmbeddedSampler && mapping.sourceData.indirectIndex.useCombinedImageSamplerIndex;
} else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_ARRAY_EXT) {
return !mapping.sourceData.indirectIndexArray.pEmbeddedSampler &&
mapping.sourceData.indirectIndexArray.useCombinedImageSamplerIndex;
}
return false;
}
void CachedDescriptorSize::Init(const vvl::DeviceState& device_state) {
const VkPhysicalDevice gpu = device_state.physical_device;
if (IsExtEnabled(device_state.extensions.vk_ext_descriptor_heap)) {
heap_size_[0] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_SAMPLER);
heap_size_[2] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE);
heap_size_[3] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE);
heap_size_[4] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER);
heap_size_[5] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER);
heap_size_[6] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
heap_size_[7] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
heap_size_[10] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT);
if (IsExtEnabled(device_state.extensions.vk_khr_acceleration_structure)) {
heap_size_[1] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR);
} else {
heap_size_[1] = 0;
}
if (IsExtEnabled(device_state.extensions.vk_nv_ray_tracing)) {
heap_size_[8] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV);
} else {
heap_size_[8] = 0;
}
if (IsExtEnabled(device_state.extensions.vk_nv_partitioned_acceleration_structure)) {
heap_size_[9] =
DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV);
} else {
heap_size_[9] = 0;
}
if (IsExtEnabled(device_state.extensions.vk_arm_tensors)) {
heap_size_[11] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_TENSOR_ARM);
} else {
heap_size_[11] = 0;
}
if (IsExtEnabled(device_state.extensions.vk_qcom_image_processing)) {
heap_size_[12] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM);
heap_size_[13] = DispatchGetPhysicalDeviceDescriptorSizeEXT(gpu, VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM);
} else {
heap_size_[12] = 0;
heap_size_[13] = 0;
}
}
if (IsExtEnabled(device_state.extensions.vk_ext_descriptor_buffer)) {
const VkPhysicalDeviceDescriptorBufferPropertiesEXT& props = device_state.phys_dev_ext_props.descriptor_buffer_props;
const bool robust = device_state.enabled_features.robustBufferAccess;
buffer_size_[0] = props.samplerDescriptorSize;
buffer_size_[1] = props.accelerationStructureDescriptorSize;
buffer_size_[2] = props.sampledImageDescriptorSize;
buffer_size_[3] = props.storageImageDescriptorSize;
buffer_size_[4] = robust ? props.robustUniformTexelBufferDescriptorSize : props.uniformTexelBufferDescriptorSize;
buffer_size_[5] = robust ? props.robustStorageTexelBufferDescriptorSize : props.storageTexelBufferDescriptorSize;
buffer_size_[6] = robust ? props.robustUniformBufferDescriptorSize : props.uniformBufferDescriptorSize;
buffer_size_[7] = robust ? props.robustStorageBufferDescriptorSize : props.storageBufferDescriptorSize;
buffer_size_[8] = 0; // nv as
buffer_size_[9] = 0; // nv partitioned as
buffer_size_[10] = props.inputAttachmentDescriptorSize;
buffer_size_[11] = 0; // arm tensor
buffer_size_[12] = 0; // qcom sample weight
buffer_size_[13] = 0; // qcom block match
}
}
VkDeviceSize CachedDescriptorSize::GetSize(VkDescriptorType type, bool use_heap) const {
uint32_t index = (uint32_t)type;
if (type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR) {
// takes VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER value at index 1
index = 1;
} else if (type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV) {
// takes VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC value at index 8
index = 8;
} else if (type == VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV) {
// takes VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC value at index 9
index = 9;
} else if (type == VK_DESCRIPTOR_TYPE_TENSOR_ARM) {
index = 11;
} else if (type == VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM) {
index = 12;
} else if (type == VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM) {
index = 13;
} else {
assert(IsValueIn(
type, {VK_DESCRIPTOR_TYPE_SAMPLER, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT}));
}
return use_heap ? heap_size_[index] : buffer_size_[index];
}
std::vector<VkDeviceSize> CachedDescriptorSize::GetAllSizes(bool use_heap) const {
std::vector<VkDeviceSize> sizes;
// Pre-allocate as no known implementations seem to go above 4 different sizes
sizes.reserve(4);
for (uint32_t i = 0; i < 14; i++) {
const VkDeviceSize new_size = use_heap ? heap_size_[i] : buffer_size_[i];
if (new_size == 0) {
continue;
}
const size_t current_count = sizes.size();
if (current_count == 0) {
sizes.emplace_back(new_size);
} else if (current_count == 1) {
if (sizes[0] != new_size) {
sizes.emplace_back(new_size);
}
} else {
// The vector is small enough and the number of unique sizes is small enough that just looping the vector to find it
// should be fast enough
bool add = true;
for (size_t j = 0; j < current_count; j++) {
if (sizes[j] == new_size) {
add = false;
break;
}
}
if (add) {
sizes.emplace_back(new_size);
}
}
}
return sizes;
}