| /* Copyright (c) 2026 LunarG, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "descriptor_heap_pass.h" |
| #include <vulkan/vulkan_core.h> |
| #include "containers/container_utils.h" |
| #include "generated/spirv_grammar_helper.h" |
| #include "gpuav/descriptor_validation/gpuav_descriptor_validation.h" |
| #include "gpuav/shaders/gpuav_error_header.h" |
| #include "instrumentation_status.h" |
| #include "link.h" |
| #include "module.h" |
| #include <cassert> |
| #include <cstdint> |
| #include <spirv/unified1/spirv.hpp> |
| #include <iostream> |
| |
| #include "generated/gpuav_offline_spirv.h" |
| #include "pass.h" |
| #include "type_manager.h" |
| #include "utils/math_utils.h" |
| |
| namespace gpuav { |
| namespace spirv { |
| |
| const static OfflineModule kOfflineModule = {instrumentation_descriptor_heap_comp, instrumentation_descriptor_heap_comp_size, |
| UseErrorPayloadVariable}; |
| |
| const static OfflineFunction kOfflineFunction[] = { |
| {"inst_heap_mapping_constant_offset", instrumentation_descriptor_heap_comp_function_0_offset}, |
| {"inst_heap_mapping_push_index", instrumentation_descriptor_heap_comp_function_1_offset}, |
| {"inst_heap_mapping_indirect_index", instrumentation_descriptor_heap_comp_function_2_offset}, |
| {"inst_heap_mapping_indirect_index_array", instrumentation_descriptor_heap_comp_function_3_offset}, |
| {"inst_heap_mapping_resource_heap_data", instrumentation_descriptor_heap_comp_function_4_offset}, |
| {"inst_heap_mapping_push_data", instrumentation_descriptor_heap_comp_function_5_offset}, |
| {"inst_heap_mapping_push_address", instrumentation_descriptor_heap_comp_function_6_offset}, |
| {"inst_heap_mapping_indirect_address", instrumentation_descriptor_heap_comp_function_7_offset}, |
| {"inst_heap_untyped", instrumentation_descriptor_heap_comp_function_8_offset}, |
| }; |
| |
| DescriptorHeapPass::DescriptorHeapPass(Module& module) : Pass(module, kOfflineModule) { module.use_bda_ = true; } |
| |
| uint32_t DescriptorHeapPass::GetLinkFunctionId(const FunctionNames func_name) { |
| return GetLinkFunction(link_function_id_[func_name], kOfflineFunction[func_name]); |
| } |
| |
| // Unfortunately this is a duplicate of the util function because there is no spirv::ResourceInterfaceVariable |
| bool DescriptorHeapPass::ResourceTypeMatchesBinding(VkSpirvResourceTypeFlagsEXT resource_type, const AccessPath& access_path, |
| bool is_sampler) const { |
| if (resource_type == VK_SPIRV_RESOURCE_TYPE_ALL_EXT) { |
| return true; |
| } |
| |
| // cant use |descriptor_type| as its only for the image resource here |
| if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT) != 0 && is_sampler) { |
| return true; |
| } |
| if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT) != 0 && |
| (access_path.descriptor_type == gpuav::descriptor::TYPE_IMAGE_SAMPLED)) { |
| return true; |
| } |
| if (access_path.descriptor_type == gpuav::descriptor::TYPE_IMAGE_STORAGE || |
| access_path.descriptor_type == gpuav::descriptor::TYPE_IMAGE_TEXEL_BUFFER_STORAGE || |
| access_path.descriptor_type == gpuav::descriptor::TYPE_IMAGE_INPUT_ATTACHMENT) { |
| // NonWritable must be on the OpVariable, not the OpTypeStruct |
| // https://gitlab.khronos.org/vulkan/vulkan/-/issues/4789 |
| const bool nonwritable = GetDecoration(access_path.variable->Id(), spv::DecorationNonWritable) != nullptr; |
| if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_ONLY_IMAGE_BIT_EXT) != 0 && nonwritable) { |
| return true; |
| } |
| if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_WRITE_IMAGE_BIT_EXT) != 0 && !nonwritable) { |
| return true; |
| } |
| } |
| |
| if ((resource_type & VK_SPIRV_RESOURCE_TYPE_COMBINED_SAMPLED_IMAGE_BIT_EXT) != 0 && access_path.is_combined_image_sampler) { |
| return true; |
| } |
| if ((resource_type & VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT) != 0 && |
| access_path.descriptor_type == gpuav::descriptor::TYPE_UNIFORM_BUFFER) { |
| return true; |
| } |
| if (access_path.descriptor_type == gpuav::descriptor::TYPE_STORAGE_BUFFER) { |
| // NonWritable must be on the OpVariable, not the OpTypeStruct |
| // https://gitlab.khronos.org/vulkan/vulkan/-/issues/4789 |
| const bool nonwritable = GetDecoration(access_path.variable->Id(), spv::DecorationNonWritable) != nullptr; |
| 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 && |
| access_path.descriptor_type == gpuav::descriptor::TYPE_ACCELERATION_STRUCTURE) { |
| return true; |
| } |
| |
| // TODO - VK_SPIRV_RESOURCE_TYPE_TENSOR_BIT_ARM |
| |
| return false; |
| } |
| |
| // TODO - if we find this slow, we could pre-sort all the mappings |
| const uint32_t kMappingIndexInvalid = 0xFFFFFFFF; |
| uint32_t DescriptorHeapPass::GetMapping(const AccessPath& access_path, bool is_sampler) const { |
| const Variable& variable = is_sampler ? *access_path.sampler_variable : *access_path.variable; |
| const DescriptorInterface& interface = variable.interface_; |
| if (interface.IsHeap()) { |
| return glsl::kInst_DescriptorHeap_MappingIndexUntyped; |
| } |
| |
| for (uint32_t i = 0; i < module_.interface_.mapping_info->mappingCount; i++) { |
| const VkDescriptorSetAndBindingMappingEXT& mapping = module_.interface_.mapping_info->pMappings[i]; |
| const uint32_t last_binding = mapping.firstBinding + mapping.bindingCount; |
| if (mapping.descriptorSet == interface.set && interface.binding >= mapping.firstBinding && |
| interface.binding < last_binding && ResourceTypeMatchesBinding(mapping.resourceMask, access_path, is_sampler)) { |
| const uint32_t encode_index = (uint32_t)module_.out_status.device.heap_mappings.size(); |
| module_.out_status.device.heap_mappings.emplace_back(HeapMappingStatus{i, interface.binding, variable.Id(), mapping}); |
| |
| if (encode_index >= glsl::kInst_DescriptorHeap_MappingIndexUntyped) { |
| module_.InternalWarning(Name(), |
| "Tried to use an index into VkShaderDescriptorSetAndBindingMappingInfoEXT::pMapping that " |
| "is over 64k and not able to " |
| "track. Please report an issue as we made assumption no one would this many mappings!"); |
| } |
| return encode_index; |
| } |
| } |
| |
| assert(false); |
| return kMappingIndexInvalid; |
| } |
| |
| uint32_t DescriptorHeapPass::CreateFunctionCall(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta, |
| bool is_seperate_sampler) { |
| const Variable& descriptor_variable = is_seperate_sampler ? *meta.access_path.sampler_variable : *meta.access_path.variable; |
| const uint32_t descriptor_index = |
| is_seperate_sampler ? meta.access_path.sampler_descriptor_index_id : meta.access_path.descriptor_index_id; |
| const uint32_t descriptor_index_id = CastToUint32(descriptor_index, block, inst_it); // might be int32 |
| |
| const uint32_t inst_position = meta.target_instruction->GetPositionOffset(); |
| const uint32_t inst_position_id = type_manager_.CreateConstantUInt32(inst_position).Id(); |
| const uint32_t is_sampler_id = type_manager_.GetConstantBool(is_seperate_sampler).Id(); |
| |
| const VkDescriptorSetAndBindingMappingEXT* mapping = nullptr; |
| |
| // We try and encode a lot of information in a single uint32_t |
| uint32_t desc_encoding_id = 0; |
| { |
| const bool is_buffer = meta.access_path.descriptor_type & gpuav::descriptor::TYPE_BUFFER_MASK; |
| const bool is_sampler = is_seperate_sampler; |
| const bool is_image = !is_buffer && !is_sampler; |
| |
| const uint32_t desc_type = is_sampler ? gpuav::descriptor::TYPE_SAMPLER : meta.access_path.descriptor_type; |
| |
| const uint32_t desc_alignment_value = is_buffer ? module_.settings_.descriptor_alignment_buffer |
| : is_image ? module_.settings_.descriptor_alignment_image |
| : module_.settings_.descriptor_alignment_sampler; |
| const uint32_t desc_alignment_shift = GetAlignmentShift(desc_alignment_value); |
| assert(desc_alignment_shift <= glsl::kInst_DescriptorHeap_AlignmentShiftMask); |
| |
| const uint32_t mapping_index_encoded = is_seperate_sampler ? meta.mapping_index_sampler : meta.mapping_index_resource; |
| mapping = (mapping_index_encoded == glsl::kInst_DescriptorHeap_MappingIndexUntyped) |
| ? nullptr |
| : &module_.out_status.device.heap_mappings[mapping_index_encoded].mapping_data; |
| |
| const uint32_t desc_encoding = (desc_type << glsl::kInst_DescriptorHeap_DescriptorTypeShift) | |
| (mapping_index_encoded << glsl::kInst_DescriptorHeap_MappingIndexShift) | |
| (desc_alignment_shift & glsl::kInst_DescriptorHeap_AlignmentShiftMask); |
| |
| desc_encoding_id = type_manager_.GetConstantUInt32(desc_encoding).Id(); |
| } |
| |
| uint32_t function_result = module_.TakeNextId(); |
| const uint32_t bool_type = type_manager_.GetTypeBool().Id(); |
| |
| // TODO - This logic is not obvious and should be either fixed or moved to a common util |
| // If dealing with a seperate sampler, only need to do it on the resource |
| if (meta.access_path.image_load_inst && !is_seperate_sampler) { |
| const uint32_t opcode = meta.target_instruction->Opcode(); |
| if (opcode != spv::OpImageRead && opcode != spv::OpImageFetch && opcode != spv::OpImageWrite) { |
| // if not a direct read/write/fetch, will be a OpSampledImage |
| // "All OpSampledImage instructions must be in the same block in which their Result <id> are consumed" |
| // the simple way around this is to add a OpCopyObject to be consumed by the target instruction |
| uint32_t image_id = meta.target_instruction->Operand(0); |
| const Instruction* sampled_image_inst = block.function_->FindInstruction(image_id); |
| // TODO - Add tests to understand what else can be here other then OpSampledImage |
| if (sampled_image_inst->Opcode() == spv::OpSampledImage) { |
| const uint32_t type_id = sampled_image_inst->TypeId(); |
| const uint32_t copy_id = module_.TakeNextId(); |
| const_cast<Instruction*>(meta.target_instruction)->ReplaceOperandId(image_id, copy_id); |
| |
| // incase the OpSampledImage is shared, copy the previous OpCopyObject |
| auto copied = copy_object_map_.find(image_id); |
| if (copied != copy_object_map_.end()) { |
| image_id = copied->second; |
| block.CreateInstruction(spv::OpCopyObject, {type_id, copy_id, image_id}, inst_it); |
| } else { |
| copy_object_map_.emplace(image_id, copy_id); |
| // slower, but need to guarantee it is placed after a OpSampledImage |
| block.function_->CreateInstruction(spv::OpCopyObject, {type_id, copy_id, image_id}, image_id, inst_it); |
| } |
| } |
| } |
| } |
| |
| const uint32_t binding_offset = mapping ? descriptor_variable.interface_.binding - mapping->firstBinding : 0; |
| |
| bool has_embedded_sampler = false; |
| if (!mapping) { |
| assert(descriptor_variable.interface_.IsHeap()); // Untyped |
| uint32_t heap_offset_id = 0; |
| const Type* descriptor_array = nullptr; |
| if (meta.access_path.pointer_type->spv_type_ == SpvType::kStruct) { |
| const Instruction* offset_decoration = GetMemberDecoration( |
| meta.access_path.pointer_type->Id(), meta.access_path.heap_offset_member_index, spv::DecorationOffsetIdEXT); |
| assert(offset_decoration); |
| heap_offset_id = offset_decoration->Word(4); |
| heap_offset_id = CastToUint32(heap_offset_id, block, inst_it); |
| |
| descriptor_array = |
| type_manager_.FindTypeById(meta.access_path.pointer_type->inst_.Operand(meta.access_path.heap_offset_member_index)); |
| assert(descriptor_array->IsArray()); |
| } else { |
| assert(meta.access_path.pointer_type->IsArray()); |
| descriptor_array = meta.access_path.pointer_type; |
| heap_offset_id = type_manager_.GetConstantZeroUint32().Id(); |
| } |
| |
| const uint32_t array_stride_dec = GetDecoration(descriptor_array->Id(), spv::DecorationArrayStrideIdEXT)->Word(3); |
| const Constant* array_stride = type_manager_.FindConstantById(array_stride_dec); |
| const uint32_t array_stride_id = CastToUint32(array_stride->Id(), block, inst_it); // might be int32 |
| |
| const uint32_t function_def = GetLinkFunctionId(UNTYPED); |
| |
| block.CreateInstruction(spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, array_stride_id, |
| descriptor_index_id, desc_encoding_id, is_sampler_id}, |
| inst_it); |
| } else if (mapping->source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT) { |
| const VkDescriptorMappingSourceConstantOffsetEXT& map_data = mapping->sourceData.constantOffset; |
| has_embedded_sampler = map_data.pEmbeddedSampler != nullptr; |
| |
| const uint32_t heap_offset = map_data.heapOffset + (binding_offset * map_data.heapArrayStride); |
| const uint32_t heap_offset_id = type_manager_.GetConstantUInt32(heap_offset).Id(); |
| const uint32_t heap_array_stride_id = type_manager_.GetConstantUInt32(map_data.heapArrayStride).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_CONSTANT_OFFSET); |
| |
| block.CreateInstruction(spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, heap_array_stride_id, |
| descriptor_index_id, desc_encoding_id, is_sampler_id}, |
| inst_it); |
| } else if (mapping->source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_PUSH_INDEX_EXT) { |
| const VkDescriptorMappingSourcePushIndexEXT& map_data = mapping->sourceData.pushIndex; |
| has_embedded_sampler = map_data.pEmbeddedSampler != nullptr; |
| |
| const uint32_t heap_offset = map_data.heapOffset + (binding_offset * map_data.heapArrayStride); |
| const uint32_t heap_offset_id = type_manager_.GetConstantUInt32(heap_offset).Id(); |
| // VU enforces pushOffset to multiple of 4, and the GLSL is using a uint array |
| const uint32_t push_offset_id = type_manager_.GetConstantUInt32(map_data.pushOffset / 4).Id(); |
| const uint32_t heap_index_stride_id = type_manager_.GetConstantUInt32(map_data.heapIndexStride).Id(); |
| const uint32_t heap_array_stride_id = type_manager_.GetConstantUInt32(map_data.heapArrayStride).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_PUSH_INDEX); |
| |
| block.CreateInstruction(spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, push_offset_id, |
| heap_index_stride_id, heap_array_stride_id, descriptor_index_id, desc_encoding_id, is_sampler_id}, |
| inst_it); |
| } else if (mapping->source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_EXT) { |
| const VkDescriptorMappingSourceIndirectIndexEXT& map_data = mapping->sourceData.indirectIndex; |
| has_embedded_sampler = map_data.pEmbeddedSampler != nullptr; |
| |
| const uint32_t heap_offset = map_data.heapOffset + (binding_offset * map_data.heapArrayStride); |
| const uint32_t heap_offset_id = type_manager_.GetConstantUInt32(heap_offset).Id(); |
| // VU enforces pushOffset to multiple of 8, and the GLSL is using a uint array |
| const uint32_t push_offset_id = type_manager_.GetConstantUInt32(map_data.pushOffset / 4).Id(); |
| const uint32_t address_offset_id = type_manager_.GetConstantUInt32(map_data.addressOffset / 4).Id(); |
| const uint32_t heap_index_stride_id = type_manager_.GetConstantUInt32(map_data.heapIndexStride).Id(); |
| const uint32_t heap_array_stride_id = type_manager_.GetConstantUInt32(map_data.heapArrayStride).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_INDIRECT_INDEX); |
| |
| block.CreateInstruction( |
| spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, push_offset_id, address_offset_id, |
| heap_index_stride_id, heap_array_stride_id, descriptor_index_id, desc_encoding_id, is_sampler_id}, |
| inst_it); |
| } else if (mapping->source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_ARRAY_EXT) { |
| const VkDescriptorMappingSourceIndirectIndexArrayEXT& map_data = mapping->sourceData.indirectIndexArray; |
| has_embedded_sampler = map_data.pEmbeddedSampler != nullptr; |
| |
| const uint32_t heap_offset_id = type_manager_.GetConstantUInt32(map_data.heapOffset).Id(); |
| // VU enforces pushOffset to multiple of 8, and the GLSL is using a uint array |
| const uint32_t push_offset_id = type_manager_.GetConstantUInt32(map_data.pushOffset / 4).Id(); |
| const uint32_t frist_index_offset = (map_data.addressOffset / 4) + binding_offset; |
| const uint32_t address_offset_id = type_manager_.GetConstantUInt32(frist_index_offset).Id(); |
| const uint32_t heap_index_stride_id = type_manager_.GetConstantUInt32(map_data.heapIndexStride).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_INDIRECT_INDEX_ARRAY); |
| |
| block.CreateInstruction(spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, push_offset_id, |
| address_offset_id, heap_index_stride_id, descriptor_index_id, desc_encoding_id, is_sampler_id}, |
| inst_it); |
| } else if (mapping->source == VK_DESCRIPTOR_MAPPING_SOURCE_RESOURCE_HEAP_DATA_EXT) { |
| const VkDescriptorMappingSourceHeapDataEXT& map_data = mapping->sourceData.heapData; |
| |
| const uint32_t heap_offset_id = type_manager_.GetConstantUInt32(map_data.heapOffset).Id(); |
| const uint32_t push_offset_id = type_manager_.GetConstantUInt32(map_data.pushOffset / 4).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_RESOURCE_HEAP_DATA); |
| |
| block.CreateInstruction( |
| spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, push_offset_id, desc_encoding_id}, |
| inst_it); |
| } else if (mapping->source == VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_DATA_EXT) { |
| // TODO - is there any GPU-AV needed for this mapping? |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_PUSH_DATA); |
| block.CreateInstruction(spv::OpFunctionCall, {bool_type, function_result, function_def}, inst_it); |
| } else if (mapping->source == VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_ADDRESS_EXT) { |
| const uint32_t push_offset_id = type_manager_.GetConstantUInt32(mapping->sourceData.pushAddressOffset / 4).Id(); |
| |
| const bool is_uniform = meta.access_path.descriptor_type == gpuav::descriptor::TYPE_UNIFORM_BUFFER; |
| const uint32_t buffer_alignment = |
| is_uniform ? module_.settings_.min_uniform_buffer_alignment : module_.settings_.min_storage_buffer_alignment; |
| const uint32_t buffer_alignment_id = type_manager_.GetConstantUInt32(buffer_alignment).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_PUSH_ADDRESS); |
| |
| block.CreateInstruction( |
| spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, push_offset_id, buffer_alignment_id, desc_encoding_id}, |
| inst_it); |
| } else if (mapping->source == VK_DESCRIPTOR_MAPPING_SOURCE_INDIRECT_ADDRESS_EXT) { |
| const VkDescriptorMappingSourceIndirectAddressEXT& map_data = mapping->sourceData.indirectAddress; |
| |
| const uint32_t push_offset_id = type_manager_.GetConstantUInt32(map_data.pushOffset / 4).Id(); |
| const uint32_t address_offset_id = type_manager_.GetConstantUInt32(map_data.addressOffset / 4).Id(); |
| |
| const bool is_uniform = meta.access_path.descriptor_type == gpuav::descriptor::TYPE_UNIFORM_BUFFER; |
| const uint32_t buffer_alignment = |
| is_uniform ? module_.settings_.min_uniform_buffer_alignment : module_.settings_.min_storage_buffer_alignment; |
| const uint32_t buffer_alignment_id = type_manager_.GetConstantUInt32(buffer_alignment).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_INDIRECT_ADDRESS); |
| |
| block.CreateInstruction(spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, push_offset_id, address_offset_id, |
| buffer_alignment_id, desc_encoding_id}, |
| inst_it); |
| |
| } else { |
| assert(false); // TODO - Shader Record |
| } |
| |
| module_.need_log_error_ = true; |
| |
| // If there is a sampler, we have another descriptor at this spot we need to validate |
| if (!is_seperate_sampler && meta.access_path.HasSampler() && !has_embedded_sampler) { |
| const uint32_t valid_image = function_result; |
| uint32_t valid_sampler = 0; |
| if (meta.access_path.is_combined_image_sampler) { |
| assert(mapping); // not allowed with untyped pointers |
| valid_sampler = CreateFunctionCallCombinedSampler(block, inst_it, meta, *mapping, descriptor_index_id); |
| } else { |
| valid_sampler = CreateFunctionCall(block, inst_it, meta, true); |
| } |
| |
| if (module_.settings_.safe_mode) { |
| function_result = module_.TakeNextId(); |
| block.CreateInstruction(spv::OpLogicalAnd, {bool_type, function_result, valid_image, valid_sampler}, inst_it); |
| } |
| } |
| |
| return function_result; |
| } |
| |
| // Sampler are annoying, decided this should just be its own function as its only used for 4 mappings |
| uint32_t DescriptorHeapPass::CreateFunctionCallCombinedSampler(BasicBlock& block, InstructionIt* inst_it, |
| const InstructionMeta& meta, |
| const VkDescriptorSetAndBindingMappingEXT& mapping, |
| const uint32_t descriptor_index_id) { |
| const uint32_t inst_position = meta.target_instruction->GetPositionOffset(); |
| const uint32_t inst_position_id = type_manager_.CreateConstantUInt32(inst_position).Id(); |
| const uint32_t is_sampler_id = type_manager_.GetConstantBool(true).Id(); |
| const uint32_t desc_alignment_id = type_manager_.GetConstantUInt32(module_.settings_.descriptor_alignment_sampler).Id(); |
| |
| const uint32_t binding_offset = meta.access_path.variable->interface_.binding - mapping.firstBinding; |
| |
| uint32_t function_result = module_.TakeNextId(); |
| const uint32_t bool_type = type_manager_.GetTypeBool().Id(); |
| |
| if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT) { |
| const VkDescriptorMappingSourceConstantOffsetEXT& map_data = mapping.sourceData.constantOffset; |
| |
| const uint32_t heap_offset = map_data.heapOffset + (binding_offset * map_data.samplerHeapOffset); |
| const uint32_t heap_offset_id = type_manager_.GetConstantUInt32(heap_offset).Id(); |
| const uint32_t heap_array_stride_id = type_manager_.GetConstantUInt32(map_data.samplerHeapArrayStride).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_CONSTANT_OFFSET); |
| |
| block.CreateInstruction(spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, heap_array_stride_id, |
| descriptor_index_id, desc_alignment_id, is_sampler_id}, |
| inst_it); |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_PUSH_INDEX_EXT) { |
| const VkDescriptorMappingSourcePushIndexEXT& map_data = mapping.sourceData.pushIndex; |
| |
| const uint32_t heap_offset = map_data.heapOffset + (binding_offset * map_data.samplerHeapOffset); |
| const uint32_t heap_offset_id = type_manager_.GetConstantUInt32(heap_offset).Id(); |
| const uint32_t push_offset_id = type_manager_.GetConstantUInt32(map_data.samplerPushOffset / 4).Id(); |
| const uint32_t heap_index_stride_id = type_manager_.GetConstantUInt32(map_data.samplerHeapIndexStride).Id(); |
| const uint32_t heap_array_stride_id = type_manager_.GetConstantUInt32(map_data.samplerHeapArrayStride).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_PUSH_INDEX); |
| |
| block.CreateInstruction(spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, push_offset_id, |
| heap_index_stride_id, heap_array_stride_id, descriptor_index_id, desc_alignment_id, is_sampler_id}, |
| inst_it); |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_EXT) { |
| const VkDescriptorMappingSourceIndirectIndexEXT& map_data = mapping.sourceData.indirectIndex; |
| |
| const uint32_t heap_offset = map_data.heapOffset + (binding_offset * map_data.samplerHeapOffset); |
| const uint32_t heap_offset_id = type_manager_.GetConstantUInt32(heap_offset).Id(); |
| const uint32_t push_offset_id = type_manager_.GetConstantUInt32(map_data.samplerPushOffset / 4).Id(); |
| const uint32_t address_offset_id = type_manager_.GetConstantUInt32(map_data.samplerAddressOffset / 4).Id(); |
| const uint32_t heap_index_stride_id = type_manager_.GetConstantUInt32(map_data.samplerHeapIndexStride).Id(); |
| const uint32_t heap_array_stride_id = type_manager_.GetConstantUInt32(map_data.samplerHeapArrayStride).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_INDIRECT_INDEX); |
| |
| block.CreateInstruction( |
| spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, push_offset_id, address_offset_id, |
| heap_index_stride_id, heap_array_stride_id, descriptor_index_id, desc_alignment_id, is_sampler_id}, |
| inst_it); |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_ARRAY_EXT) { |
| const VkDescriptorMappingSourceIndirectIndexArrayEXT& map_data = mapping.sourceData.indirectIndexArray; |
| |
| const uint32_t heap_offset_id = type_manager_.GetConstantUInt32(map_data.samplerHeapOffset).Id(); |
| const uint32_t push_offset_id = type_manager_.GetConstantUInt32(map_data.samplerPushOffset / 4).Id(); |
| const uint32_t frist_index_offset = (map_data.samplerAddressOffset / 4) + binding_offset; |
| const uint32_t address_offset_id = type_manager_.GetConstantUInt32(frist_index_offset).Id(); |
| const uint32_t heap_index_stride_id = type_manager_.GetConstantUInt32(map_data.samplerHeapIndexStride).Id(); |
| |
| const uint32_t function_def = GetLinkFunctionId(MAPPING_INDIRECT_INDEX_ARRAY); |
| |
| block.CreateInstruction(spv::OpFunctionCall, |
| {bool_type, function_result, function_def, inst_position_id, heap_offset_id, push_offset_id, |
| address_offset_id, heap_index_stride_id, descriptor_index_id, desc_alignment_id, is_sampler_id}, |
| inst_it); |
| } |
| |
| return function_result; |
| } |
| |
| bool DescriptorHeapPass::RequiresInstrumentation(const Function& function, const Instruction& inst, InstructionMeta& meta) { |
| meta.access_path = type_manager_.BuildAccessPath(function, inst); |
| if (!meta.access_path.IsValid() || !meta.access_path.variable->IsDescriptor()) { |
| return false; |
| } |
| if (meta.access_path.descriptor_type == gpuav::descriptor::TYPE_ACCELERATION_STRUCTURE) { |
| return false; // not supported yet |
| } |
| |
| // We look for mappings here incase we can't find it, we can skip safely |
| meta.mapping_index_resource = GetMapping(meta.access_path, false); |
| if (meta.access_path.sampler_variable) { |
| meta.mapping_index_sampler = GetMapping(meta.access_path, true); |
| } |
| if (meta.mapping_index_resource == kMappingIndexInvalid || meta.mapping_index_sampler == kMappingIndexInvalid) { |
| return false; |
| } |
| |
| meta.target_instruction = &inst; |
| |
| return true; |
| } |
| |
| bool DescriptorHeapPass::Instrument() { |
| for (Function& function : module_.functions_) { |
| if (!function.called_from_target_) { |
| continue; |
| } |
| |
| for (auto block_it = function.blocks_.begin(); block_it != function.blocks_.end(); ++block_it) { |
| BasicBlock& current_block = **block_it; |
| |
| cf_.Update(current_block); |
| if (debug_disable_loops_ && cf_.in_loop) { |
| continue; |
| } |
| |
| if (current_block.IsLoopHeader()) { |
| continue; // Currently can't properly handle injecting CFG logic into a loop header block |
| } |
| auto& block_instructions = current_block.instructions_; |
| |
| for (auto inst_it = block_instructions.begin(); inst_it != block_instructions.end(); ++inst_it) { |
| InstructionMeta meta; |
| // Every instruction is analyzed by the specific pass and lets us know if we need to inject a function or not |
| if (!RequiresInstrumentation(function, *(inst_it->get()), meta)) { |
| continue; |
| } |
| |
| if (MaxInstrumentationsCountReached()) { |
| return instrumentations_count_ != 0; |
| } |
| instrumentations_count_++; |
| |
| if (!module_.settings_.safe_mode) { |
| CreateFunctionCall(current_block, &inst_it, meta, false); |
| // This is just a dumb hack around iterators, for samplers we call a second function |
| if (meta.access_path.HasSampler()) { |
| inst_it++; |
| } |
| } else { |
| InjectConditionalData ic_data = InjectFunctionPre(function, block_it, inst_it); |
| ic_data.function_result_id = CreateFunctionCall(current_block, nullptr, meta, false); |
| InjectFunctionPost(current_block, ic_data); |
| // Skip the newly added valid and invalid block. Start searching again from newly split merge block |
| block_it++; |
| block_it++; |
| break; |
| } |
| } |
| } |
| } |
| |
| return instrumentations_count_ != 0; |
| } |
| |
| void DescriptorHeapPass::PrintDebugInfo() const { |
| std::cout << "DescriptorHeapPass instrumentation count: " << instrumentations_count_ << "\n"; |
| } |
| |
| } // namespace spirv |
| } // namespace gpuav |