| /* Copyright (c) 2026 Valve Corporation |
| * 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 "containers/container_utils.h" |
| #include "containers/limits.h" |
| #include "error_message/log_message_type.h" |
| #include "generated/error_location_helper.h" |
| #include "gpu_dump_state.h" |
| #include "gpu_dump.h" |
| #include <vulkan/vk_enum_string_helper.h> |
| #include <vulkan/vulkan_core.h> |
| #include <cstdint> |
| #include <iostream> |
| #include <sstream> |
| #include <algorithm> |
| #include "containers/range.h" |
| #include "containers/small_vector.h" |
| #include "generated/dispatch_functions.h" |
| #include "state_tracker/buffer_state.h" |
| #include "state_tracker/cmd_buffer_state.h" |
| #include "state_tracker/descriptor_mode.h" |
| #include "state_tracker/pipeline_layout_state.h" |
| #include "state_tracker/pipeline_state.h" |
| #include "state_tracker/shader_module.h" |
| #include "state_tracker/shader_object_state.h" |
| #include "state_tracker/shader_stage_state.h" |
| #include "state_tracker/state_tracker.h" |
| #include "utils/math_utils.h" |
| #include "utils/vk_api_utils.h" |
| |
| namespace gpudump { |
| |
| // Return true if found warning |
| bool CommandBufferSubState::DumpDescriptorBuffer(std::ostringstream& ss, const LastBound& last_bound) const { |
| const vvl::CommandBuffer& cb_state = last_bound.cb_state; |
| bool found_warning = false; |
| ss << "vkCmdBindDescriptorBuffersEXT last bound the following descriptor buffers:\n"; |
| for (uint32_t binding_i = 0; binding_i < cb_state.descriptor_buffer.binding_info.size(); binding_i++) { |
| const VkDeviceAddress address = cb_state.descriptor_buffer.binding_info[binding_i].address; |
| ss << " - pBindingInfos[" << std::dec << binding_i << "].address 0x" << std::hex << address << '\n'; |
| found_warning |= dev_data.ListBuffers(ss, address, 1); |
| } |
| |
| struct BindingInfo { |
| uint32_t index; |
| VkDescriptorType type; |
| uint32_t count; |
| uint32_t size; |
| VkDeviceSize offset; |
| vvl::range<VkDeviceAddress> range; |
| std::string variable_name; |
| |
| // We want to sort the bindings we print by their address |
| bool operator<(const BindingInfo& other) const { return range.begin < other.range.begin; } |
| |
| void Print(std::ostringstream& binding_ss, bool robust_buffer) { |
| binding_ss << " - Binding " << std::dec << index << " (" << string_VkDescriptorType(type) << ")"; |
| binding_ss << ", offset: " << offset << ", range: " << string_range_hex(range) << " ("; |
| if (count > 1) { |
| binding_ss << "descriptorCount [" << std::dec << count << "] * "; |
| } |
| binding_ss << DescribeDescriptorBufferSize(robust_buffer, type) << " [" << std::dec << size << "])\n"; |
| } |
| }; |
| |
| struct SetInfo { |
| uint32_t index; // set of the descriptor |
| uint32_t binding_index; // into pBindingInfos[] |
| vvl::range<VkDeviceAddress> range; |
| const vvl::DescriptorSetLayout* dsl; |
| std::vector<BindingInfo> bindings{}; |
| |
| // We want to sort the sets we print by their address |
| bool operator<(const SetInfo& other) const { return range.begin < other.range.begin; } |
| |
| void Print(std::ostringstream& set_ss) { |
| set_ss << " - Set " << std::dec << index << ", size: " << range.size() << " bytes, range: " << string_range_hex(range); |
| if (binding_index != vvl::kNoIndex32) { |
| // Only print if there are multiple bindings |
| set_ss << " (pBindingInfos[" << std::dec << binding_index << "])"; |
| } |
| set_ss << '\n'; |
| } |
| }; |
| |
| // Quick way to combine shaderObjects and pipelines |
| small_vector<const ShaderStageState*, 2> stages; |
| if (last_bound.pipeline_state) { |
| for (const ShaderStageState& stage : last_bound.pipeline_state->stage_states) { |
| stages.emplace_back(&stage); |
| } |
| } else { |
| for (const auto& shader_object : last_bound.shader_object_states) { |
| if (shader_object) { |
| stages.emplace_back(&shader_object->stage); |
| } |
| } |
| } |
| |
| // Can be a push constant only shader, which is valid here |
| // But if there are descriptors it is only valid if they are no accessed, which is warning territory |
| if (!last_bound.desc_set_pipeline_layout) { |
| ss << "No VkPipelineLayout found from a previous vkCmdSetDescriptorBufferOffsetsEXT call\n"; |
| |
| bool uses_descriptors = false; |
| for (const ShaderStageState* stage : stages) { |
| if (stage->HasSpirv() && !stage->entrypoint->resource_interface_variables.empty()) { |
| uses_descriptors = true; |
| break; |
| } |
| } |
| if (uses_descriptors) { |
| ss << "[WARNING] no vkCmdSetDescriptorBufferOffsetsEXT was called so any accesses to the descriptors in the shader " |
| "will be invalid.\n"; |
| // quickly check if they set the wrong bind point (only for the more common one) |
| if (last_bound.bind_point != VK_PIPELINE_BIND_POINT_GRAPHICS && |
| cb_state.GetLastBoundGraphics().desc_set_pipeline_layout) { |
| ss << " vkCmdSetDescriptorBufferOffsetsEXT was called with VK_PIPELINE_BIND_POINT_GRAPHICS, did you mean " |
| << string_VkPipelineBindPoint(last_bound.bind_point) << "?\n"; |
| } else if (last_bound.bind_point != VK_PIPELINE_BIND_POINT_COMPUTE && |
| cb_state.GetLastBoundCompute().desc_set_pipeline_layout) { |
| ss << " vkCmdSetDescriptorBufferOffsetsEXT was called with VK_PIPELINE_BIND_POINT_COMPUTE, did you mean " |
| << string_VkPipelineBindPoint(last_bound.bind_point) << "?\n"; |
| } else if (last_bound.bind_point != VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR && |
| cb_state.GetLastBoundRayTracing().desc_set_pipeline_layout) { |
| ss << " vkCmdSetDescriptorBufferOffsetsEXT was called with VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, did you mean " |
| << string_VkPipelineBindPoint(last_bound.bind_point) << "?\n"; |
| } |
| found_warning = true; |
| } |
| return found_warning; |
| } |
| |
| const vvl::PipelineLayout& pipeline_layout = *last_bound.desc_set_pipeline_layout; |
| ss << "vkCmdSetDescriptorBufferOffsetsEXT has bound the following with " << dev_data.FormatHandle(pipeline_layout.VkHandle()) |
| << ":\n"; |
| |
| for (const ShaderStageState* stage : stages) { |
| if (!stage->HasSpirv()) { |
| ss << "[No SPIR-V found for " << string_VkShaderStageFlagBits(stage->GetStage()) |
| << ", can't detect which descriptors are being accessed]\n"; |
| continue; |
| } |
| const spirv::EntryPoint& entry_point = *stage->entrypoint; |
| ss << entry_point.Describe() << "\n"; |
| |
| std::vector<SetInfo> sorted_sets; |
| sorted_sets.reserve(pipeline_layout.set_layouts.list.size()); |
| |
| for (const spirv::ResourceInterfaceVariable& resource_variable : entry_point.resource_interface_variables) { |
| const uint32_t var_set = resource_variable.decorations.set; |
| const uint32_t var_binding = resource_variable.decorations.binding; |
| |
| SetInfo* set_info = nullptr; |
| for (SetInfo& info : sorted_sets) { |
| if (info.index == var_set) { |
| set_info = &info; |
| break; |
| } |
| } |
| |
| if (set_info == nullptr) { |
| const vvl::DescriptorSetLayout* dsl = pipeline_layout.set_layouts.list[var_set].get(); |
| const auto& descriptor_buffer_binding = last_bound.ds_slots[var_set].descriptor_buffer_binding; |
| |
| // Will print the invalid/unknown sets first, no need to sort these |
| if (!dsl || dsl->Destroyed()) { |
| ss << " - [WARNING] Set " << std::dec << var_set |
| << " VkDescriptorSetLayout was destroyed (TODO - Track more info in pipeline layout)"; |
| found_warning = true; |
| continue; |
| } else if ((dsl->GetCreateFlags() & VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT) == 0) { |
| ss << " - [WARNING] Set " << std::dec << var_set |
| << " was not created with VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT"; |
| found_warning = true; |
| continue; |
| } else if (!descriptor_buffer_binding.has_value()) { |
| ss << " - [WARNING] Set " << std::dec << var_set |
| << " was never bound with offset. (WARNING - only valid if descriptor is not used in the shader"; |
| if (dsl->HasImmutableSamplers()) { |
| ss << " or because all bindings are using Immutable Samplers"; |
| } |
| ss << ")\n"; |
| found_warning = true; |
| continue; |
| } |
| |
| const VkDeviceAddress start_address = |
| cb_state.descriptor_buffer.binding_info[descriptor_buffer_binding->index].address + |
| descriptor_buffer_binding->offset; |
| const VkDeviceSize dsl_size = dsl->GetLayoutSizeInBytes(); |
| vvl::range<VkDeviceAddress> set_range{start_address, start_address + dsl_size}; |
| |
| // only care to print index in pBindingInfos |
| const uint32_t binding_index = |
| cb_state.descriptor_buffer.binding_info.size() > 1 ? descriptor_buffer_binding->index : vvl::kNoIndex32; |
| |
| set_info = &sorted_sets.emplace_back(SetInfo{var_set, binding_index, set_range, dsl, {}}); |
| } |
| |
| // To variables might be the same set/binding if doing descriptor indexing aliasing |
| bool alias_binding = false; |
| for (BindingInfo& info : set_info->bindings) { |
| if (info.index == var_binding && !info.variable_name.empty() && !resource_variable.debug_name.empty()) { |
| info.variable_name = info.variable_name + ", \"" + resource_variable.debug_name + "\""; |
| alias_binding = true; |
| break; |
| } |
| } |
| if (alias_binding) { |
| continue; |
| } |
| |
| const auto& ds_layout_def = *set_info->dsl->GetLayoutDef(); |
| const VkDescriptorType type = ds_layout_def.GetTypeFromBinding(var_binding); |
| const uint32_t count = ds_layout_def.GetDescriptorCountFromBinding(var_binding); |
| |
| const uint32_t descriptor_size = (uint32_t)GetDescriptorBufferSize(dev_data.phys_dev_ext_props.descriptor_buffer_props, |
| dev_data.enabled_features.robustBufferAccess, type); |
| VkDeviceSize binding_offset = 0; |
| DispatchGetDescriptorSetLayoutBindingOffsetEXT(dev_data.device, set_info->dsl->VkHandle(), var_binding, |
| &binding_offset); |
| const VkDeviceAddress binding_start_address = set_info->range.begin + binding_offset; |
| vvl::range<VkDeviceAddress> binding_range{binding_start_address, binding_start_address + (descriptor_size * count)}; |
| |
| set_info->bindings.emplace_back(BindingInfo{var_binding, type, count, descriptor_size, binding_offset, binding_range, |
| resource_variable.debug_name}); |
| } |
| |
| if (sorted_sets.empty()) { |
| ss << " - No descriptors were detected in the shader\n"; |
| continue; |
| } |
| |
| // Sort by address and print out everything |
| std::sort(sorted_sets.begin(), sorted_sets.end()); |
| for (SetInfo& set_info : sorted_sets) { |
| set_info.Print(ss); |
| |
| std::sort(set_info.bindings.begin(), set_info.bindings.end()); |
| for (BindingInfo& binding_info : set_info.bindings) { |
| binding_info.Print(ss, dev_data.enabled_features.robustBufferAccess); |
| } |
| } |
| } |
| return found_warning; |
| } |
| |
| struct MappingInfo { |
| const VkDescriptorSetAndBindingMappingEXT* mapping; |
| const spirv::ResourceInterfaceVariable* resource_variable; |
| uint32_t index; // into pMappings |
| |
| // Sort by binding |
| bool operator<(const MappingInfo& other) const { |
| return resource_variable->decorations.binding < other.resource_variable->decorations.binding; |
| } |
| }; |
| |
| static const vvl::Buffer& GetLargestBuffer(const GpuDump& dev_data, uint64_t address) { |
| auto buffer_states = dev_data.GetBuffersByAddress(address); |
| const vvl::Buffer* longer_buffer = nullptr; |
| VkDeviceAddress max_end = 0; |
| for (auto& buffer_state : buffer_states) { |
| VkDeviceAddress new_end = buffer_state->DeviceAddressRange().end; |
| if (new_end > max_end) { |
| max_end = new_end; |
| longer_buffer = buffer_state; |
| } |
| } |
| assert(longer_buffer); |
| return *longer_buffer; |
| } |
| |
| bool CommandBufferSubState::DumpDescriptorHeapMapping(std::ostringstream& ss, const MappingInfo& mapping_info) const { |
| const VkDescriptorSetAndBindingMappingEXT& mapping = *mapping_info.mapping; |
| const spirv::ResourceInterfaceVariable& resource_variable = *mapping_info.resource_variable; |
| vvl::CommandBuffer::DescriptorHeap& heap = base.descriptor_heap; |
| |
| ss << " - Binding " << std::dec << resource_variable.decorations.binding << ", " |
| << string_VkDescriptorMappingSourceEXT(mapping.source) << " (from pMappings[" << mapping_info.index << "])\n"; |
| const bool is_combined_image_sampler = resource_variable.is_combined_image_sampler; |
| const bool is_sampler = resource_variable.is_sampler; |
| const char* main_heap_type = is_sampler ? "Sampler" : "Resource"; |
| const vvl::range<VkDeviceAddress>& heap_range = is_sampler ? heap.sampler_range : heap.resource_range; |
| const vvl::range<VkDeviceAddress>& heap_reserved = is_sampler ? heap.sampler_reserved : heap.resource_reserved; |
| |
| const char* new_line = "\n "; |
| |
| const bool is_array = resource_variable.IsArray(); |
| const bool is_runtime_array = resource_variable.IsRuntimeArray(); |
| uint32_t array_length = 0; |
| if (is_array && !is_runtime_array && resource_variable.array_length != spirv::kSpecConstant) { |
| array_length = resource_variable.array_length; |
| } |
| |
| const VkDescriptorType descriptor_type = resource_variable.GetPotentialDescriptorType(); |
| // TODO - Cache these once on device creation |
| VkDeviceSize descriptor_size = descriptor_type == VK_DESCRIPTOR_TYPE_MAX_ENUM |
| ? 0 |
| : DispatchGetPhysicalDeviceDescriptorSizeEXT(dev_data.physical_device, descriptor_type); |
| |
| // TODO - Make common util if others need it |
| VkDeviceSize required_alignment = 0; |
| vvl::Field alignment_name = vvl::Field::Empty; |
| if (descriptor_type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR) { |
| required_alignment = 256; // from spec |
| } else if (descriptor_type == VK_DESCRIPTOR_TYPE_TENSOR_ARM) { |
| required_alignment = dev_data.phys_dev_ext_props.descriptor_heap_tensor_props.tensorDescriptorAlignment; |
| alignment_name = vvl::Field::tensorDescriptorAlignment; |
| } else if (descriptor_type == VK_DESCRIPTOR_TYPE_SAMPLER) { |
| required_alignment = dev_data.phys_dev_ext_props.descriptor_heap_props.samplerDescriptorAlignment; |
| alignment_name = vvl::Field::samplerDescriptorAlignment; |
| } else if (IsValueIn(descriptor_type, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, |
| VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER})) { |
| required_alignment = dev_data.phys_dev_ext_props.descriptor_heap_props.imageDescriptorAlignment; |
| alignment_name = vvl::Field::imageDescriptorAlignment; |
| } else if (IsValueIn(descriptor_type, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, |
| VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER})) { |
| required_alignment = dev_data.phys_dev_ext_props.descriptor_heap_props.bufferDescriptorAlignment; |
| alignment_name = vvl::Field::bufferDescriptorAlignment; |
| } |
| |
| // attempts to catch obvious OOB offsets in mappings |
| std::ostringstream warn_ss; |
| bool warn_indirect_buffer = false; |
| uint32_t warn_reserved_range_start = vvl::kNoIndex32; |
| uint32_t warn_reserved_range_end = vvl::kNoIndex32; |
| |
| auto warn_oob = [&](VkDeviceSize offset, bool from_sampler) { |
| if (from_sampler) { |
| if (offset > heap.sampler_range.size()) { |
| warn_ss |
| << new_line |
| << "[WARNING] OUT OF BOUNDS - descriptor not in sampler heap and any access to this descriptor will be invalid"; |
| } |
| } else { |
| if (offset > heap_range.size()) { |
| warn_ss << new_line |
| << "[WARNING] OUT OF BOUNDS - descriptor not in resource heap and any access to this descriptor will be " |
| "invalid"; |
| } |
| } |
| }; |
| |
| auto warn_alignment_scalar_indirect = [&](VkDeviceAddress address, VkDeviceSize alignment) { |
| if (!IsPointerAligned(address, alignment)) { |
| warn_ss << new_line << "[WARNING] MISALIGNED - the indirect address is not aligned to "; |
| if (alignment == 4) { |
| warn_ss << "4 (scalar alignment for a uint32_t)"; |
| } else if (alignment == 8) { |
| warn_ss << "8 (scalar alignment for a VkDeviceAddress)"; |
| } else { |
| assert(false); |
| } |
| warn_ss << " and any access to this descriptor will be invalid\n"; |
| } |
| }; |
| |
| auto warn_alignment_indirect_address = [&](VkDeviceAddress address, bool from_resource = false) { |
| warn_ss << new_line << "[WARNING] MISALIGNED - the "; |
| if (from_resource) { |
| warn_ss << "resource"; |
| } else { |
| warn_ss << "indirect"; |
| } |
| warn_ss << " address is not aligned to "; |
| |
| if (resource_variable.is_uniform_buffer) { |
| warn_ss << "minUniformBufferOffsetAlignment (0x" << std::hex |
| << dev_data.phys_dev_props.limits.minUniformBufferOffsetAlignment |
| << ") and any access to this descriptor will be invalid"; |
| } else if (resource_variable.is_storage_buffer) { |
| warn_ss << "minStorageBufferOffsetAlignment (0x" << std::hex |
| << dev_data.phys_dev_props.limits.minStorageBufferOffsetAlignment |
| << ") and any access to this descriptor will be invalid"; |
| } else if (resource_variable.is_acceleration_structure) { |
| // TODO |
| } |
| }; |
| |
| auto warn_alignment_descriptor = [&](VkDeviceAddress address) { |
| if (!IsPointerAligned(address, required_alignment)) { |
| warn_ss << new_line << "[WARNING] MISALIGNED - the final address"; |
| if (resource_variable.IsArray()) { |
| warn_ss << ", to the first element of the array,"; |
| } |
| warn_ss << " is not aligned to "; |
| if (alignment_name != vvl::Field::Empty) { |
| warn_ss << String(alignment_name) << " "; |
| } |
| warn_ss << "(0x" << std::hex << required_alignment << ") and any access to this descriptor will be invalid"; |
| } |
| }; |
| |
| auto warn_alignment_sampler = [&](VkDeviceAddress address) { |
| if (!IsPointerAligned(address, dev_data.phys_dev_ext_props.descriptor_heap_props.samplerDescriptorAlignment)) { |
| warn_ss << new_line << "[WARNING] MISALIGNED - the final address"; |
| if (resource_variable.IsArray()) { |
| warn_ss << ", to the first element of the array,"; |
| } |
| warn_ss << " is not aligned to samplerDescriptorAlignment " << "(0x" << std::hex |
| << dev_data.phys_dev_ext_props.descriptor_heap_props.samplerDescriptorAlignment |
| << ") and any access to this descriptor will be invalid"; |
| } |
| }; |
| |
| auto warn_index_oob = [&](uint32_t max_index) { |
| if (array_length > (max_index + 1)) { |
| warn_ss << new_line << "[WARNING] OUT OF BOUNDS - descriptor has an array length of [" << std::dec << array_length |
| << "] but any element accessed starting at [" << max_index + 1 << std::hex << "] will be invalid if accessed"; |
| } |
| }; |
| |
| auto warn_index_array = [&](std::vector<uint32_t>& bad_indexes) { |
| if (!bad_indexes.empty()) { |
| warn_ss << new_line << "[WARNING] OUT OF BOUNDS - descriptors indexes at [" << std::dec; |
| for (uint32_t i = 0; i < bad_indexes.size(); i++) { |
| if (i != 0) warn_ss << ", "; |
| warn_ss << bad_indexes[i]; |
| } |
| warn_ss << std::hex << "] will be invalid if accessed"; |
| } |
| }; |
| |
| auto warn_alignment_index_array = [&](std::vector<uint32_t>& bad_indexes) { |
| if (!bad_indexes.empty()) { |
| warn_ss << new_line << "[WARNING] MISALIGNED - descriptors indexes at [" << std::dec; |
| for (uint32_t i = 0; i < bad_indexes.size(); i++) { |
| if (i != 0) warn_ss << ", "; |
| warn_ss << bad_indexes[i]; |
| } |
| warn_ss << "] will not be aligned to "; |
| if (alignment_name != vvl::Field::Empty) { |
| warn_ss << String(alignment_name) << " "; |
| } |
| warn_ss << "(0x" << std::hex << required_alignment << ") and any access to this descriptor will be invalid"; |
| } |
| }; |
| |
| auto warn_reserved_range_index_array = [&](std::vector<uint32_t>& bad_indexes) { |
| if (!bad_indexes.empty()) { |
| warn_ss << new_line << "[WARNING] RESERVE RANGE - descriptors indexes at [" << std::dec; |
| for (uint32_t i = 0; i < bad_indexes.size(); i++) { |
| if (i != 0) warn_ss << ", "; |
| warn_ss << bad_indexes[i]; |
| } |
| warn_ss << std::hex << "] will overlap with the reserved range is any access will be invalid"; |
| } |
| }; |
| |
| auto warn_alignment_index_array_sampler = [&](std::vector<uint32_t>& bad_indexes) { |
| if (!bad_indexes.empty()) { |
| warn_ss << new_line << "[WARNING] MISALIGNED - descriptors indexes at [" << std::dec; |
| for (uint32_t i = 0; i < bad_indexes.size(); i++) { |
| if (i != 0) warn_ss << ", "; |
| warn_ss << bad_indexes[i]; |
| } |
| warn_ss << "] will not be aligned to samplerDescriptorAlignment (0x" << std::hex << required_alignment |
| << ") and any access to this descriptor will be invalid"; |
| } |
| }; |
| |
| ss << " - "; |
| if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT) { |
| const VkDescriptorMappingSourceConstantOffsetEXT& map_data = mapping.sourceData.constantOffset; |
| |
| ss << "heapOffset: 0x" << std::hex << map_data.heapOffset << ", heapArrayStride: 0x" << map_data.heapArrayStride; |
| VkDeviceSize index_zero_offset = map_data.heapOffset; |
| // CONSTANT_OFFSET will be fully caught already in normal VVL if alignment is off |
| VkDeviceAddress index_zero_address = heap_range.begin + index_zero_offset; |
| ss << new_line << main_heap_type << " Heap address: 0x" << index_zero_address; |
| if (is_array) { |
| ss << " + (descriptor_index * 0x" << map_data.heapArrayStride << ")"; |
| const VkDeviceSize available_space = (heap_range.size() - index_zero_offset) - descriptor_size; |
| const uint32_t max_index = (uint32_t)(available_space / map_data.heapArrayStride); |
| if (array_length != 0) { |
| const VkDeviceSize final_array_offset = (array_length - 1) * map_data.heapArrayStride; |
| ss << new_line << " The final descriptor index at [" << std::dec << array_length << std::hex |
| << "] will access [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| warn_index_oob(max_index); |
| } else if (is_runtime_array) { |
| const VkDeviceSize final_array_offset = (max_index * map_data.heapArrayStride); |
| ss << new_line << " The final descriptor index in bounds is [" << std::dec << max_index << std::hex |
| << "] which would be accessed at [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| } |
| |
| if (!heap_reserved.empty()) { |
| const uint32_t max_search_index = array_length != 0 ? array_length : max_index + 1; |
| for (uint32_t i = 0; i < max_search_index; i++) { |
| // TODO - be smart where to start searching if reserve range is at the end of huge buffer |
| VkDeviceAddress next_index_address = index_zero_address + (i * map_data.heapArrayStride); |
| vvl::range<VkDeviceAddress> next_index_range{next_index_address, next_index_address + descriptor_size}; |
| if (next_index_range.intersects(heap_reserved)) { |
| if (warn_reserved_range_start == vvl::kNoIndex32) { |
| warn_reserved_range_start = i; |
| } |
| warn_reserved_range_end = i; |
| } else if (warn_reserved_range_end != vvl::kNoIndex32) { |
| break; // found end of reserved range |
| } |
| } |
| } |
| } else if (!heap_reserved.empty()) { |
| vvl::range<VkDeviceAddress> index_zero_range{index_zero_address, index_zero_address + descriptor_size}; |
| if (index_zero_range.intersects(heap_reserved)) { |
| warn_reserved_range_start = 0; |
| } |
| } |
| |
| warn_oob(index_zero_offset + descriptor_size, false); |
| |
| if (is_combined_image_sampler) { |
| ss << new_line << "samplerHeapOffset: 0x" << std::hex << map_data.samplerHeapOffset << ", samplerHeapArrayStride: 0x" |
| << map_data.samplerHeapArrayStride; |
| index_zero_offset = map_data.samplerHeapOffset; |
| index_zero_address = heap.sampler_range.begin + index_zero_offset; |
| ss << new_line << "Sampler Heap address: 0x" << index_zero_address; |
| if (is_array) { |
| ss << " + (descriptor_index * 0x" << map_data.samplerHeapArrayStride << ")"; |
| const VkDeviceSize available_space = (heap.sampler_range.size() - index_zero_offset) - descriptor_size; |
| const uint32_t max_index = (uint32_t)(available_space / map_data.samplerHeapArrayStride); |
| if (array_length != 0) { |
| const VkDeviceSize final_array_offset = (array_length - 1) * map_data.samplerHeapArrayStride; |
| ss << new_line << " The final descriptor index at [" << std::dec << array_length << std::hex |
| << "] will access [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| warn_index_oob(max_index); |
| } else if (is_runtime_array) { |
| const VkDeviceSize final_array_offset = |
| (map_data.samplerHeapOffset + (max_index * map_data.samplerHeapArrayStride)); |
| ss << new_line << " The final descriptor index in bounds is [" << std::dec << max_index << std::hex |
| << "] which would be accessed at [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| } |
| |
| if (!heap.sampler_reserved.empty() && warn_reserved_range_start == vvl::kNoIndex32) { |
| const uint32_t max_search_index = array_length != 0 ? array_length : max_index + 1; |
| for (uint32_t i = 0; i < max_search_index; i++) { |
| VkDeviceAddress next_index_address = index_zero_address + (i * map_data.samplerHeapArrayStride); |
| vvl::range<VkDeviceAddress> next_index_range{next_index_address, next_index_address + descriptor_size}; |
| if (next_index_range.intersects(heap.sampler_reserved)) { |
| if (warn_reserved_range_start == vvl::kNoIndex32) { |
| warn_reserved_range_start = i; |
| } |
| warn_reserved_range_end = i; |
| } else if (warn_reserved_range_end != vvl::kNoIndex32) { |
| break; // found end of reserved range |
| } |
| } |
| } |
| } else if (!heap.sampler_reserved.empty()) { |
| vvl::range<VkDeviceAddress> index_zero_range{index_zero_address, index_zero_address + descriptor_size}; |
| if (index_zero_range.intersects(heap.sampler_reserved)) { |
| warn_reserved_range_start = 0; |
| } |
| } |
| warn_oob(index_zero_offset + descriptor_size, true); |
| } |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_PUSH_INDEX_EXT) { |
| const VkDescriptorMappingSourcePushIndexEXT& map_data = mapping.sourceData.pushIndex; |
| uint32_t push_index = *((uint32_t*)&push_data_value[map_data.pushOffset]); |
| |
| ss << "pushOffset: 0x" << std::hex << map_data.pushOffset << ", heapOffset: 0x" << map_data.heapOffset |
| << ", heapIndexStride: 0x" << map_data.heapIndexStride << ", heapArrayStride: 0x" << map_data.heapArrayStride; |
| ss << new_line << "pushIndex: 0x" << push_index; |
| ss << new_line << main_heap_type << " Heap address: 0x" << heap.resource_range.begin + map_data.heapOffset << " + (0x" |
| << push_index << " * 0x" << map_data.heapIndexStride << ")"; |
| VkDeviceSize index_zero_offset = map_data.heapOffset + (push_index * map_data.heapIndexStride); |
| VkDeviceAddress index_zero_address = heap_range.begin + index_zero_offset; |
| |
| warn_alignment_descriptor(index_zero_address); |
| |
| if (is_array) { |
| ss << " + (descriptor_index * 0x" << map_data.heapArrayStride << ")"; |
| const VkDeviceSize available_space = (heap_range.size() - index_zero_offset) - descriptor_size; |
| const uint32_t max_index = (uint32_t)(available_space / map_data.heapArrayStride); |
| if (array_length != 0) { |
| const VkDeviceSize final_array_offset = (array_length - 1) * map_data.heapArrayStride; |
| ss << new_line << " The final descriptor index at [" << std::dec << array_length << std::hex |
| << "] will access [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| warn_index_oob(max_index); |
| } else if (is_runtime_array) { |
| const VkDeviceSize final_array_offset = max_index * map_data.heapArrayStride; |
| ss << new_line << " The final descriptor index in bounds is [" << std::dec << max_index << std::hex |
| << "] which would be accessed at [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| } |
| |
| if (!heap_reserved.empty()) { |
| const uint32_t max_search_index = array_length != 0 ? array_length : max_index + 1; |
| for (uint32_t i = 0; i < max_search_index; i++) { |
| // TODO - be smart where to start searching if reserve range is at the end of huge buffer |
| VkDeviceAddress next_index_address = index_zero_address + (i * map_data.heapArrayStride); |
| vvl::range<VkDeviceAddress> next_index_range{next_index_address, next_index_address + descriptor_size}; |
| if (next_index_range.intersects(heap_reserved)) { |
| if (warn_reserved_range_start == vvl::kNoIndex32) { |
| warn_reserved_range_start = i; |
| } |
| warn_reserved_range_end = i; |
| } else if (warn_reserved_range_end != vvl::kNoIndex32) { |
| break; // found end of reserved range |
| } |
| } |
| } |
| } else { |
| ss << " [final address 0x" << index_zero_address << "]"; |
| |
| if (!heap_reserved.empty()) { |
| vvl::range<VkDeviceAddress> index_zero_range{index_zero_address, index_zero_address + descriptor_size}; |
| if (index_zero_range.intersects(heap_reserved)) { |
| warn_reserved_range_start = 0; |
| } |
| } |
| } |
| warn_oob(index_zero_offset + descriptor_size, false); |
| |
| if (is_combined_image_sampler) { |
| ss << new_line << "pushOffset: 0x" << std::hex << map_data.pushOffset << ", samplerHeapOffset: 0x" |
| << map_data.samplerHeapOffset << ", samplerHeapIndexStride: 0x" << map_data.samplerHeapIndexStride |
| << ", samplerHeapArrayStride: 0x" << map_data.samplerHeapArrayStride; |
| ss << new_line << "pushIndex: 0x" << push_index; |
| ss << new_line << "Sampler Heap address: 0x" << heap.sampler_range.begin + map_data.samplerHeapOffset << " + (0x" |
| << push_index << " * 0x" << map_data.samplerHeapIndexStride << ")"; |
| index_zero_offset = map_data.samplerHeapOffset + (push_index * map_data.samplerHeapIndexStride); |
| index_zero_address = heap.sampler_range.begin + index_zero_offset; |
| |
| warn_alignment_sampler(index_zero_address); |
| |
| if (is_array) { |
| ss << " + (descriptor_index * 0x" << map_data.samplerHeapArrayStride << ")"; |
| const VkDeviceSize available_space = (heap.sampler_range.size() - index_zero_offset) - descriptor_size; |
| const uint32_t max_index = (uint32_t)(available_space / map_data.samplerHeapArrayStride); |
| if (array_length != 0) { |
| const VkDeviceSize final_array_offset = (array_length - 1) * map_data.samplerHeapArrayStride; |
| ss << new_line << " The final descriptor index at [" << std::dec << array_length << std::hex |
| << "] will access [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| warn_index_oob(max_index); |
| } else if (is_runtime_array) { |
| const VkDeviceSize final_array_offset = max_index * map_data.samplerHeapArrayStride; |
| ss << new_line << " The final descriptor index in bounds is [" << std::dec << max_index << std::hex |
| << "] which would be accessed at [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| } |
| |
| if (!heap.sampler_reserved.empty() && warn_reserved_range_start == vvl::kNoIndex32) { |
| const uint32_t max_search_index = array_length != 0 ? array_length : max_index + 1; |
| for (uint32_t i = 0; i < max_search_index; i++) { |
| VkDeviceAddress next_index_address = index_zero_address + (i * map_data.samplerHeapArrayStride); |
| vvl::range<VkDeviceAddress> next_index_range{next_index_address, next_index_address + descriptor_size}; |
| if (next_index_range.intersects(heap.sampler_reserved)) { |
| if (warn_reserved_range_start == vvl::kNoIndex32) { |
| warn_reserved_range_start = i; |
| } |
| warn_reserved_range_end = i; |
| } else if (warn_reserved_range_end != vvl::kNoIndex32) { |
| break; // found end of reserved range |
| } |
| } |
| } |
| } else { |
| ss << " [final address 0x" << index_zero_address << "]"; |
| |
| if (!heap.sampler_reserved.empty()) { |
| vvl::range<VkDeviceAddress> index_zero_range{index_zero_address, index_zero_address + descriptor_size}; |
| if (index_zero_range.intersects(heap.sampler_reserved)) { |
| warn_reserved_range_start = 0; |
| } |
| } |
| } |
| warn_oob(index_zero_offset + descriptor_size, true); |
| } |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_EXT) { |
| const VkDescriptorMappingSourceIndirectIndexEXT& map_data = mapping.sourceData.indirectIndex; |
| VkDeviceAddress push_indirect_address = *((VkDeviceAddress*)&push_data_value[map_data.pushOffset]); |
| VkDeviceAddress final_indirect_address = push_indirect_address + map_data.addressOffset; |
| |
| warn_alignment_scalar_indirect(final_indirect_address, 4); |
| |
| std::vector<uint8_t> indirect_index_data = dev_data.CopyDataFromMemory(final_indirect_address, 4); |
| bool know_ubo = !indirect_index_data.empty(); |
| uint32_t indirect_index = know_ubo ? *((uint32_t*)indirect_index_data.data()) : 0; |
| |
| ss << "pushOffset: 0x" << std::hex << map_data.pushOffset << ", addressOffset: 0x" << map_data.addressOffset |
| << ", heapOffset: 0x" << map_data.heapOffset << ", heapIndexStride: 0x" << map_data.heapIndexStride |
| << ", heapArrayStride: 0x" << map_data.heapArrayStride; |
| ss << new_line << "indirectAddress: 0x" << final_indirect_address << " (0x" << push_indirect_address << " + 0x" |
| << map_data.addressOffset << ")"; |
| |
| warn_indirect_buffer |= dev_data.ListBuffers(ss, final_indirect_address, 3, true); |
| |
| if (know_ubo) { |
| ss << new_line << "indirectIndex: 0x" << indirect_index; |
| } |
| ss << new_line << main_heap_type << " Heap address: 0x" << heap_range.begin + map_data.heapOffset << " + ("; |
| if (know_ubo) { |
| ss << "0x" << indirect_index; |
| } else { |
| ss << "indirectIndex"; |
| } |
| ss << " * 0x" << map_data.heapIndexStride << ")"; |
| if (is_array) { |
| ss << " + (descriptor_index * 0x" << map_data.heapArrayStride << ")"; |
| |
| if (know_ubo) { |
| VkDeviceSize index_zero_offset = map_data.heapOffset + (indirect_index * map_data.heapIndexStride); |
| VkDeviceAddress index_zero_address = heap_range.begin + index_zero_offset; |
| warn_alignment_descriptor(index_zero_address); |
| |
| VkDeviceSize available_space = (heap_range.size() - index_zero_offset) - descriptor_size; |
| uint32_t max_index = (uint32_t)(available_space / map_data.heapArrayStride); |
| if (array_length != 0) { |
| const VkDeviceSize final_array_offset = (array_length - 1) * map_data.heapArrayStride; |
| ss << new_line << " The final descriptor index at [" << std::dec << array_length << std::hex |
| << "] will access [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| warn_index_oob(max_index); |
| } else if (is_runtime_array) { |
| const VkDeviceSize final_array_offset = max_index * map_data.heapArrayStride; |
| ss << new_line << " The final descriptor index in bounds is [" << std::dec << max_index << std::hex |
| << "] which would be accessed at [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| } |
| |
| if (!heap_reserved.empty()) { |
| const uint32_t max_search_index = array_length != 0 ? array_length : max_index + 1; |
| for (uint32_t i = 0; i < max_search_index; i++) { |
| // TODO - be smart where to start searching if reserve range is at the end of huge buffer |
| VkDeviceAddress next_index_address = index_zero_address + (i * map_data.heapArrayStride); |
| vvl::range<VkDeviceAddress> next_index_range{next_index_address, next_index_address + descriptor_size}; |
| if (next_index_range.intersects(heap_reserved)) { |
| if (warn_reserved_range_start == vvl::kNoIndex32) { |
| warn_reserved_range_start = i; |
| } |
| warn_reserved_range_end = i; |
| } else if (warn_reserved_range_end != vvl::kNoIndex32) { |
| break; // found end of reserved range |
| } |
| } |
| } |
| } |
| } else if (know_ubo) { |
| VkDeviceAddress final_offset = map_data.heapOffset + (indirect_index * map_data.heapIndexStride); |
| VkDeviceAddress index_zero_address = heap_range.begin + final_offset; |
| warn_alignment_descriptor(index_zero_address); |
| |
| ss << " [final address 0x" << (heap_range.begin + final_offset) << "]"; |
| |
| warn_oob(final_offset + descriptor_size, false); |
| |
| if (!heap_reserved.empty()) { |
| vvl::range<VkDeviceAddress> index_zero_range{index_zero_address, index_zero_address + descriptor_size}; |
| if (index_zero_range.intersects(heap_reserved)) { |
| warn_reserved_range_start = 0; |
| } |
| } |
| } |
| |
| warn_oob(map_data.heapOffset + descriptor_size, false); |
| |
| if (is_combined_image_sampler) { |
| push_indirect_address = *((VkDeviceAddress*)&push_data_value[map_data.samplerPushOffset]); |
| final_indirect_address = push_indirect_address + map_data.samplerAddressOffset; |
| |
| warn_alignment_scalar_indirect(final_indirect_address, 4); |
| |
| indirect_index_data = dev_data.CopyDataFromMemory(final_indirect_address, 4); |
| know_ubo = !indirect_index_data.empty(); |
| indirect_index = know_ubo ? *((uint32_t*)indirect_index_data.data()) : 0; |
| |
| ss << new_line << "samplerPushOffset: 0x" << std::hex << map_data.samplerPushOffset << ", samplerAddressOffset: 0x" |
| << map_data.samplerAddressOffset << ", samplerHeapOffset: 0x" << map_data.samplerHeapOffset |
| << ", samplerHeapIndexStride: 0x" << map_data.samplerHeapIndexStride << ", samplerHeapArrayStride: 0x" |
| << map_data.samplerHeapArrayStride; |
| ss << new_line << "indirectAddress: 0x" << final_indirect_address << " (0x" << push_indirect_address << " + 0x" |
| << map_data.samplerAddressOffset << ")"; |
| |
| warn_indirect_buffer |= dev_data.ListBuffers(ss, final_indirect_address, 3, true); |
| |
| if (know_ubo) { |
| ss << new_line << "indirectIndex: 0x" << indirect_index; |
| } |
| ss << new_line << "Sampler Heap address: 0x" << heap.sampler_range.begin + map_data.samplerHeapOffset << " + ("; |
| if (know_ubo) { |
| ss << "0x" << indirect_index; |
| } else { |
| ss << "indirectIndex"; |
| } |
| ss << " * 0x" << map_data.samplerHeapIndexStride << ")"; |
| if (is_array) { |
| ss << " + (descriptor_index * 0x" << map_data.samplerHeapArrayStride << ")"; |
| |
| if (know_ubo) { |
| VkDeviceSize index_zero_offset = |
| map_data.samplerHeapOffset + (indirect_index * map_data.samplerHeapIndexStride); |
| VkDeviceAddress index_zero_address = heap.sampler_range.begin + index_zero_offset; |
| warn_alignment_sampler(index_zero_address); |
| |
| VkDeviceSize available_space = (heap.sampler_range.size() - index_zero_offset) - descriptor_size; |
| uint32_t max_index = (uint32_t)(available_space / map_data.samplerHeapArrayStride); |
| if (array_length != 0) { |
| const VkDeviceSize final_array_offset = (array_length - 1) * map_data.samplerHeapArrayStride; |
| ss << new_line << " The final descriptor index at [" << std::dec << array_length << std::hex |
| << "] will access [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| warn_index_oob(max_index); |
| } else if (is_runtime_array) { |
| const VkDeviceSize final_array_offset = max_index * map_data.samplerHeapArrayStride; |
| ss << new_line << " The final descriptor index in bounds is [" << std::dec << max_index << std::hex |
| << "] which would be accessed at [0x" << (index_zero_address + final_array_offset) << ", 0x" |
| << (index_zero_address + final_array_offset + descriptor_size) << ")"; |
| } |
| |
| if (!heap.sampler_reserved.empty() && warn_reserved_range_start == vvl::kNoIndex32) { |
| const uint32_t max_search_index = array_length != 0 ? array_length : max_index + 1; |
| for (uint32_t i = 0; i < max_search_index; i++) { |
| VkDeviceAddress next_index_address = index_zero_address + (i * map_data.samplerHeapArrayStride); |
| vvl::range<VkDeviceAddress> next_index_range{next_index_address, next_index_address + descriptor_size}; |
| if (next_index_range.intersects(heap.sampler_reserved)) { |
| if (warn_reserved_range_start == vvl::kNoIndex32) { |
| warn_reserved_range_start = i; |
| } |
| warn_reserved_range_end = i; |
| } else if (warn_reserved_range_end != vvl::kNoIndex32) { |
| break; // found end of reserved range |
| } |
| } |
| } |
| } |
| } else if (know_ubo) { |
| VkDeviceAddress final_offset = map_data.samplerHeapOffset + (indirect_index * map_data.samplerHeapIndexStride); |
| VkDeviceAddress index_zero_address = heap.sampler_range.begin + final_offset; |
| warn_alignment_sampler(index_zero_address); |
| |
| ss << " [final address 0x" << (index_zero_address) << "]"; |
| warn_oob(final_offset + descriptor_size, true); |
| |
| if (!heap.sampler_range.empty()) { |
| vvl::range<VkDeviceAddress> index_zero_range{index_zero_address, index_zero_address + descriptor_size}; |
| if (index_zero_range.intersects(heap.sampler_range)) { |
| warn_reserved_range_start = 0; |
| } |
| } |
| } |
| |
| warn_oob(map_data.samplerHeapOffset + descriptor_size, true); |
| } |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_ARRAY_EXT) { |
| const VkDescriptorMappingSourceIndirectIndexArrayEXT& map_data = mapping.sourceData.indirectIndexArray; |
| VkDeviceAddress push_indirect_address = *((VkDeviceAddress*)&push_data_value[map_data.pushOffset]); |
| VkDeviceAddress final_indirect_address = push_indirect_address + map_data.addressOffset; |
| |
| warn_alignment_scalar_indirect(final_indirect_address, 4); |
| |
| ss << "pushOffset: 0x" << std::hex << map_data.pushOffset << ", addressOffset: 0x" << map_data.addressOffset |
| << ", heapOffset: 0x" << map_data.heapOffset << ", heapIndexStride: 0x" << map_data.heapIndexStride; |
| ss << new_line << "indirectAddress: 0x" << final_indirect_address << " (0x" << push_indirect_address << " + 0x" |
| << map_data.addressOffset << ")"; |
| |
| warn_indirect_buffer |= dev_data.ListBuffers(ss, final_indirect_address, 3, true); |
| |
| ss << new_line << main_heap_type << " Heap address: 0x" << heap_range.begin + map_data.heapOffset |
| << " + (indirectIndex * 0x" << map_data.heapIndexStride << ")"; |
| |
| const vvl::Buffer& buffer_state = GetLargestBuffer(dev_data, final_indirect_address); |
| uint32_t available_bytes = (uint32_t)(buffer_state.DeviceAddressRange().end - final_indirect_address); |
| uint32_t available_slots = available_bytes / sizeof(uint32_t); |
| // We can assume this is an array, otherwise, not sure why people are using this mapping |
| uint32_t search_slots = !is_array ? 1 : is_runtime_array ? available_slots : array_length; |
| |
| if (array_length != 0 && array_length > available_slots) { |
| warn_index_oob(available_slots - 1); // will report warning |
| search_slots = available_slots; |
| } |
| uint32_t search_bytes = search_slots * sizeof(uint32_t); |
| |
| std::vector<uint8_t> indirect_index_data = dev_data.CopyDataFromMemory(final_indirect_address, search_bytes); |
| bool know_ubo = !indirect_index_data.empty(); |
| |
| if (is_runtime_array) { |
| // Currently don't go searching in, no way to know desired upper bound |
| ss << new_line << " Any descriptor index starting at [" << std::dec << search_slots << std::hex |
| << "] will be invalid as there are no more values found for indirectIndex inside " |
| << dev_data.FormatHandle(buffer_state.Handle()); |
| } |
| |
| if (know_ubo) { |
| uint32_t* indirect_index_words = (uint32_t*)indirect_index_data.data(); |
| if (!is_array) { |
| uint32_t indirect_index = indirect_index_words[0]; |
| VkDeviceSize final_offset = map_data.heapOffset + (indirect_index * map_data.heapIndexStride); |
| VkDeviceAddress final_address = heap_range.begin + final_offset; |
| warn_alignment_descriptor(final_address); |
| |
| ss << " [final address 0x" << final_address << " where indirectIndex is 0x" << indirect_index << "]"; |
| warn_oob(final_offset + descriptor_size, false); |
| } else if (!is_runtime_array) { |
| // Runtime arrays are unbounded and not idea where to stop looking, |
| // can add if people find valuable. |
| ss << new_line << "indirectIndex values from buffer: ["; |
| std::vector<uint32_t> bad_array_indexes; |
| std::vector<uint32_t> bad_alignment_indexes; |
| std::vector<uint32_t> bad_reserve_indexes; |
| for (uint32_t i = 0; i < search_slots; i++) { |
| const uint32_t current_index_value = indirect_index_words[i]; |
| VkDeviceSize final_offset = map_data.heapOffset + (current_index_value * map_data.heapIndexStride); |
| if (final_offset + descriptor_size > heap_range.size()) { |
| bad_array_indexes.emplace_back(i); |
| } |
| if (i != 0) ss << ", "; |
| ss << "0x" << current_index_value; |
| |
| VkDeviceAddress next_index_address = heap_range.begin + final_offset; |
| if (!IsPointerAligned(next_index_address, required_alignment)) { |
| bad_alignment_indexes.emplace_back(i); |
| } |
| |
| if (!heap_reserved.empty()) { |
| vvl::range<VkDeviceAddress> next_index_range{next_index_address, next_index_address + descriptor_size}; |
| if (next_index_range.intersects(heap_reserved)) { |
| bad_reserve_indexes.emplace_back(i); |
| } |
| } |
| } |
| warn_index_array(bad_array_indexes); |
| warn_alignment_index_array(bad_alignment_indexes); |
| warn_reserved_range_index_array(bad_reserve_indexes); |
| ss << "]"; |
| } |
| } |
| warn_oob(map_data.heapOffset + descriptor_size, false); |
| |
| if (is_combined_image_sampler) { |
| push_indirect_address = *((VkDeviceAddress*)&push_data_value[map_data.samplerPushOffset]); |
| final_indirect_address = push_indirect_address + map_data.samplerAddressOffset; |
| |
| warn_alignment_scalar_indirect(final_indirect_address, 4); |
| |
| ss << new_line << "samplerPushOffset: 0x" << std::hex << map_data.samplerPushOffset << ", samplerAddressOffset: 0x" |
| << map_data.samplerAddressOffset << ", samplerHeapOffset: 0x" << map_data.samplerHeapOffset |
| << ", samplerHeapIndexStride: 0x" << map_data.samplerHeapIndexStride; |
| ss << new_line << "indirectAddress: 0x" << final_indirect_address << " (0x" << push_indirect_address << " + 0x" |
| << map_data.samplerAddressOffset << ")"; |
| |
| warn_indirect_buffer |= dev_data.ListBuffers(ss, final_indirect_address, 3, true); |
| |
| ss << new_line << "Sampler Heap address: 0x" << heap.sampler_range.begin + map_data.samplerHeapOffset |
| << " + (indirectIndex * 0x" << map_data.samplerHeapIndexStride << ")"; |
| |
| const vvl::Buffer& sampler_buffer_state = GetLargestBuffer(dev_data, final_indirect_address); |
| available_bytes = (uint32_t)(sampler_buffer_state.DeviceAddressRange().end - final_indirect_address); |
| available_slots = available_bytes / sizeof(uint32_t); |
| search_slots = !is_array ? 1 : is_runtime_array ? available_slots : array_length; |
| |
| if (array_length != 0 && array_length > available_slots) { |
| warn_index_oob(available_slots - 1); // will report warning |
| search_slots = available_slots; |
| } |
| search_bytes = search_slots * sizeof(uint32_t); |
| |
| indirect_index_data = dev_data.CopyDataFromMemory(final_indirect_address, search_bytes); |
| know_ubo = !indirect_index_data.empty(); |
| |
| if (is_runtime_array) { |
| ss << new_line << " Any descriptor index starting at [" << std::dec << search_slots << std::hex |
| << "] will be invalid as there are no more values found for indirectIndex inside " |
| << dev_data.FormatHandle(sampler_buffer_state.Handle()); |
| } |
| |
| if (know_ubo) { |
| uint32_t* indirect_index_words = (uint32_t*)indirect_index_data.data(); |
| if (!is_array) { |
| uint32_t indirect_index = indirect_index_words[0]; |
| VkDeviceSize final_offset = map_data.samplerHeapOffset + (indirect_index * map_data.samplerHeapIndexStride); |
| VkDeviceAddress final_address = heap.sampler_range.begin + final_offset; |
| warn_alignment_sampler(final_address); |
| |
| ss << " [final address 0x" << final_address << "]"; |
| warn_oob(final_offset + descriptor_size, true); |
| } else if (!is_runtime_array) { |
| ss << new_line << "indirectIndex values from buffer: ["; |
| std::vector<uint32_t> bad_array_indexes; |
| std::vector<uint32_t> bad_alignment_indexes; |
| std::vector<uint32_t> bad_reserve_indexes; |
| for (uint32_t i = 0; i < search_slots; i++) { |
| const uint32_t current_index_value = indirect_index_words[i]; |
| VkDeviceSize final_offset = |
| map_data.samplerHeapOffset + (current_index_value * map_data.samplerHeapIndexStride); |
| if (final_offset + descriptor_size > heap.sampler_range.size()) { |
| bad_array_indexes.emplace_back(i); |
| } |
| if (i != 0) ss << ", "; |
| ss << "0x" << current_index_value; |
| |
| VkDeviceAddress next_index_address = heap.sampler_range.begin + final_offset; |
| if (!IsPointerAligned(next_index_address, |
| dev_data.phys_dev_ext_props.descriptor_heap_props.samplerDescriptorAlignment)) { |
| bad_alignment_indexes.emplace_back(i); |
| } |
| |
| if (!heap.sampler_reserved.empty()) { |
| vvl::range<VkDeviceAddress> next_index_range{next_index_address, next_index_address + descriptor_size}; |
| if (next_index_range.intersects(heap.sampler_reserved)) { |
| bad_reserve_indexes.emplace_back(i); |
| } |
| } |
| } |
| warn_index_array(bad_array_indexes); |
| warn_alignment_index_array_sampler(bad_alignment_indexes); |
| warn_reserved_range_index_array(bad_reserve_indexes); |
| ss << "]"; |
| } |
| } |
| warn_oob(map_data.samplerHeapOffset + descriptor_size, true); |
| } |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_RESOURCE_HEAP_DATA_EXT) { |
| const VkDescriptorMappingSourceHeapDataEXT& map_data = mapping.sourceData.heapData; |
| uint32_t push_data = *((uint32_t*)&push_data_value[map_data.pushOffset]); |
| |
| ss << "pushOffset: 0x" << std::hex << map_data.pushOffset << ", heapOffset: 0x" << map_data.heapOffset; |
| ss << new_line << "Push data at 0x" << std::hex << map_data.pushOffset << ": 0x" << push_data; |
| ss << new_line << main_heap_type << " Heap address: 0x" << heap_range.begin + map_data.heapOffset << " + 0x" |
| << push_data; |
| VkDeviceAddress final_offset = map_data.heapOffset + push_data; |
| VkDeviceAddress final_address = heap_range.begin + final_offset; |
| ss << " [final address 0x" << final_address << "]"; |
| |
| warn_oob(final_offset + descriptor_size, false); |
| |
| if (!heap_reserved.empty()) { |
| vvl::range<VkDeviceAddress> index_zero_range{final_address, final_address + descriptor_size}; |
| if (index_zero_range.intersects(heap_reserved)) { |
| warn_reserved_range_start = 0; |
| } |
| } |
| |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_DATA_EXT) { |
| ss << "pushDataOffset: 0x" << std::hex << mapping.sourceData.pushDataOffset; |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_ADDRESS_EXT) { |
| uint64_t indirect_address = *((uint64_t*)&push_data_value[mapping.sourceData.pushAddressOffset]); |
| ss << "pushAddressOffset: 0x" << std::hex << mapping.sourceData.pushAddressOffset; |
| ss << new_line << "Indirect Adresss 0x" << indirect_address; |
| |
| warn_alignment_indirect_address(indirect_address); |
| warn_indirect_buffer |= dev_data.ListBuffers(ss, indirect_address, 3, true); |
| |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_INDIRECT_ADDRESS_EXT) { |
| const VkDescriptorMappingSourceIndirectAddressEXT& map_data = mapping.sourceData.indirectAddress; |
| VkDeviceAddress push_indirect_address = *((VkDeviceAddress*)&push_data_value[map_data.pushOffset]); |
| VkDeviceAddress final_indirect_address = push_indirect_address + map_data.addressOffset; |
| |
| ss << "pushOffset: 0x" << std::hex << map_data.pushOffset << ", addressOffset: 0x" << map_data.addressOffset; |
| ss << new_line << "Indirect Address: 0x" << final_indirect_address << " (0x" << push_indirect_address << " + 0x" |
| << map_data.addressOffset << ")"; |
| |
| warn_alignment_scalar_indirect(final_indirect_address, 8); |
| warn_indirect_buffer |= dev_data.ListBuffers(ss, final_indirect_address, 3, true); |
| |
| std::vector<uint8_t> indirect_address_data = dev_data.CopyDataFromMemory(final_indirect_address, 8); |
| if (!indirect_address_data.empty()) { |
| const VkDeviceAddress resource_address = *((VkDeviceAddress*)indirect_address_data.data()); |
| ss << new_line << "Resource Adresss 0x" << resource_address; |
| |
| warn_alignment_indirect_address(resource_address, true); |
| warn_indirect_buffer |= dev_data.ListBuffers(ss, resource_address, 3, true); |
| } |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_SHADER_RECORD_INDEX_EXT) { |
| // TODO - Add address for RTX |
| const VkDescriptorMappingSourceShaderRecordIndexEXT& map_data = mapping.sourceData.shaderRecordIndex; |
| ss << "heapOffset: 0x" << std::hex << map_data.heapOffset << ", shaderRecordOffset: 0x" << map_data.shaderRecordOffset |
| << ", heapIndexStride: 0x" << map_data.heapIndexStride << ", heapArrayStride: 0x" << map_data.heapArrayStride; |
| if (is_combined_image_sampler) { |
| ss << new_line << "samplerHeapOffset: 0x" << std::hex << map_data.samplerHeapOffset << ", samplerShaderRecordOffset: 0x" |
| << map_data.samplerShaderRecordOffset << ", samplerHeapIndexStride: 0x" << map_data.samplerHeapIndexStride |
| << ", samplerHeapArrayStride: 0x" << map_data.samplerHeapArrayStride; |
| } |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_SHADER_RECORD_DATA_EXT) { |
| // TODO - Add more info probably |
| ss << "shaderRecordDataOffset: " << std::hex << mapping.sourceData.shaderRecordDataOffset; |
| } else if (mapping.source == VK_DESCRIPTOR_MAPPING_SOURCE_SHADER_RECORD_ADDRESS_EXT) { |
| // TODO - Add more info probably |
| ss << "shaderRecordAddressOffset: " << std::hex << mapping.sourceData.shaderRecordAddressOffset; |
| } |
| |
| if (descriptor_type != VK_DESCRIPTOR_TYPE_MAX_ENUM) { |
| ss << new_line << "Descriptor size: 0x" << descriptor_size << " (" << string_VkDescriptorType(descriptor_type) << ")"; |
| } |
| |
| if (warn_reserved_range_start != vvl::kNoIndex32) { |
| if (warn_reserved_range_start == warn_reserved_range_end) { |
| warn_ss << new_line << "[WARNING] RESERVE RANGE - descriptor index at [" << std::dec << warn_reserved_range_start |
| << std::hex << "] will overlap with the reserved range and the access will be invalid"; |
| } else if (warn_reserved_range_end != vvl::kNoIndex32) { |
| warn_ss << new_line << "[WARNING] RESERVE RANGE - descriptor index starting at [" << std::dec |
| << warn_reserved_range_start << "] to [" << warn_reserved_range_end << std::hex |
| << "] will overlap with the reserved range and the access will be invalid"; |
| } else { |
| warn_ss << new_line |
| << "[WARNING] RESERVE RANGE - this descriptor overlaps with the reserved range is any access will be invalid"; |
| } |
| } |
| |
| bool found_warning = warn_indirect_buffer; |
| if (!warn_ss.str().empty()) { |
| ss << warn_ss.str(); |
| found_warning = true; |
| } |
| |
| return found_warning; |
| } |
| |
| // Return true if warning found |
| bool CommandBufferSubState::DumpDescriptorHeap(std::ostringstream& ss, const LastBound& last_bound) const { |
| bool found_warning = false; |
| const vvl::CommandBuffer& cb_state = last_bound.cb_state; |
| if (!cb_state.descriptor_heap.resource_range.empty()) { |
| ss << "vkCmdBindResourceHeapEXT last bound the resource heap to " |
| << string_range_hex(cb_state.descriptor_heap.resource_range); |
| if (!cb_state.descriptor_heap.resource_reserved.empty()) { |
| ss << " (reserved range " << std::dec << cb_state.descriptor_heap.resource_reserved.size() << " bytes at " << string_range_hex(cb_state.descriptor_heap.resource_reserved) << ")"; |
| } else { |
| ss << " (no reserved range)"; |
| } |
| ss << '\n'; |
| found_warning |= dev_data.ListBuffers(ss, cb_state.descriptor_heap.resource_range.begin, 1); |
| } |
| if (!cb_state.descriptor_heap.sampler_range.empty()) { |
| ss << "vkCmdBindSamplerHeapEXT last bound the sampler heap to " << string_range_hex(cb_state.descriptor_heap.sampler_range); |
| if (!cb_state.descriptor_heap.sampler_reserved.empty()) { |
| ss << " (reserved range " << std::dec << cb_state.descriptor_heap.sampler_reserved.size() << " bytes at " |
| << string_range_hex(cb_state.descriptor_heap.sampler_reserved) << ")"; |
| } else { |
| ss << " (no reserved range)"; |
| } |
| ss << '\n'; |
| found_warning |= dev_data.ListBuffers(ss, cb_state.descriptor_heap.sampler_range.begin, 1); |
| } |
| |
| // Quick way to combine shaderObjects and pipelines |
| small_vector<const ShaderStageState*, 2> stages; |
| if (last_bound.pipeline_state) { |
| for (const ShaderStageState& stage : last_bound.pipeline_state->stage_states) { |
| stages.emplace_back(&stage); |
| } |
| } else { |
| for (const auto& shader_object : last_bound.shader_object_states) { |
| if (shader_object) { |
| stages.emplace_back(&shader_object->stage); |
| } |
| } |
| } |
| |
| for (const ShaderStageState* stage : stages) { |
| if (!stage->HasSpirv()) { |
| ss << "[No SPIR-V found for " << string_VkShaderStageFlagBits(stage->GetStage()) |
| << ", can't detect which descriptors are being accessed]\n"; |
| continue; |
| } |
| const spirv::EntryPoint& entry_point = *stage->entrypoint; |
| ss << entry_point.Describe() << "\n"; |
| |
| const auto* mapping_info = vku::FindStructInPNextChain<VkShaderDescriptorSetAndBindingMappingInfoEXT>(stage->GetPNext()); |
| |
| // Want to sort and print all the mappings for a given Set together |
| std::map<uint32_t, std::vector<MappingInfo>> mapping_info_map; |
| |
| for (const spirv::ResourceInterfaceVariable& resource_variable : entry_point.resource_interface_variables) { |
| if (resource_variable.IsHeap()) { |
| // TODO detect offsets from start of heap and other info |
| ss << " - " << resource_variable.DescribeDescriptor(); |
| continue; |
| } |
| |
| const uint32_t var_set = resource_variable.decorations.set; |
| |
| for (uint32_t i = 0; i < mapping_info->mappingCount; i++) { |
| const VkDescriptorSetAndBindingMappingEXT& mapping = mapping_info->pMappings[i]; |
| if (!IsResourceVaribleInMapping(mapping, resource_variable)) { |
| continue; |
| } |
| mapping_info_map[var_set].emplace_back(MappingInfo{&mapping, &resource_variable, i}); |
| } |
| } |
| |
| if (mapping_info_map.empty()) { |
| ss << " No VkDescriptorSetAndBindingMappingEXT were found for this shader\n"; |
| continue; |
| } |
| |
| for (auto& [set_index, mapping_info_list] : mapping_info_map) { |
| ss << " - Set " << set_index << ":\n"; |
| std::sort(mapping_info_list.begin(), mapping_info_list.end()); |
| for (const MappingInfo& set_info : mapping_info_list) { |
| found_warning |= DumpDescriptorHeapMapping(ss, set_info); |
| } |
| } |
| } |
| return found_warning; |
| } |
| |
| void CommandBufferSubState::DumpDescriptors(const LastBound& last_bound, const Location& loc) const { |
| vvl::DescriptorMode descriptor_mode = last_bound.GetDescriptorMode(); |
| if (descriptor_mode != vvl::DescriptorModeBuffer && descriptor_mode != vvl::DescriptorModeHeap) { |
| return; |
| } |
| std::ostringstream ss; |
| ss << "[Dump Descriptor] ("; |
| // Embedded the objects into the message at the top instead of providing them in the callback we normally do |
| const LogObjectList objlist = last_bound.cb_state.GetObjectList(last_bound.bind_point); |
| bool first_obj = true; |
| for (auto object : objlist) { |
| if (first_obj) { |
| first_obj = false; |
| } else { |
| ss << ", "; |
| } |
| ss << dev_data.FormatHandle(object); |
| } |
| ss << ")\n"; |
| |
| bool found_warning = false; |
| if (descriptor_mode == vvl::DescriptorModeBuffer) { |
| found_warning = DumpDescriptorBuffer(ss, last_bound); |
| } else if (descriptor_mode == vvl::DescriptorModeHeap) { |
| found_warning = DumpDescriptorHeap(ss, last_bound); |
| } |
| |
| if (dev_data.gpu_dump_settings.to_stdout) { |
| std::cout << "GPU-DUMP " << ss.str() << '\n'; |
| } else { |
| const VkFlags log_level = found_warning ? kWarningBit : kInformationBit; |
| // Don't provide a LogObjectList, embed it into the message instead to keep things cleaner |
| // (because the default callback will list them at the bottom) |
| dev_data.debug_report->LogMessage(log_level, "GPU-DUMP", {}, loc, ss.str().c_str()); |
| } |
| } |
| |
| } // namespace gpudump |