| /* Copyright (c) 2020-2026 The Khronos Group Inc. |
| * Copyright (c) 2020-2026 Valve Corporation |
| * Copyright (c) 2020-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 "gpuav/instrumentation/gpuav_shader_instrumentor.h" |
| #include <vulkan/vulkan_core.h> |
| #include <cstdint> |
| |
| #include "error_message/error_location.h" |
| #include "generated/vk_extension_helper.h" |
| #include "generated/dispatch_functions.h" |
| #include "chassis/chassis_modification_state.h" |
| #include "gpuav/spirv/interface.h" |
| #include "utils/shader_utils.h" |
| #include "utils/spirv_tools_utils.h" |
| #include "utils/math_utils.h" |
| |
| #include "gpuav/shaders/gpuav_shaders_constants.h" |
| #include "gpuav/shaders/gpuav_error_codes.h" |
| #include "gpuav/shaders/gpuav_error_header.h" |
| #include "gpuav/spirv/log_error_pass.h" |
| #include "error_message/spirv_logging.h" |
| #include <spirv/unified1/NonSemanticShaderDebugInfo.h> |
| #include <spirv/unified1/spirv.hpp> |
| |
| #include "state_tracker/pipeline_state.h" |
| #include "state_tracker/descriptor_sets.h" |
| #include "state_tracker/shader_object_state.h" |
| #include "state_tracker/descriptor_mode.h" |
| #include "gpuav/resources/gpuav_state_trackers.h" |
| |
| #include "gpuav/spirv/module.h" |
| #include "gpuav/spirv/descriptor_indexing_oob_pass.h" |
| #include "gpuav/spirv/buffer_device_address_pass.h" |
| #include "gpuav/spirv/descriptor_indexing_oob_pass.h" |
| #include "gpuav/spirv/descriptor_class_general_buffer_pass.h" |
| #include "gpuav/spirv/descriptor_class_texel_buffer_pass.h" |
| #include "gpuav/spirv/ray_query_pass.h" |
| #include "gpuav/spirv/trace_ray_pass.h" |
| #include "gpuav/spirv/shared_memory_data_race_pass.h" |
| #include "gpuav/spirv/mesh_shading_pass.h" |
| #include "gpuav/spirv/debug_printf_pass.h" |
| #include "gpuav/spirv/debug_descriptor_pass.h" |
| #include "gpuav/spirv/post_process_descriptor_indexing_pass.h" |
| #include "gpuav/spirv/vertex_attribute_fetch_oob_pass.h" |
| #include "gpuav/spirv/sanitizer_pass.h" |
| |
| #include <cassert> |
| #include <string> |
| #include <filesystem> |
| namespace fs = std::filesystem; |
| |
| namespace gpuav { |
| |
| ReadLockGuard GpuShaderInstrumentor::ReadLock() const { |
| if (global_settings.fine_grained_locking) { |
| return ReadLockGuard(validation_object_mutex, std::defer_lock); |
| } else { |
| return ReadLockGuard(validation_object_mutex); |
| } |
| } |
| |
| WriteLockGuard GpuShaderInstrumentor::WriteLock() { |
| if (global_settings.fine_grained_locking) { |
| return WriteLockGuard(validation_object_mutex, std::defer_lock); |
| } else { |
| return WriteLockGuard(validation_object_mutex); |
| } |
| } |
| |
| void GpuShaderInstrumentor::SetupClassicDescriptor(const Location& loc) { |
| const VkDescriptorSetLayoutCreateInfo debug_desc_layout_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, |
| static_cast<uint32_t>(instrumentation_bindings_.size()), |
| instrumentation_bindings_.data()}; |
| |
| VkResult result = DispatchCreateDescriptorSetLayout(device, &debug_desc_layout_info, nullptr, |
| &instrumentation_desc_layout_[vvl::DescriptorModeClassic]); |
| if (result != VK_SUCCESS) { |
| InternalError(device, loc, "vkCreateDescriptorSetLayout failed for internal descriptor set"); |
| Cleanup(); |
| return; |
| } |
| |
| const VkDescriptorSetLayoutCreateInfo dummy_desc_layout_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, |
| 0, nullptr}; |
| result = DispatchCreateDescriptorSetLayout(device, &dummy_desc_layout_info, nullptr, |
| &dummy_desc_layout_[vvl::DescriptorModeClassic]); |
| if (result != VK_SUCCESS) { |
| InternalError(device, loc, "vkCreateDescriptorSetLayout failed for internal dummy descriptor set"); |
| Cleanup(); |
| return; |
| } |
| |
| std::vector<VkDescriptorSetLayout> debug_layouts; |
| for (uint32_t j = 0; j < instrumentation_desc_set_bind_index_; ++j) { |
| debug_layouts.push_back(dummy_desc_layout_[vvl::DescriptorModeClassic]); |
| } |
| debug_layouts.push_back(instrumentation_desc_layout_[vvl::DescriptorModeClassic]); |
| |
| const VkPipelineLayoutCreateInfo debug_pipeline_layout_info = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, |
| nullptr, |
| 0u, |
| static_cast<uint32_t>(debug_layouts.size()), |
| debug_layouts.data(), |
| 0u, |
| nullptr}; |
| result = DispatchCreatePipelineLayout(device, &debug_pipeline_layout_info, nullptr, |
| &instrumentation_pipeline_layout_[vvl::DescriptorModeClassic]); |
| if (result != VK_SUCCESS) { |
| InternalError(device, loc, "vkCreateDescriptorSetLayout failed for internal pipeline layout"); |
| Cleanup(); |
| return; |
| } |
| } |
| |
| void GpuShaderInstrumentor::SetupDescriptorBuffers(const Location& loc) { |
| if (!IsExtEnabled(extensions.vk_ext_descriptor_buffer)) { |
| return; |
| } |
| |
| // We don't use dynamic offset in descriptor buffer, instead we just map the offset each call |
| // This isn't ideal to set like this, will not be a problem when we get Root Node working |
| instrumentation_bindings_[glsl::kBindingInstActionIndex].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; |
| instrumentation_bindings_[glsl::kBindingInstCmdResourceIndex].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; |
| |
| const VkDescriptorSetLayoutCreateInfo descriptor_buffer_dsl_info = { |
| VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT, |
| static_cast<uint32_t>(instrumentation_bindings_.size()), instrumentation_bindings_.data()}; |
| |
| VkResult result = DispatchCreateDescriptorSetLayout(device, &descriptor_buffer_dsl_info, nullptr, |
| &instrumentation_desc_layout_[vvl::DescriptorModeBuffer]); |
| if (result != VK_SUCCESS) { |
| InternalError(device, loc, "vkCreateDescriptorSetLayout failed for internal descriptor set for descriptor buffer"); |
| Cleanup(); |
| return; |
| } |
| |
| const VkDescriptorSetLayoutCreateInfo descriptor_buffer_dummy_dsl_info = { |
| VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT, 0, |
| nullptr}; |
| result = DispatchCreateDescriptorSetLayout(device, &descriptor_buffer_dummy_dsl_info, nullptr, |
| &dummy_desc_layout_[vvl::DescriptorModeBuffer]); |
| if (result != VK_SUCCESS) { |
| InternalError(device, loc, "vkCreateDescriptorSetLayout failed for internal dummy descriptor set for descriptor buffer"); |
| Cleanup(); |
| return; |
| } |
| |
| std::vector<VkDescriptorSetLayout> debug_layouts; |
| for (uint32_t j = 0; j < instrumentation_desc_set_bind_index_; ++j) { |
| debug_layouts.push_back(dummy_desc_layout_[vvl::DescriptorModeBuffer]); |
| } |
| debug_layouts.push_back(instrumentation_desc_layout_[vvl::DescriptorModeBuffer]); |
| |
| const VkPipelineLayoutCreateInfo debug_pipeline_layout_db_info = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, |
| nullptr, |
| 0u, |
| static_cast<uint32_t>(debug_layouts.size()), |
| debug_layouts.data(), |
| 0u, |
| nullptr}; |
| result = DispatchCreatePipelineLayout(device, &debug_pipeline_layout_db_info, nullptr, |
| &instrumentation_pipeline_layout_[vvl::DescriptorModeBuffer]); |
| if (result != VK_SUCCESS) { |
| InternalError(device, loc, "vkCreateDescriptorSetLayout failed for internal pipeline layout for descriptor buffer"); |
| Cleanup(); |
| return; |
| } |
| |
| VkDeviceSize bytes_to_reserve = 0; |
| DispatchGetDescriptorSetLayoutSizeEXT(device, instrumentation_desc_layout_[vvl::DescriptorModeBuffer], &bytes_to_reserve); |
| |
| resource_descriptor_buffer_size_ = bytes_to_reserve; |
| resource_descriptor_buffer_offsets_.resize(glsl::kTotalBindings); |
| for (uint32_t i = 0; i < glsl::kTotalBindings; i++) { |
| DispatchGetDescriptorSetLayoutBindingOffsetEXT(device, instrumentation_desc_layout_[vvl::DescriptorModeBuffer], i, |
| &resource_descriptor_buffer_offsets_[i]); |
| } |
| |
| // Revert, because classic needs for fixing disturbed pipelines |
| instrumentation_bindings_[glsl::kBindingInstActionIndex].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC; |
| instrumentation_bindings_[glsl::kBindingInstCmdResourceIndex].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC; |
| } |
| |
| void GpuShaderInstrumentor::SetupDescriptorHeap(const Location& loc) { |
| if (!IsExtEnabled(extensions.vk_ext_descriptor_heap)) { |
| return; |
| } |
| const VkPhysicalDeviceDescriptorHeapPropertiesEXT& descriptor_heap_props = phys_dev_ext_props.descriptor_heap_props; |
| VkDeviceSize bytes_to_reserve = |
| Align(descriptor_heap_props.bufferDescriptorSize * glsl::kTotalBindings, descriptor_heap_props.bufferDescriptorAlignment); |
| |
| resource_heap_reserved_bytes_ = bytes_to_reserve; |
| buffer_descriptor_size_ = descriptor_heap_props.bufferDescriptorSize; |
| buffer_descriptor_alignment_ = descriptor_heap_props.bufferDescriptorAlignment; |
| push_data_offset_ = static_cast<uint32_t>(descriptor_heap_props.maxPushDataSize) - 8u; |
| } |
| |
| // In charge of getting things for shader instrumentation that both GPU-AV and DebugPrintF will need |
| void GpuShaderInstrumentor::FinishDeviceSetup(const VkDeviceCreateInfo* pCreateInfo, const Location& loc) { |
| DeviceProxy::FinishDeviceSetup(pCreateInfo, loc); |
| |
| // Update feature and extension state based on changes made to the create info. |
| GetEnabledDeviceFeatures(pCreateInfo, &modified_features, api_version); |
| modified_extensions = DeviceExtensions(extensions, api_version, pCreateInfo); |
| |
| // Check hard requirements for GPU-AV against what we enabled. |
| if (!modified_features.fragmentStoresAndAtomics) { |
| InternalError( |
| device, loc, |
| "GPU Shader Instrumentation requires fragmentStoresAndAtomics to allow witting out data inside the fragment shader."); |
| return; |
| } |
| if (!modified_features.vertexPipelineStoresAndAtomics) { |
| InternalError(device, loc, |
| "GPU Shader Instrumentation requires vertexPipelineStoresAndAtomics to allow witting out data inside the " |
| "vertex shader."); |
| return; |
| } |
| if (!modified_features.timelineSemaphore) { |
| InternalError(device, loc, |
| "GPU Shader Instrumentation requires timelineSemaphore to manage when command buffers are submitted at queue " |
| "submit time."); |
| return; |
| } |
| if (!modified_features.bufferDeviceAddress) { |
| InternalError(device, loc, "GPU Shader Instrumentation requires bufferDeviceAddress to manage witting out of the shader."); |
| return; |
| } |
| if (!modified_features.scalarBlockLayout) { |
| InternalError(device, loc, "GPU Shader Instrumentation requires scalarBlockLayout to pack data in a shader."); |
| return; |
| } |
| if (modified_features.vulkanMemoryModel && !modified_features.vulkanMemoryModelDeviceScope) { |
| InternalError(device, loc, |
| "GPU Shader Instrumentation requires vulkanMemoryModelDeviceScope feature (if vulkanMemoryModel is enabled) " |
| "to let us call atomicAdd to the output buffer."); |
| return; |
| } |
| |
| // maxBoundDescriptorSets limit, but possibly adjusted |
| const uint32_t adjusted_max_desc_sets_limit = |
| std::min(kMaxAdjustedBoundDescriptorSet, phys_dev_props.limits.maxBoundDescriptorSets); |
| // If gpu_validation_reserve_binding_slot: the max slot is where we reserved |
| // else: always use the last possible set as least likely to be used |
| instrumentation_desc_set_bind_index_ = adjusted_max_desc_sets_limit - 1; |
| |
| // We can't do anything if there is only one. |
| // Device probably not a legit Vulkan device, since there should be at least 4. Protect ourselves. |
| if (adjusted_max_desc_sets_limit == 1) { |
| InternalError(device, loc, "Device can bind only a single descriptor set."); |
| return; |
| } |
| |
| SetupClassicDescriptor(loc); |
| SetupDescriptorBuffers(loc); |
| SetupDescriptorHeap(loc); |
| |
| // Settings we will want for every SPIR-V instrumention pass |
| instrumentation_device_settings_.output_buffer_descriptor_set = instrumentation_desc_set_bind_index_; |
| instrumentation_device_settings_.safe_mode = gpuav_settings.safe_mode; |
| instrumentation_device_settings_.print_debug_info = gpuav_settings.debug_print_instrumentation_info; |
| instrumentation_device_settings_.max_instrumentations_count = gpuav_settings.debug_max_instrumentations_count; |
| instrumentation_device_settings_.support_non_semantic_info = |
| IsExtEnabled(extensions.vk_khr_shader_non_semantic_info) && !IsExtEnabled(extensions.vk_khr_portability_subset); |
| instrumentation_device_settings_.error_buffer_data_length = glsl::kErrorBufferDataLength; |
| instrumentation_device_settings_.debug_printf_buffer_size = gpuav_settings.debug_printf_buffer_size; |
| instrumentation_device_settings_.max_compute_shared_memory_size = phys_dev_props.limits.maxComputeSharedMemorySize; |
| } |
| |
| void GpuShaderInstrumentor::Cleanup() { |
| for (uint32_t i = 0; i < vvl::DescriptorModeCount; i++) { |
| if (instrumentation_desc_layout_[i]) { |
| DispatchDestroyDescriptorSetLayout(device, instrumentation_desc_layout_[i], nullptr); |
| instrumentation_desc_layout_[i] = VK_NULL_HANDLE; |
| } |
| if (dummy_desc_layout_[i]) { |
| DispatchDestroyDescriptorSetLayout(device, dummy_desc_layout_[i], nullptr); |
| dummy_desc_layout_[i] = VK_NULL_HANDLE; |
| } |
| if (instrumentation_pipeline_layout_[i]) { |
| DispatchDestroyPipelineLayout(device, instrumentation_pipeline_layout_[i], nullptr); |
| instrumentation_pipeline_layout_[i] = VK_NULL_HANDLE; |
| } |
| } |
| } |
| |
| void GpuShaderInstrumentor::PreCallRecordDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator, |
| const RecordObject& record_obj) { |
| Cleanup(); |
| DeviceProxy::PreCallRecordDestroyDevice(device, pAllocator, record_obj); |
| } |
| |
| // Just gives a warning about a possible deadlock. |
| bool GpuShaderInstrumentor::ValidateCmdWaitEvents(VkCommandBuffer command_buffer, VkPipelineStageFlags2 src_stage_mask, |
| const Location& loc) const { |
| if (src_stage_mask & VK_PIPELINE_STAGE_2_HOST_BIT) { |
| std::ostringstream error_msg; |
| error_msg << loc.Message() |
| << " recorded with VK_PIPELINE_STAGE_HOST_BIT set. GPU-Assisted validation waits on queue completion. This wait " |
| "could block the host's signaling of this event, resulting in deadlock."; |
| InternalError(command_buffer, loc, error_msg.str().c_str()); |
| } |
| return false; |
| } |
| |
| bool GpuShaderInstrumentor::PreCallValidateCmdWaitEvents( |
| VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, VkPipelineStageFlags srcStageMask, |
| VkPipelineStageFlags dstStageMask, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, |
| uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, |
| const VkImageMemoryBarrier* pImageMemoryBarriers, const ErrorObject& error_obj) const { |
| return ValidateCmdWaitEvents(commandBuffer, static_cast<VkPipelineStageFlags2>(srcStageMask), error_obj.location); |
| } |
| |
| bool GpuShaderInstrumentor::PreCallValidateCmdWaitEvents2KHR(VkCommandBuffer commandBuffer, uint32_t eventCount, |
| const VkEvent* pEvents, const VkDependencyInfoKHR* pDependencyInfos, |
| const ErrorObject& error_obj) const { |
| return PreCallValidateCmdWaitEvents2(commandBuffer, eventCount, pEvents, pDependencyInfos, error_obj); |
| } |
| |
| bool GpuShaderInstrumentor::PreCallValidateCmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, |
| const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos, |
| const ErrorObject& error_obj) const { |
| VkPipelineStageFlags2 src_stage_mask = 0; |
| |
| for (uint32_t i = 0; i < eventCount; i++) { |
| auto exec_scopes = sync_utils::GetExecScopes(pDependencyInfos[i]); |
| src_stage_mask |= exec_scopes.src; |
| } |
| |
| return ValidateCmdWaitEvents(commandBuffer, src_stage_mask, error_obj.location); |
| } |
| |
| vvl::DescriptorMode GpuShaderInstrumentor::SelectDescriptorModeFromDSL(uint32_t set_layout_count, |
| const VkDescriptorSetLayout* set_layouts) const { |
| vvl::DescriptorMode mode = vvl::DescriptorModeClassic; |
| if (IsExtEnabled(extensions.vk_ext_descriptor_buffer)) { |
| if (set_layout_count > 0) { |
| // It is valid to have null DSL (using GPL) so need to find the first valid |
| for (uint32_t i = 0; i < set_layout_count; i++) { |
| // VU 08008 forces all layouts to have this flag, so only need to check first flag |
| if (set_layouts[i]) { |
| const auto& dsl_state = Get<vvl::DescriptorSetLayout>(set_layouts[i]); |
| if (dsl_state->GetCreateFlags() & VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT) { |
| mode = vvl::DescriptorModeBuffer; |
| break; |
| } |
| } |
| } |
| } else if (enabled_features.descriptorBuffer) { |
| // At this point, we have actually zero way to know how this VkPipelineLayout/VkShaderEXT is going to be used because |
| // the extension never added a flag for creation time here.... so assume that if the descriptorBuffer feature is |
| // enabled, app is using it. This is such a rare case it likely is good enough of a solution for now, otherwise we will |
| // have to create 2 versions a modified handle and swap it out later. |
| mode = vvl::DescriptorModeBuffer; |
| } |
| } |
| return mode; |
| } |
| |
| void GpuShaderInstrumentor::PreCallRecordCreatePipelineLayout(VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo, |
| const VkAllocationCallbacks* pAllocator, |
| VkPipelineLayout* pPipelineLayout, const RecordObject& record_obj, |
| chassis::CreatePipelineLayout& chassis_state) { |
| if (gpuav_settings.IsSpirvModified()) { |
| if (chassis_state.modified_create_info.setLayoutCount > instrumentation_desc_set_bind_index_) { |
| std::ostringstream strm; |
| strm << "pCreateInfo::setLayoutCount (" << chassis_state.modified_create_info.setLayoutCount |
| << ") will conflicts with validation's descriptor set at slot " << instrumentation_desc_set_bind_index_ << ". " |
| << "This Pipeline Layout has too many descriptor sets that will not allow GPU shader instrumentation to be setup " |
| "for pipelines created with it, therefore no validation error will be repored for them by GPU-AV at runtime."; |
| InternalWarning(device, record_obj.location, strm.str().c_str()); |
| } else { |
| vvl::DescriptorMode mode = SelectDescriptorModeFromDSL(pCreateInfo->setLayoutCount, pCreateInfo->pSetLayouts); |
| |
| // Modify the pipeline layout by: |
| // 1. Copying the caller's descriptor set desc_layouts |
| // 2. Fill in dummy descriptor layouts up to the max binding |
| // 3. Fill in with the debug descriptor layout at the max binding slot |
| chassis_state.new_layouts.reserve(instrumentation_desc_set_bind_index_ + 1); |
| chassis_state.new_layouts.insert(chassis_state.new_layouts.end(), &pCreateInfo->pSetLayouts[0], |
| &pCreateInfo->pSetLayouts[pCreateInfo->setLayoutCount]); |
| for (uint32_t i = pCreateInfo->setLayoutCount; i < instrumentation_desc_set_bind_index_; ++i) { |
| chassis_state.new_layouts.push_back(dummy_desc_layout_[mode]); |
| } |
| chassis_state.new_layouts.push_back(instrumentation_desc_layout_[mode]); |
| chassis_state.modified_create_info.pSetLayouts = chassis_state.new_layouts.data(); |
| chassis_state.modified_create_info.setLayoutCount = instrumentation_desc_set_bind_index_ + 1; |
| } |
| } |
| } |
| |
| void GpuShaderInstrumentor::PostCallRecordCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo, |
| const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule, |
| const RecordObject& record_obj, |
| chassis::CreateShaderModule& chassis_state) { |
| if (record_obj.result != VK_SUCCESS) { |
| return; |
| } |
| |
| // By default, we instrument everything, but if the setting is enabled, we only will instrument the shaders the app picks |
| if (gpuav_settings.select_instrumented_shaders && IsSelectiveInstrumentationEnabled(pCreateInfo->pNext)) { |
| // If this is being filled up, likely only a few shaders and the app scope is narrowed down, so no need to spend time |
| // removing these later |
| selected_instrumented_shaders.insert(*pShaderModule); |
| }; |
| } |
| |
| // We on the spot create a VkShaderEXT without instrumentation to return to the user |
| // We assume people are not trying to use GPU-AV while calling vkGetShaderBinaryDataEXT |
| // But this is needed for things like CTS that are using this to mock a fake Binary Shader Object |
| void GpuShaderInstrumentor::PreCallRecordGetShaderBinaryDataEXT(VkDevice device, VkShaderEXT shader, size_t* pDataSize, void* pData, |
| const RecordObject& record_obj, |
| chassis::ShaderBinaryData& chassis_state) { |
| const auto& shader_object_state = Get<vvl::ShaderObject>(shader); |
| ASSERT_AND_RETURN(shader_object_state); |
| auto& sub_state = SubState(*shader_object_state); |
| |
| VkShaderEXT original_handle = VK_NULL_HANDLE; |
| |
| auto it = instrumented_shaders_map_.find(sub_state.unique_shader_id); |
| if (it == instrumented_shaders_map_.end() || it->second.original_spirv.empty()) { |
| // This will occur if the shader was so simple we didn't even instrument anything |
| return; |
| } |
| |
| // The original pCode might be gone, so need to make a shallow copy and put original SPIR-V inside |
| VkShaderCreateInfoEXT create_info_copy = *sub_state.original_create_info.ptr(); |
| // The pCode doesn't live in the safe struct, we need to grab it from our other map |
| const gpuav::InstrumentedShader* instrumented_shader = &it->second; |
| create_info_copy.pCode = instrumented_shader->original_spirv.data(); |
| create_info_copy.codeSize = instrumented_shader->original_spirv.size() * sizeof(uint32_t); |
| |
| // Only warn on the first call to query the size |
| if (pData == nullptr) { |
| InternalWarning( |
| shader, record_obj.location, |
| "GPU-AV instruments all shaders at vkCreateShadersEXT time, this means there are embedded descriptors bound " |
| "that we can't detect if needed or not later.\nWe will be calling vkCreateShadersEXT again now to create the " |
| "original shader to pass down to the drivere."); |
| } |
| |
| // vkGetShaderBinaryDataEXT will be called twice, only need to re-created once |
| if (sub_state.original_handle == VK_NULL_HANDLE) { |
| DispatchCreateShadersEXT(device, 1, &create_info_copy, nullptr, &original_handle); |
| sub_state.original_handle = original_handle; // will be destroyed later |
| } |
| |
| chassis_state.modified_shader_handle = sub_state.original_handle; |
| } |
| |
| bool GpuShaderInstrumentor::PreCallRecordShaderObjectInstrumentation(vku::safe_VkShaderCreateInfoEXT& modified_create_info, |
| const Location& create_info_loc, |
| chassis::ShaderObjectInstrumentationData& instrumentation_data, |
| const vvl::DescriptorMode descriptor_mode) { |
| const uint32_t unique_shader_id = unique_shader_module_id_++; |
| |
| std::vector<uint32_t>& instrumented_spirv = instrumentation_data.instrumented_spirv; |
| spirv::InstrumentationInterface interface(create_info_loc); |
| interface.unique_shader_id = unique_shader_id; |
| interface.entry_point_name = modified_create_info.pName; |
| interface.entry_point_stage = modified_create_info.stage; |
| interface.specialization_info = modified_create_info.pSpecializationInfo->ptr(); |
| interface.has_task_shader = (modified_create_info.flags & VK_SHADER_CREATE_NO_TASK_SHADER_BIT_EXT) == 0; |
| interface.descriptor_mode = descriptor_mode; |
| |
| BuildDescriptorSetLayoutInfo(modified_create_info, interface.instrumentation_dsl); |
| |
| const bool is_shader_instrumented = InstrumentShader( |
| vvl::make_span(static_cast<const uint32_t*>(modified_create_info.pCode), modified_create_info.codeSize / sizeof(uint32_t)), |
| interface, instrumented_spirv); |
| |
| if (is_shader_instrumented) { |
| instrumentation_data.unique_shader_id = unique_shader_id; |
| modified_create_info.pCode = instrumented_spirv.data(); |
| modified_create_info.codeSize = instrumented_spirv.size() * sizeof(uint32_t); |
| } |
| return is_shader_instrumented; |
| } |
| |
| void GpuShaderInstrumentor::PreCallRecordCreateShadersEXT(VkDevice device, uint32_t createInfoCount, |
| const VkShaderCreateInfoEXT* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkShaderEXT* pShaders, |
| const RecordObject& record_obj, chassis::ShaderObject& chassis_state) { |
| if (!gpuav_settings.IsSpirvModified()) return; |
| |
| // Resize here so if using just CoreCheck we don't waste time allocating this |
| chassis_state.instrumentations_data.resize(createInfoCount); |
| chassis_state.modified_create_infos.resize(createInfoCount); |
| |
| for (uint32_t i = 0; i < createInfoCount; ++i) { |
| // Need deep copy as there might be pNext items |
| vku::safe_VkShaderCreateInfoEXT& new_create_info = chassis_state.modified_create_infos[i]; |
| new_create_info.initialize(&pCreateInfos[i]); |
| |
| if (new_create_info.codeType != VK_SHADER_CODE_TYPE_SPIRV_EXT) { |
| continue; |
| } else if (!chassis_state.module_states[i]) { |
| continue; |
| } |
| |
| const Location& create_info_loc = record_obj.location.dot(vvl::Field::pCreateInfos, i); |
| auto& instrumentation_data = chassis_state.instrumentations_data[i]; |
| |
| // See pipeline version for explanation |
| if (new_create_info.flags & VK_SHADER_CREATE_INDIRECT_BINDABLE_BIT_EXT) { |
| InternalError(device, create_info_loc, |
| "Unable to instrument shader using VkIndirectExecutionSetEXT validly, things might work, but likely will " |
| "not because of GPU-AV's usage of VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC (If you don't " |
| "need VK_SHADER_CREATE_INDIRECT_BINDABLE_BIT_EXT, turn it off)."); |
| } |
| |
| if (new_create_info.setLayoutCount > instrumentation_desc_set_bind_index_) { |
| std::ostringstream strm; |
| strm << "pCreateInfos[" << i << "]::setLayoutCount (" << new_create_info.setLayoutCount |
| << ") will conflicts with validation's descriptor set at slot " << instrumentation_desc_set_bind_index_ << ". " |
| << "This Shader Object has too many descriptor sets that will not allow GPU shader instrumentation to be setup " |
| "for VkShaderEXT created with it, therefore no validation error will be repored for them by GPU-AV at " |
| "runtime."; |
| InternalWarning(device, record_obj.location, strm.str().c_str()); |
| } else if (gpuav_settings.select_instrumented_shaders && !IsSelectiveInstrumentationEnabled(new_create_info.pNext)) { |
| continue; |
| } else { |
| // Modify the pipeline layout by: |
| // 1. Copying the caller's descriptor set desc_layouts |
| // 2. Fill in dummy descriptor layouts up to the max binding |
| // 3. Fill in with the debug descriptor layout at the max binding slot |
| const VkShaderCreateInfoEXT& original_create_info = pCreateInfos[i]; |
| |
| const vvl::DescriptorMode mode = |
| (original_create_info.flags & VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT) |
| ? vvl::DescriptorMode::DescriptorModeHeap |
| : SelectDescriptorModeFromDSL(original_create_info.setLayoutCount, original_create_info.pSetLayouts); |
| if (mode == vvl::DescriptorMode::DescriptorModeHeap) { |
| AddDescriptorHeapMappings(reinterpret_cast<VkBaseOutStructure*>(&new_create_info)); |
| chassis_state.is_modified |= |
| PreCallRecordShaderObjectInstrumentation(new_create_info, create_info_loc, instrumentation_data, mode); |
| } else { |
| // We need to remove the old layouts we copied in safe_VkShaderCreateInfoEXT::initialize |
| if (new_create_info.pSetLayouts) { |
| delete[] new_create_info.pSetLayouts; |
| } |
| |
| new_create_info.setLayoutCount = instrumentation_desc_set_bind_index_ + 1; |
| new_create_info.pSetLayouts = new VkDescriptorSetLayout[new_create_info.setLayoutCount]; |
| for (uint32_t k = 0; k < original_create_info.setLayoutCount; ++k) { |
| new_create_info.pSetLayouts[k] = original_create_info.pSetLayouts[k]; |
| } |
| for (uint32_t k = original_create_info.setLayoutCount; k < instrumentation_desc_set_bind_index_; ++k) { |
| new_create_info.pSetLayouts[k] = dummy_desc_layout_[mode]; |
| } |
| new_create_info.pSetLayouts[instrumentation_desc_set_bind_index_] = instrumentation_desc_layout_[mode]; |
| |
| chassis_state.is_modified |= |
| PreCallRecordShaderObjectInstrumentation(new_create_info, create_info_loc, instrumentation_data, mode); |
| } |
| } |
| } |
| |
| chassis_state.pCreateInfos = reinterpret_cast<VkShaderCreateInfoEXT*>(chassis_state.modified_create_infos.data()); |
| } |
| |
| void GpuShaderInstrumentor::PostCallRecordCreateShadersEXT(VkDevice device, uint32_t createInfoCount, |
| const VkShaderCreateInfoEXT* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkShaderEXT* pShaders, |
| const RecordObject& record_obj, chassis::ShaderObject& chassis_state) { |
| if (!gpuav_settings.IsSpirvModified()) { |
| return; |
| } |
| // This can occur if the driver failed to compile the instrumented shader or if a PreCall step failed |
| if (!chassis_state.is_modified) { |
| return; |
| } |
| for (uint32_t i = 0; i < createInfoCount; ++i) { |
| // If there are multiple shaders being created, and one is bad, will return a non VK_SUCCESS but we need to check if the |
| // VkShaderEXT was null or not to actually know if it was created |
| const VkShaderEXT shader_handle = pShaders[i]; |
| if (shader_handle == VK_NULL_HANDLE) { |
| continue; |
| } |
| |
| auto& instrumentation_data = chassis_state.instrumentations_data[i]; |
| |
| // if the shader for some reason was not instrumented, there is nothing to save |
| // (like not using VK_SHADER_CODE_TYPE_SPIRV_EXT) |
| if (!instrumentation_data.IsInstrumented()) { |
| continue; |
| } |
| const auto& shader_object_state = Get<vvl::ShaderObject>(shader_handle); |
| ASSERT_AND_CONTINUE(shader_object_state); |
| auto& sub_state = SubState(*shader_object_state); |
| |
| sub_state.was_instrumented = true; |
| sub_state.unique_shader_id = instrumentation_data.unique_shader_id; |
| // Note - this doesn't make a deep copy of the pCode, but does of the DescriptorSetLayout which we |
| sub_state.original_create_info.initialize(&pCreateInfos[i]); |
| |
| // We currently need to store a copy of the original, non-instrumented shader so if there is debug information. |
| std::vector<uint32_t> code; |
| if (shader_object_state->stage.spirv_state) { |
| code = shader_object_state->stage.spirv_state->words_; |
| } |
| |
| instrumented_shaders_map_.insert_or_assign(instrumentation_data.unique_shader_id, VK_NULL_HANDLE, VK_NULL_HANDLE, |
| shader_handle, std::move(code)); |
| } |
| } |
| |
| void GpuShaderInstrumentor::PreCallRecordDestroyShaderEXT(VkDevice device, VkShaderEXT shader, |
| const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) { |
| if (auto shader_object_state = Get<vvl::ShaderObject>(shader)) { |
| auto& sub_state = SubState(*shader_object_state); |
| instrumented_shaders_map_.pop(sub_state.unique_shader_id); |
| |
| if (sub_state.original_handle != VK_NULL_HANDLE) { |
| DispatchDestroyShaderEXT(device, sub_state.original_handle, nullptr); |
| } |
| } |
| } |
| |
| void GpuShaderInstrumentor::PreCallRecordCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count, |
| const VkGraphicsPipelineCreateInfo* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const RecordObject& record_obj, PipelineStates& pipeline_states, |
| chassis::CreateGraphicsPipelines& chassis_state) { |
| if (!gpuav_settings.IsSpirvModified()) return; |
| |
| chassis_state.shader_instrumentations_metadata.resize(count); |
| chassis_state.modified_create_infos.resize(count); |
| |
| for (uint32_t i = 0; i < count; ++i) { |
| const auto& pipeline_state = pipeline_states[i]; |
| const Location create_info_loc = record_obj.location.dot(vvl::Field::pCreateInfos, i); |
| |
| // Need to make a deep copy so if SPIR-V is inlined, user doesn't see it after the call |
| auto& new_pipeline_ci = chassis_state.modified_create_infos[i]; |
| new_pipeline_ci.initialize(&pipeline_state->GraphicsCreateInfo()); |
| |
| if (!NeedPipelineCreationShaderInstrumentation(*pipeline_state, create_info_loc)) { |
| continue; |
| } |
| |
| auto& shader_instrumentation_metadata = chassis_state.shader_instrumentations_metadata[i]; |
| |
| bool success = false; |
| if (pipeline_state->linking_shaders != 0) { |
| success = PreCallRecordPipelineCreationShaderInstrumentationGPL(pAllocator, *pipeline_state, new_pipeline_ci, |
| create_info_loc, shader_instrumentation_metadata); |
| } else { |
| success = PreCallRecordPipelineCreationShaderInstrumentation(pAllocator, *pipeline_state, new_pipeline_ci, |
| uint32_t(pipeline_state->stage_states.size()), |
| create_info_loc, shader_instrumentation_metadata); |
| } |
| if (!success) { |
| return; |
| } |
| } |
| |
| chassis_state.is_modified = true; |
| chassis_state.pCreateInfos = reinterpret_cast<VkGraphicsPipelineCreateInfo*>(chassis_state.modified_create_infos.data()); |
| } |
| |
| void GpuShaderInstrumentor::PreCallRecordCreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count, |
| const VkComputePipelineCreateInfo* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const RecordObject& record_obj, PipelineStates& pipeline_states, |
| chassis::CreateComputePipelines& chassis_state) { |
| if (!gpuav_settings.IsSpirvModified()) return; |
| |
| chassis_state.shader_instrumentations_metadata.resize(count); |
| chassis_state.modified_create_infos.resize(count); |
| |
| for (uint32_t i = 0; i < count; ++i) { |
| const auto& pipeline_state = pipeline_states[i]; |
| const Location create_info_loc = record_obj.location.dot(vvl::Field::pCreateInfos, i); |
| |
| // Need to make a deep copy so if SPIR-V is inlined, user doesn't see it after the call |
| auto& new_pipeline_ci = chassis_state.modified_create_infos[i]; |
| new_pipeline_ci.initialize(&pipeline_state->ComputeCreateInfo()); |
| |
| if (!NeedPipelineCreationShaderInstrumentation(*pipeline_state, create_info_loc)) { |
| continue; |
| } |
| |
| auto& shader_instrumentation_metadata = chassis_state.shader_instrumentations_metadata[i]; |
| |
| bool success = PreCallRecordPipelineCreationShaderInstrumentation(pAllocator, *pipeline_state, new_pipeline_ci, 1, |
| create_info_loc, shader_instrumentation_metadata); |
| if (!success) { |
| return; |
| } |
| } |
| |
| chassis_state.is_modified = true; |
| chassis_state.pCreateInfos = reinterpret_cast<VkComputePipelineCreateInfo*>(chassis_state.modified_create_infos.data()); |
| } |
| |
| void GpuShaderInstrumentor::PreCallRecordCreateRayTracingPipelinesKHR( |
| VkDevice device, VkDeferredOperationKHR deferredOperation, VkPipelineCache pipelineCache, uint32_t count, |
| const VkRayTracingPipelineCreateInfoKHR* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const RecordObject& record_obj, PipelineStates& pipeline_states, chassis::CreateRayTracingPipelinesKHR& chassis_state) { |
| if (!gpuav_settings.IsSpirvModified()) { |
| return; |
| } |
| |
| chassis_state.shader_instrumentations_metadata.resize(count); |
| chassis_state.modified_create_infos.resize(count); |
| |
| for (uint32_t i = 0; i < count; ++i) { |
| const auto& pipeline_state = pipeline_states[i]; |
| const Location create_info_loc = record_obj.location.dot(vvl::Field::pCreateInfos, i); |
| |
| // Need to make a deep copy so if SPIR-V is inlined, user doesn't see it after the call |
| auto& new_pipeline_ci = chassis_state.modified_create_infos[i]; |
| new_pipeline_ci.initialize(&pipeline_state->RayTracingCreateInfo()); |
| |
| if (!NeedPipelineCreationShaderInstrumentation(*pipeline_state, create_info_loc)) { |
| continue; |
| } |
| |
| auto& shader_instrumentation_metadata = chassis_state.shader_instrumentations_metadata[i]; |
| |
| // Ray tracing pipelines can be made of libraries, but contrary to GPL instrumentation is not postponed |
| // to final link time, and done at ray tracing library creation time. |
| // => No need to iterate over shader stages coming from libraries, |
| // stop at VkRayTracingPipelineCreateInfoKHR::stageCount |
| // Note: This code implicitly relies on the fact that in pipeline_state->stage_states, |
| // stages coming from libraries are added last. |
| bool success = PreCallRecordPipelineCreationShaderInstrumentation(pAllocator, *pipeline_state, new_pipeline_ci, |
| new_pipeline_ci.stageCount, create_info_loc, |
| shader_instrumentation_metadata); |
| if (!success) { |
| return; |
| } |
| } |
| |
| chassis_state.is_modified = true; |
| chassis_state.pCreateInfos = reinterpret_cast<VkRayTracingPipelineCreateInfoKHR*>(chassis_state.modified_create_infos.data()); |
| } |
| |
| template <typename CreateInfos, typename SafeCreateInfos> |
| static void UtilCopyCreatePipelineFeedbackData(CreateInfos& create_info, SafeCreateInfos& safe_create_info) { |
| auto src_feedback_struct = vku::FindStructInPNextChain<VkPipelineCreationFeedbackCreateInfo>(safe_create_info.pNext); |
| if (!src_feedback_struct) return; |
| auto dst_feedback_struct = const_cast<VkPipelineCreationFeedbackCreateInfo*>( |
| vku::FindStructInPNextChain<VkPipelineCreationFeedbackCreateInfo>(create_info.pNext)); |
| *dst_feedback_struct->pPipelineCreationFeedback = *src_feedback_struct->pPipelineCreationFeedback; |
| for (uint32_t j = 0; j < src_feedback_struct->pipelineStageCreationFeedbackCount; j++) { |
| dst_feedback_struct->pPipelineStageCreationFeedbacks[j] = src_feedback_struct->pPipelineStageCreationFeedbacks[j]; |
| } |
| } |
| |
| void GpuShaderInstrumentor::PostCallRecordCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count, |
| const VkGraphicsPipelineCreateInfo* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const RecordObject& record_obj, PipelineStates& pipeline_states, |
| chassis::CreateGraphicsPipelines& chassis_state) { |
| if (!gpuav_settings.IsSpirvModified()) return; |
| // VK_PIPELINE_COMPILE_REQUIRED means that the current pipeline creation call was used to poke the driver cache, |
| // no pipeline is created in this case |
| if (record_obj.result == VK_PIPELINE_COMPILE_REQUIRED) return; |
| // This can occur if the driver failed to compile the instrumented shader or if a PreCall step failed |
| if (!chassis_state.is_modified) return; |
| |
| for (uint32_t i = 0; i < count; ++i) { |
| const VkPipeline pipeline_handle = pPipelines[i]; |
| if (pipeline_handle == VK_NULL_HANDLE) { |
| continue; // vkspec.html#pipelines-multiple |
| } |
| |
| UtilCopyCreatePipelineFeedbackData(pCreateInfos[i], chassis_state.modified_create_infos[i]); |
| auto pipeline_state = Get<vvl::Pipeline>(pipeline_handle); |
| ASSERT_AND_CONTINUE(pipeline_state); |
| |
| // Move all instrumentation until the final linking time |
| if (pipeline_state->create_flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) continue; |
| |
| auto& shader_instrumentation_metadata = chassis_state.shader_instrumentations_metadata[i]; |
| if (pipeline_state->linking_shaders != 0) { |
| PostCallRecordPipelineCreationShaderInstrumentationGPL(*pipeline_state, shader_instrumentation_metadata); |
| } else { |
| PostCallRecordPipelineCreationShaderInstrumentation(*pipeline_state, uint32_t(pipeline_state->stage_states.size()), |
| shader_instrumentation_metadata); |
| } |
| } |
| } |
| |
| void GpuShaderInstrumentor::PostCallRecordCreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count, |
| const VkComputePipelineCreateInfo* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const RecordObject& record_obj, PipelineStates& pipeline_states, |
| chassis::CreateComputePipelines& chassis_state) { |
| if (!gpuav_settings.IsSpirvModified()) return; |
| // VK_PIPELINE_COMPILE_REQUIRED means that the current pipeline creation call was used to poke the driver cache, |
| // no pipeline is created in this case |
| if (record_obj.result == VK_PIPELINE_COMPILE_REQUIRED) return; |
| // This can occur if the driver failed to compile the instrumented shader or if a PreCall step failed |
| if (!chassis_state.is_modified) return; |
| |
| for (uint32_t i = 0; i < count; ++i) { |
| const VkPipeline pipeline_handle = pPipelines[i]; |
| if (pipeline_handle == VK_NULL_HANDLE) { |
| continue; // vkspec.html#pipelines-multiple |
| } |
| |
| UtilCopyCreatePipelineFeedbackData(pCreateInfos[i], chassis_state.modified_create_infos[i]); |
| |
| auto pipeline_state = Get<vvl::Pipeline>(pipeline_handle); |
| ASSERT_AND_CONTINUE(pipeline_state); |
| auto& shader_instrumentation_metadata = chassis_state.shader_instrumentations_metadata[i]; |
| PostCallRecordPipelineCreationShaderInstrumentation(*pipeline_state, 1, shader_instrumentation_metadata); |
| } |
| } |
| |
| void GpuShaderInstrumentor::PostCallRecordCreateRayTracingPipelinesKHR( |
| VkDevice device, VkDeferredOperationKHR deferredOperation, VkPipelineCache pipelineCache, uint32_t count, |
| const VkRayTracingPipelineCreateInfoKHR* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const RecordObject& record_obj, PipelineStates& pipeline_states, |
| std::shared_ptr<chassis::CreateRayTracingPipelinesKHR> chassis_state) { |
| // This can occur if the driver failed to compile the instrumented shader or if a PreCall step failed |
| if (!chassis_state->is_modified) { |
| return; |
| } |
| |
| if (!gpuav_settings.IsSpirvModified()) { |
| return; |
| } |
| // VK_PIPELINE_COMPILE_REQUIRED means that the current pipeline creation call was used to poke the driver cache, |
| // no pipeline is created in this case |
| if (record_obj.result == VK_PIPELINE_COMPILE_REQUIRED) { |
| return; |
| } |
| |
| const bool is_operation_deferred = deferredOperation != VK_NULL_HANDLE && record_obj.result == VK_OPERATION_DEFERRED_KHR; |
| |
| if (is_operation_deferred) { |
| for (uint32_t i = 0; i < count; ++i) { |
| UtilCopyCreatePipelineFeedbackData(pCreateInfos[i], chassis_state->modified_create_infos[i]); |
| } |
| |
| if (dispatch_device_->wrap_handles) { |
| deferredOperation = dispatch_device_->Unwrap(deferredOperation); |
| } |
| |
| auto found = dispatch_device_->deferred_operation_post_check.pop(deferredOperation); |
| std::vector<std::function<void(std::pair<uint32_t, VkPipeline*>)>> deferred_op_post_checks; |
| if (found->first) { |
| deferred_op_post_checks = std::move(found->second); |
| } else { |
| // vvl::Device::PostCallRecordCreateRayTracingPipelinesKHR should have added a lambda in |
| // deferred_operation_post_check for the current deferredOperation. |
| // This lambda is responsible for initializing the pipeline state we maintain, |
| // this state will be accessed in the following lambda. |
| // Given how PostCallRecordCreateRayTracingPipelinesKHR is called in |
| // GpuShaderInstrumentor::PostCallRecordCreateRayTracingPipelinesKHR |
| // conditions holds as of writing. But it is something we need to be aware of. |
| assert(false); |
| return; |
| } |
| |
| deferred_op_post_checks.emplace_back([this, held_chassis_state = |
| chassis_state](std::pair<uint32_t, VkPipeline*> pipelines) mutable { |
| for (const auto [pipe_i, pipe] : vvl::enumerate(pipelines.second, pipelines.first)) { |
| std::shared_ptr<vvl::Pipeline> pipeline_state = ((GpuShaderInstrumentor*)this)->Get<vvl::Pipeline>(pipe); |
| ASSERT_AND_CONTINUE(pipeline_state); |
| if (pipeline_state->ray_tracing_library_ci) { |
| for (VkPipeline lib : vvl::make_span(pipeline_state->ray_tracing_library_ci->pLibraries, |
| pipeline_state->ray_tracing_library_ci->libraryCount)) { |
| auto lib_state = ((GpuShaderInstrumentor*)this)->Get<vvl::Pipeline>(lib); |
| ASSERT_AND_CONTINUE(lib_state); |
| pipeline_state->instrumentation_data.was_instrumented |= lib_state->instrumentation_data.was_instrumented; |
| } |
| } |
| auto& shader_instrumentation_metadata = held_chassis_state->shader_instrumentations_metadata[pipe_i]; |
| // Ray tracing pipelines can be made of libraries, but contrary to GPL instrumentation is not postponed |
| // to final link time, and done at ray tracing library creation time. |
| // => No need to iterate over shader stages coming from libraries, |
| // stop at VkRayTracingPipelineCreateInfoKHR::stageCount |
| // Note: This code implicitly relies on the fact that in pipeline_state->stage_states, |
| // stages coming from libraries are added last. |
| PostCallRecordPipelineCreationShaderInstrumentation( |
| *pipeline_state, pipeline_state->RayTracingCreateInfo().stageCount, shader_instrumentation_metadata); |
| } |
| }); |
| dispatch_device_->deferred_operation_post_check.insert(deferredOperation, std::move(deferred_op_post_checks)); |
| } else { |
| for (uint32_t i = 0; i < count; ++i) { |
| const VkPipeline pipeline_handle = pPipelines[i]; |
| if (pipeline_handle == VK_NULL_HANDLE) { |
| continue; // vkspec.html#pipelines-multiple |
| } |
| |
| UtilCopyCreatePipelineFeedbackData(pCreateInfos[i], chassis_state->modified_create_infos[i]); |
| |
| auto pipeline_state = Get<vvl::Pipeline>(pipeline_handle); |
| |
| if (pipeline_state->ray_tracing_library_ci) { |
| for (VkPipeline lib : vvl::make_span(pipeline_state->ray_tracing_library_ci->pLibraries, |
| pipeline_state->ray_tracing_library_ci->libraryCount)) { |
| auto lib_state = Get<vvl::Pipeline>(lib); |
| ASSERT_AND_CONTINUE(lib_state); |
| pipeline_state->instrumentation_data.was_instrumented |= lib_state->instrumentation_data.was_instrumented; |
| } |
| } |
| |
| auto& shader_instrumentation_metadata = chassis_state->shader_instrumentations_metadata[i]; |
| // Ray tracing pipelines can be made of libraries, but contrary to GPL instrumentation is not postponed |
| // to final link time, and done at ray tracing library creation time. |
| // => No need to iterate over shader stages coming from libraries, |
| // stop at VkRayTracingPipelineCreateInfoKHR::stageCount |
| // Note: This code implicitly relies on the fact that in pipeline_state->stage_states, |
| // stages coming from libraries are added last. |
| PostCallRecordPipelineCreationShaderInstrumentation(*pipeline_state, pipeline_state->RayTracingCreateInfo().stageCount, |
| shader_instrumentation_metadata); |
| } |
| } |
| } |
| |
| // Remove all the shader trackers associated with this destroyed pipeline. |
| void GpuShaderInstrumentor::PreCallRecordDestroyPipeline(VkDevice device, VkPipeline pipeline, |
| const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) { |
| if (auto pipeline_state = Get<vvl::Pipeline>(pipeline)) { |
| for (auto shader_module_handle : pipeline_state->instrumentation_data.shader_modules) { |
| DispatchDestroyShaderModule(device, shader_module_handle, pAllocator); |
| } |
| if (pipeline_state->instrumentation_data.instrumented_pipeline_lib != VK_NULL_HANDLE) { |
| DispatchDestroyPipeline(device, pipeline_state->instrumentation_data.instrumented_pipeline_lib, pAllocator); |
| } |
| } |
| } |
| |
| template <typename CreateInfo> |
| VkShaderModule GetShaderModule(const CreateInfo& create_info, VkShaderStageFlagBits stage) { |
| for (uint32_t i = 0; i < create_info.stageCount; ++i) { |
| if (create_info.pStages[i].stage == stage) { |
| return create_info.pStages[i].module; |
| } |
| } |
| return {}; |
| } |
| |
| template <> |
| VkShaderModule GetShaderModule(const VkComputePipelineCreateInfo& create_info, VkShaderStageFlagBits) { |
| return create_info.stage.module; |
| } |
| |
| template <typename SafeType> |
| void SetShaderModule(SafeType& create_info, const vku::safe_VkPipelineShaderStageCreateInfo& stage_info, |
| VkShaderModule shader_module, uint32_t stage_ci_index) { |
| create_info.pStages[stage_ci_index] = stage_info; |
| create_info.pStages[stage_ci_index].module = shader_module; |
| } |
| |
| template <> |
| void SetShaderModule(vku::safe_VkComputePipelineCreateInfo& create_info, |
| const vku::safe_VkPipelineShaderStageCreateInfo& stage_info, VkShaderModule shader_module, |
| uint32_t stage_ci_index) { |
| assert(stage_ci_index == 0); |
| create_info.stage = stage_info; |
| create_info.stage.module = shader_module; |
| } |
| |
| template <typename CreateInfo, typename StageInfo> |
| StageInfo& GetShaderStageCI(CreateInfo& ci, VkShaderStageFlagBits stage) { |
| static StageInfo null_stage{}; |
| for (uint32_t i = 0; i < ci.stageCount; ++i) { |
| if (ci.pStages[i].stage == stage) { |
| return ci.pStages[i]; |
| } |
| } |
| return null_stage; |
| } |
| |
| template <> |
| vku::safe_VkPipelineShaderStageCreateInfo& GetShaderStageCI(vku::safe_VkComputePipelineCreateInfo& ci, VkShaderStageFlagBits) { |
| return ci.stage; |
| } |
| |
| bool GpuShaderInstrumentor::IsSelectiveInstrumentationEnabled(const void* pNext) { |
| if (auto features = vku::FindStructInPNextChain<VkValidationFeaturesEXT>(pNext)) { |
| for (uint32_t i = 0; i < features->enabledValidationFeatureCount; i++) { |
| if (features->pEnabledValidationFeatures[i] == VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool GpuShaderInstrumentor::NeedPipelineCreationShaderInstrumentation(vvl::Pipeline& pipeline_state, const Location& loc) { |
| // Currently there is a VU (VUID-VkIndirectExecutionSetPipelineInfoEXT-initialPipeline-11019) that prevents |
| // VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC in the pipeline layout, but we need it currently for GPU-AV. |
| // As a temporary solution, we will just not support people using DGC with IES |
| if (pipeline_state.create_flags & VK_PIPELINE_CREATE_2_INDIRECT_BINDABLE_BIT_EXT) { |
| InternalError(device, loc, |
| "Unable to instrument shader using VkIndirectExecutionSetEXT validly, things might work, but likely will not " |
| "because of GPU-AV's usage of VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC (If you don't need " |
| "VK_PIPELINE_CREATE_2_INDIRECT_BINDABLE_BIT_EXT, turn it off)."); |
| // don't return false, some drivers seem to not care and app might get away with it |
| } |
| |
| // will hit with using GPL without shaders in them (ex. fragment output) |
| if (pipeline_state.stage_states.empty()) { |
| return false; |
| } |
| |
| // For graphics pipelines, move all instrumentation until the final linking time |
| // This still needs to create a copy of the create_info (we *could* have a mix of GPL and non-GPL) |
| // For ray tracing pipeline, because shader binding tables can be built from the libraries before |
| // linking the exe pipeline, no choice but to always instrument libraries: SBT have to point to instrumented shaders |
| if (pipeline_state.pipeline_type != VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR && |
| (pipeline_state.create_flags & VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR) && pipeline_state.linking_shaders == 0) { |
| return false; |
| } |
| |
| // If the app requests all available sets, the pipeline layout was not modified at pipeline layout creation and the |
| // already instrumented shaders need to be replaced with uninstrumented shaders |
| if (pipeline_state.active_slots.find(instrumentation_desc_set_bind_index_) != pipeline_state.active_slots.end()) { |
| return false; |
| } |
| const auto pipeline_layout = pipeline_state.PipelineLayoutState(); |
| if (pipeline_layout && pipeline_layout->set_layouts.list.size() > instrumentation_desc_set_bind_index_) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void GpuShaderInstrumentor::BuildDescriptorSetLayoutInfo(const vvl::Pipeline& pipeline_state, |
| spirv::InstrumentationDescriptorSetLayouts& out_instrumentation_dsl) { |
| const auto pipeline_layout = pipeline_state.PipelineLayoutState(); |
| if (!pipeline_layout) { |
| return; |
| } |
| |
| out_instrumentation_dsl.set_index_to_bindings_layout_lut.resize(pipeline_layout->set_layouts.list.size()); |
| for (uint32_t set_layout_index = 0; set_layout_index < pipeline_layout->set_layouts.list.size(); set_layout_index++) { |
| if (const auto set_layout_state = pipeline_layout->set_layouts.list[set_layout_index]) { |
| BuildDescriptorSetLayoutInfo(*set_layout_state, set_layout_index, out_instrumentation_dsl); |
| } |
| } |
| |
| // Set ray tracing pipeline flags for hit objects |
| out_instrumentation_dsl.pipeline_has_skip_aabbs_flag = |
| (pipeline_state.create_flags & VK_PIPELINE_CREATE_RAY_TRACING_SKIP_AABBS_BIT_KHR) != 0; |
| out_instrumentation_dsl.pipeline_has_skip_triangles_flag = |
| (pipeline_state.create_flags & VK_PIPELINE_CREATE_RAY_TRACING_SKIP_TRIANGLES_BIT_KHR) != 0; |
| out_instrumentation_dsl.max_shader_binding_table_record_index = |
| phys_dev_ext_props.ray_tracing_invocation_reorder_props.maxShaderBindingTableRecordIndex; |
| } |
| |
| void GpuShaderInstrumentor::BuildDescriptorSetLayoutInfo(const vku::safe_VkShaderCreateInfoEXT& modified_create_info, |
| spirv::InstrumentationDescriptorSetLayouts& out_instrumentation_dsl) { |
| out_instrumentation_dsl.set_index_to_bindings_layout_lut.resize(modified_create_info.setLayoutCount); |
| for (const auto [set_layout_index, set_layout] : |
| vvl::enumerate(modified_create_info.pSetLayouts, modified_create_info.setLayoutCount)) { |
| if (auto set_layout_state = Get<vvl::DescriptorSetLayout>(set_layout)) { |
| BuildDescriptorSetLayoutInfo(*set_layout_state, set_layout_index, out_instrumentation_dsl); |
| } |
| } |
| } |
| |
| void GpuShaderInstrumentor::BuildDescriptorSetLayoutInfo(const vvl::DescriptorSetLayout& set_layout_state, |
| const uint32_t set_layout_index, |
| spirv::InstrumentationDescriptorSetLayouts& out_instrumentation_dsl) { |
| if (set_layout_state.GetBindingCount() == 0) return; |
| const uint32_t binding_count = set_layout_state.GetMaxBinding() + 1; |
| |
| auto& binding_layouts = out_instrumentation_dsl.set_index_to_bindings_layout_lut[set_layout_index]; |
| binding_layouts.resize(binding_count); |
| |
| uint32_t start = 0; |
| auto dsl_bindings = set_layout_state.GetBindings(); |
| for (uint32_t binding_index = 0; binding_index < dsl_bindings.size(); binding_index++) { |
| auto& dsl_binding = dsl_bindings[binding_index]; |
| if (dsl_binding.descriptorType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK) { |
| binding_layouts[dsl_binding.binding] = {start, 1}; |
| start += 1; |
| } else { |
| binding_layouts[dsl_binding.binding] = {start, dsl_binding.descriptorCount}; |
| start += dsl_binding.descriptorCount; |
| } |
| |
| const VkDescriptorBindingFlags flags = set_layout_state.GetDescriptorBindingFlagsFromBinding(binding_index); |
| if (vvl::IsBindless(flags)) { |
| out_instrumentation_dsl.has_bindless_descriptors = true; |
| } |
| } |
| } |
| |
| bool GpuShaderInstrumentor::IsPipelineSelectedForInstrumentation(VkPipeline pipeline, const Location& loc) { |
| if (!gpuav_settings.select_instrumented_shaders) { |
| return true; |
| } |
| |
| bool should_instrument_pipeline = false; |
| { |
| std::string pipeline_debug_name; |
| { |
| std::unique_lock<std::mutex> lock(debug_report->debug_output_mutex); |
| pipeline_debug_name = debug_report->GetUtilsObjectNameNoLock(HandleToUint64(pipeline)); |
| } |
| |
| should_instrument_pipeline = gpuav_settings.MatchesAnyShaderSelectionRegex(pipeline_debug_name); |
| } |
| if (should_instrument_pipeline) { |
| LogInfo("GPU-AV::Selective shader instrumentation", LogObjectList(), loc, "(%s) will be instrumented for validation.", |
| FormatHandle(pipeline).c_str()); |
| } |
| return should_instrument_pipeline; |
| } |
| |
| bool GpuShaderInstrumentor::IsShaderSelectedForInstrumentation(vku::safe_VkShaderModuleCreateInfo* modified_shader_module_ci, |
| VkShaderModule modified_shader, const Location& loc) { |
| if (!gpuav_settings.select_instrumented_shaders) { |
| return true; |
| } |
| |
| bool should_instrument_shader = false; |
| { |
| if (modified_shader_module_ci && IsSelectiveInstrumentationEnabled(modified_shader_module_ci->pNext)) { |
| should_instrument_shader = true; |
| } else if (selected_instrumented_shaders.find(modified_shader) != selected_instrumented_shaders.end()) { |
| should_instrument_shader = true; |
| } else { |
| std::string shader_debug_name; |
| { |
| std::unique_lock<std::mutex> lock(debug_report->debug_output_mutex); |
| shader_debug_name = debug_report->GetUtilsObjectNameNoLock(HandleToUint64(modified_shader)); |
| } |
| should_instrument_shader = gpuav_settings.MatchesAnyShaderSelectionRegex(shader_debug_name); |
| } |
| if (should_instrument_shader) { |
| LogInfo("GPU-AV::Selective shader instrumentation", LogObjectList(), loc, "(%s) will be instrumented for validation.", |
| FormatHandle(modified_shader).c_str()); |
| } |
| } |
| return should_instrument_shader; |
| } |
| |
| void GpuShaderInstrumentor::AddDescriptorHeapMappings(VkBaseOutStructure* create_info) { |
| const vku::safe_VkShaderDescriptorSetAndBindingMappingInfoEXT* mapping_info = |
| reinterpret_cast<const vku::safe_VkShaderDescriptorSetAndBindingMappingInfoEXT*>( |
| vku::FindStructInPNextChain<VkShaderDescriptorSetAndBindingMappingInfoEXT>(create_info->pNext)); |
| |
| uint32_t mapping_count = glsl::kTotalBindings; |
| uint32_t app_mapping_count = 0; |
| if (mapping_info) { |
| app_mapping_count = mapping_info->mappingCount; |
| mapping_count += app_mapping_count; |
| } |
| vku::safe_VkDescriptorSetAndBindingMappingEXT* new_mappings = new vku::safe_VkDescriptorSetAndBindingMappingEXT[mapping_count]; |
| |
| if (mapping_info) { |
| for (uint32_t i = 0; i < app_mapping_count; i++) { |
| new_mappings[i] = mapping_info->pMappings[i]; |
| } |
| } |
| |
| for (uint32_t i = 0; i < glsl::kTotalBindings; i++) { |
| vku::safe_VkDescriptorSetAndBindingMappingEXT& mapping = new_mappings[app_mapping_count + i]; |
| mapping = vku::safe_VkDescriptorSetAndBindingMappingEXT(); |
| mapping.descriptorSet = instrumentation_desc_set_bind_index_; |
| mapping.firstBinding = i; |
| mapping.bindingCount = 1; |
| mapping.resourceMask = VK_SPIRV_RESOURCE_TYPE_ALL_EXT; |
| mapping.source = VK_DESCRIPTOR_MAPPING_SOURCE_INDIRECT_ADDRESS_EXT; |
| mapping.sourceData.indirectAddress.addressOffset = sizeof(VkDeviceAddress) * i; |
| mapping.sourceData.indirectAddress.pushOffset = push_data_offset_; |
| } |
| |
| if (mapping_info) { |
| vku::safe_VkShaderDescriptorSetAndBindingMappingInfoEXT* modified_mapping_info = |
| const_cast<vku::safe_VkShaderDescriptorSetAndBindingMappingInfoEXT*>(mapping_info); |
| modified_mapping_info->mappingCount = mapping_count; |
| delete[] modified_mapping_info->pMappings; |
| modified_mapping_info->pMappings = new_mappings; |
| } else { |
| vku::safe_VkShaderDescriptorSetAndBindingMappingInfoEXT* new_mapping_info = |
| new vku::safe_VkShaderDescriptorSetAndBindingMappingInfoEXT(); |
| new_mapping_info->mappingCount = mapping_count; |
| new_mapping_info->pMappings = new_mappings; |
| new_mapping_info->pNext = create_info->pNext; |
| create_info->pNext = reinterpret_cast<VkBaseOutStructure*>(new_mapping_info); |
| } |
| } |
| |
| // Instrument all SPIR-V that is sent through pipeline. This can be done in various ways |
| // 1. VkCreateShaderModule and passed in VkShaderModule. |
| // For this we create our own VkShaderModule with instrumented shader and manage it inside the pipeline state |
| // 2. GPL |
| // We defer until linking time, otherwise we will instrument many libraries that might never be used. |
| // (this also spreads the compile time cost evenly instead of a huge spike on startup) |
| // 3. Inlined via VkPipelineShaderStageCreateInfo pNext |
| // We just instrument the shader and update the inlined SPIR-V |
| // 4. VK_EXT_shader_module_identifier |
| // We will skip these as we don't know the incoming SPIR-V |
| // Note: Shader Objects are handled in their own path as they don't use pipelines |
| template <typename SafeCreateInfo> |
| bool GpuShaderInstrumentor::PreCallRecordPipelineCreationShaderInstrumentation( |
| const VkAllocationCallbacks* pAllocator, vvl::Pipeline& pipeline_state, SafeCreateInfo& modified_pipeline_ci, |
| uint32_t stages_count, const Location& loc, |
| std::vector<chassis::ShaderInstrumentationMetadata>& shader_instrumentation_metadata) { |
| // Init here instead of in chassis so we don't pay cost when GPU-AV is not used |
| shader_instrumentation_metadata.resize(stages_count); |
| |
| spirv::InstrumentationInterface interface(loc); |
| // Can set this once for all shaders in the pipeline |
| BuildDescriptorSetLayoutInfo(pipeline_state, interface.instrumentation_dsl); |
| |
| for (uint32_t stage_state_i = 0; stage_state_i < stages_count; ++stage_state_i) { |
| const auto& stage_state = pipeline_state.stage_states[stage_state_i]; |
| auto modified_module_state = std::const_pointer_cast<vvl::ShaderModule>(stage_state.module_state); |
| ASSERT_AND_CONTINUE(modified_module_state); |
| if (!modified_module_state->spirv) { |
| continue; // Hit when using VK_KHR_pipeline_binary |
| } |
| std::unique_lock<std::mutex> module_lock(modified_module_state->module_mutex_); |
| |
| auto& instrumentation_metadata = shader_instrumentation_metadata[stage_state_i]; |
| |
| // Check pNext for inlined SPIR-V |
| // --- |
| vku::safe_VkShaderModuleCreateInfo* modified_shader_module_ci = nullptr; |
| { |
| const VkShaderStageFlagBits stage = stage_state.GetStage(); |
| auto& stage_ci = |
| GetShaderStageCI<SafeCreateInfo, vku::safe_VkPipelineShaderStageCreateInfo>(modified_pipeline_ci, stage); |
| modified_shader_module_ci = |
| const_cast<vku::safe_VkShaderModuleCreateInfo*>(reinterpret_cast<const vku::safe_VkShaderModuleCreateInfo*>( |
| vku::FindStructInPNextChain<VkShaderModuleCreateInfo>(stage_ci.pNext))); |
| |
| if (!IsShaderSelectedForInstrumentation(modified_shader_module_ci, modified_module_state->VkHandle(), |
| loc.dot(vvl::Field::pStages, stage_state_i).dot(vvl::Field::module))) { |
| continue; |
| } |
| } |
| std::vector<uint32_t> instrumented_spirv; |
| const uint32_t unique_shader_id = unique_shader_module_id_++; |
| |
| interface.unique_shader_id = unique_shader_id; |
| interface.entry_point_name = stage_state.GetPName(); |
| interface.entry_point_stage = stage_state.GetStage(); |
| interface.specialization_info = stage_state.GetSpecializationInfo()->ptr(); |
| interface.has_task_shader = (pipeline_state.active_shaders & VK_SHADER_STAGE_TASK_BIT_EXT) != 0; |
| interface.descriptor_mode = pipeline_state.descriptor_heap_mode ? vvl::DescriptorModeHeap |
| : pipeline_state.descriptor_buffer_mode ? vvl::DescriptorModeBuffer |
| : vvl::DescriptorModeClassic; |
| const bool is_shader_instrumented = InstrumentShader(modified_module_state->spirv->words_, interface, instrumented_spirv); |
| if (is_shader_instrumented) { |
| instrumentation_metadata.unique_shader_id = unique_shader_id; |
| if (modified_module_state->VkHandle() != VK_NULL_HANDLE) { |
| // If the user used vkCreateShaderModule, we create a new VkShaderModule to replace with the instrumented |
| // shader |
| VkShaderModuleCreateInfo instrumented_shader_module_ci = vku::InitStructHelper(); |
| instrumented_shader_module_ci.pCode = instrumented_spirv.data(); |
| instrumented_shader_module_ci.codeSize = instrumented_spirv.size() * sizeof(uint32_t); |
| VkShaderModule instrumented_shader_module = VK_NULL_HANDLE; |
| VkResult result = |
| DispatchCreateShaderModule(device, &instrumented_shader_module_ci, pAllocator, &instrumented_shader_module); |
| if (result == VK_SUCCESS) { |
| SetShaderModule(modified_pipeline_ci, *stage_state.pipeline_create_info, instrumented_shader_module, |
| stage_state_i); |
| |
| pipeline_state.instrumentation_data.shader_modules.emplace_back(instrumented_shader_module); |
| pipeline_state.instrumentation_data.was_instrumented = true; |
| } else { |
| InternalError(device, loc, "Unable to replace non-instrumented shader with instrumented one."); |
| return false; |
| } |
| } else if (modified_shader_module_ci) { |
| // The user is inlining the Shader Module into the pipeline, so just need to update the spirv |
| instrumentation_metadata.passed_in_shader_stage_ci = true; |
| // TODO - This makes a copy, but could save on Chassis stack instead (then remove function from VUL). |
| // The core issue is we always use std::vector<uint32_t> but Safe Struct manages its own version of the pCode |
| // memory. It would be much harder to change everything from std::vector and instead to adjust Safe Struct to not |
| // double-free the memory on us. If making any changes, we have to consider a case where the user inlines the |
| // fragment shader, but use a normal VkShaderModule in the vertex shader. |
| modified_shader_module_ci->SetCode(instrumented_spirv); |
| } else { |
| assert(false); |
| return false; |
| } |
| } |
| |
| if (stage_state.descriptor_heap_mode) { |
| const VkShaderStageFlagBits stage = stage_state.GetStage(); |
| auto& stage_ci = |
| GetShaderStageCI<SafeCreateInfo, vku::safe_VkPipelineShaderStageCreateInfo>(modified_pipeline_ci, stage); |
| AddDescriptorHeapMappings(reinterpret_cast<VkBaseOutStructure*>(&stage_ci)); |
| } |
| } |
| return true; |
| } |
| |
| // Now that we have created the pipeline (and have its handle) build up the shader map for each shader we instrumented |
| void GpuShaderInstrumentor::PostCallRecordPipelineCreationShaderInstrumentation( |
| vvl::Pipeline& pipeline_state, uint32_t stages_count, |
| std::vector<chassis::ShaderInstrumentationMetadata>& shader_instrumentation_metadata) { |
| // if we return early from NeedPipelineCreationShaderInstrumentation, will need to skip at this point in PostCall |
| if (shader_instrumentation_metadata.empty()) return; |
| |
| for (uint32_t stage_state_i = 0; stage_state_i < stages_count; ++stage_state_i) { |
| auto& instrumentation_metadata = shader_instrumentation_metadata[stage_state_i]; |
| |
| // if the shader for some reason was not instrumented, there is nothing to save |
| if (!instrumentation_metadata.IsInstrumented()) { |
| continue; |
| } |
| pipeline_state.instrumentation_data.was_instrumented = true; |
| |
| const auto& stage_state = pipeline_state.stage_states[stage_state_i]; |
| auto& module_state = stage_state.module_state; |
| |
| // We currently need to store a copy of the original, non-instrumented shader so if there is debug information, |
| // we can reference it by the instruction number printed out in the shader. Since the application can destroy the |
| // original VkShaderModule, there is a chance this will be gone, we need to copy it now. |
| // TODO - in the instrumentation, instead of printing the instruction number only, if we print out debug info, we |
| // can remove this copy |
| std::vector<uint32_t> code; |
| if (module_state && module_state->spirv) code = module_state->spirv->words_; |
| |
| VkShaderModule shader_module_handle = module_state->VkHandle(); |
| if (shader_module_handle == VK_NULL_HANDLE && instrumentation_metadata.passed_in_shader_stage_ci) { |
| shader_module_handle = kPipelineStageInfoHandle; |
| } |
| |
| instrumented_shaders_map_.insert_or_assign(instrumentation_metadata.unique_shader_id, pipeline_state.VkHandle(), |
| shader_module_handle, VK_NULL_HANDLE, std::move(code)); |
| } |
| } |
| |
| // While have an almost duplicated function is not ideal, the core issue is we have a single, templated function designed for |
| // Graphics, Compute, and Ray Tracing. GPL is only for graphics, so we end up needing this "side code path" for graphics only and it |
| // doesn't fit in the "all pipeline" templated flow. |
| bool GpuShaderInstrumentor::PreCallRecordPipelineCreationShaderInstrumentationGPL( |
| const VkAllocationCallbacks* pAllocator, vvl::Pipeline& linked_pipeline_state, |
| vku::safe_VkGraphicsPipelineCreateInfo& modified_pipeline_ci, const Location& loc, |
| std::vector<chassis::ShaderInstrumentationMetadata>& shader_instrumentation_metadata) { |
| // Init here instead of in chassis so we don't pay cost when GPU-AV is not used |
| const size_t total_stages = linked_pipeline_state.stage_states.size(); |
| shader_instrumentation_metadata.resize(total_stages); |
| |
| spirv::InstrumentationInterface interface(loc); |
| // Can set this once for all shaders in the pipeline |
| BuildDescriptorSetLayoutInfo(linked_pipeline_state, interface.instrumentation_dsl); |
| |
| auto modified_library_ci = const_cast<VkPipelineLibraryCreateInfoKHR*>( |
| vku::FindStructInPNextChain<VkPipelineLibraryCreateInfoKHR>(modified_pipeline_ci.pNext)); |
| |
| // the "pStages[]" is spread across libraries, so build it up in the double for loop |
| uint32_t shader_i = 0; |
| |
| // This outer loop is the main difference between the GPL and non-GPL version and why its hard to merge them |
| for (uint32_t modified_lib_i = 0; modified_lib_i < modified_library_ci->libraryCount; ++modified_lib_i) { |
| const auto modified_lib = Get<vvl::Pipeline>(modified_library_ci->pLibraries[modified_lib_i]); |
| if (!modified_lib) { |
| continue; |
| } |
| if (modified_lib->stage_states.empty()) { |
| continue; |
| } |
| |
| // without this, would get ASAN for things like |
| // modified_lib->instrumentation_data.shader_modules.emplace_back() |
| std::unique_lock<std::mutex> lib_lock(modified_lib->instrumentation_data.mutex); |
| |
| // If a library is used to create multiple executable pipelines, we don't want to instrument it again. |
| // Check that there is indeed an instrumented_pipeline_lib: |
| // The library could be considered instrumented if itself it was made up of instrumented libraries, |
| // but in this case instrumented_pipeline_lib would not have been set. |
| // Note: Well in this case we could use modified_lib->VkHandle()? |
| if (modified_lib->instrumentation_data.was_instrumented && |
| (modified_lib->instrumentation_data.instrumented_pipeline_lib != VK_NULL_HANDLE)) { |
| assert(modified_lib->instrumentation_data.instrumented_pipeline_lib != VK_NULL_HANDLE); |
| const_cast<VkPipeline*>(modified_library_ci->pLibraries)[modified_lib_i] = |
| modified_lib->instrumentation_data.instrumented_pipeline_lib; |
| linked_pipeline_state.instrumentation_data.was_instrumented = true; |
| continue; |
| } |
| |
| vku::safe_VkGraphicsPipelineCreateInfo new_lib_ci(modified_lib->GraphicsCreateInfo()); |
| // If the application supplied pipeline might be interested in failing to be created |
| // if the driver does not find it in its cache, GPU-AV needs to succeed in the instrumented pipeline library |
| // creation process no matter caching state. |
| new_lib_ci.flags &= ~VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT; |
| bool need_new_pipeline = false; |
| |
| // If pipeline library is selected for instrumentation, force instrumentation of all its shaders |
| const bool should_instrument_pipeline = |
| IsPipelineSelectedForInstrumentation(modified_lib->VkHandle(), loc.dot(vvl::Field::pLibraries, modified_lib_i)); |
| for (uint32_t stage_state_i = 0; stage_state_i < static_cast<uint32_t>(modified_lib->stage_states.size()); |
| ++stage_state_i) { |
| const ShaderStageState& modified_stage_state = modified_lib->stage_states[stage_state_i]; |
| auto modified_module_state = std::const_pointer_cast<vvl::ShaderModule>(modified_stage_state.module_state); |
| ASSERT_AND_CONTINUE(modified_module_state); |
| if (!modified_module_state->spirv) { |
| continue; // Hit when using VK_KHR_pipeline_binary |
| } |
| std::unique_lock<std::mutex> module_lock(modified_module_state->module_mutex_); |
| |
| chassis::ShaderInstrumentationMetadata& instrumentation_metadata = shader_instrumentation_metadata[shader_i++]; |
| |
| // Check pNext for inlined SPIR-V |
| // --- |
| vku::safe_VkShaderModuleCreateInfo* modified_shader_module_ci = nullptr; |
| { |
| vku::safe_VkPipelineShaderStageCreateInfo* modified_stage_ci = nullptr; |
| const VkShaderStageFlagBits stage = modified_stage_state.GetStage(); |
| for (uint32_t i = 0; i < new_lib_ci.stageCount; ++i) { |
| if (new_lib_ci.pStages[i].stage == stage) { |
| modified_stage_ci = &new_lib_ci.pStages[i]; |
| } |
| } |
| |
| // Getting here means that the currently explored stage state comes |
| // from a sub library, one used to composed the currently explored library. |
| // This sub library itself went through the linking process, thus has |
| // already been instrumented. |
| // => Just proceed to the next shader stage. |
| if (!modified_stage_ci) { |
| continue; |
| } |
| |
| modified_shader_module_ci = |
| const_cast<vku::safe_VkShaderModuleCreateInfo*>(reinterpret_cast<const vku::safe_VkShaderModuleCreateInfo*>( |
| vku::FindStructInPNextChain<VkShaderModuleCreateInfo>(modified_stage_ci->pNext))); |
| |
| // TODO - this is in need of testing, when only selecting various library as well as selecting everything |
| if (!should_instrument_pipeline && |
| !IsShaderSelectedForInstrumentation(modified_shader_module_ci, modified_module_state->VkHandle(), |
| loc.dot(vvl::Field::pStages, stage_state_i).dot(vvl::Field::module))) { |
| continue; |
| } |
| |
| if (modified_stage_state.descriptor_heap_mode) { |
| AddDescriptorHeapMappings(reinterpret_cast<VkBaseOutStructure*>(modified_stage_ci)); |
| } |
| } |
| |
| // Instrument shader |
| // --- |
| std::vector<uint32_t> instrumented_spirv; |
| const uint32_t unique_shader_id = unique_shader_module_id_++; |
| |
| interface.unique_shader_id = unique_shader_id; |
| interface.entry_point_name = modified_stage_state.GetPName(); |
| interface.entry_point_stage = modified_stage_state.GetStage(); |
| interface.specialization_info = modified_stage_state.GetSpecializationInfo()->ptr(); |
| interface.has_task_shader = (linked_pipeline_state.active_shaders & VK_SHADER_STAGE_TASK_BIT_EXT) != 0; |
| interface.descriptor_mode = linked_pipeline_state.descriptor_heap_mode ? vvl::DescriptorModeHeap |
| : linked_pipeline_state.descriptor_buffer_mode ? vvl::DescriptorModeBuffer |
| : vvl::DescriptorModeClassic; |
| const bool is_shader_instrumented = |
| InstrumentShader(modified_module_state->spirv->words_, interface, instrumented_spirv); |
| |
| if (is_shader_instrumented) { |
| instrumentation_metadata.unique_shader_id = unique_shader_id; |
| need_new_pipeline = true; |
| } |
| |
| if (modified_module_state->VkHandle() != VK_NULL_HANDLE) { |
| // If the user used vkCreateShaderModule, we create a new VkShaderModule to replace with the instrumented |
| // shader |
| VkShaderModule instrumented_shader_module; |
| VkShaderModuleCreateInfo create_info = vku::InitStructHelper(); |
| if (is_shader_instrumented) { |
| create_info.pCode = instrumented_spirv.data(); |
| create_info.codeSize = instrumented_spirv.size() * sizeof(uint32_t); |
| } else { |
| // We need to replace the shader regardless as the user may have destroyed the original VkShaderModule and |
| // we will crash trying to unwrap it. So just make a duplicate VkShaderModule. (This is rare we hit this, |
| // only when the user has a shader with nothing to instrument, which tends to be passthrough vertex shaders |
| // which are quick enough to re-create) |
| create_info.pCode = modified_module_state->spirv->words_.data(); |
| create_info.codeSize = modified_module_state->spirv->words_.size() * sizeof(uint32_t); |
| } |
| VkResult result = DispatchCreateShaderModule(device, &create_info, pAllocator, &instrumented_shader_module); |
| if (result == VK_SUCCESS) { |
| new_lib_ci.pStages[stage_state_i] = *modified_stage_state.pipeline_create_info; |
| new_lib_ci.pStages[stage_state_i].module = instrumented_shader_module; |
| |
| modified_lib->instrumentation_data.shader_modules.emplace_back(instrumented_shader_module); |
| |
| } else { |
| InternalError(device, loc, "Unable to replace non-instrumented shader with instrumented one."); |
| return false; |
| } |
| } else if (modified_shader_module_ci) { |
| // If inlining and not instrumented, leave it alone |
| if (is_shader_instrumented) { |
| // The user is inlining the Shader Module into the pipeline, so just need to update the spirv |
| instrumentation_metadata.passed_in_shader_stage_ci = true; |
| // TODO - This makes a copy, but could save on Chassis stack instead (then remove function from VUL). |
| // The core issue is we always use std::vector<uint32_t> but Safe Struct manages its own version of the pCode |
| // memory. It would be much harder to change everything from std::vector and instead to adjust Safe Struct to |
| // not double-free the memory on us. If making any changes, we have to consider a case where the user inlines |
| // the fragment shader, but use a normal VkShaderModule in the vertex shader. |
| modified_shader_module_ci->SetCode(instrumented_spirv); |
| } |
| } else { |
| assert(false); |
| return false; |
| } |
| } |
| |
| // Create instrumented pipeline library if we have instrumented one of the libraries inside of it |
| if (need_new_pipeline) { |
| VkPipeline instrumented_pipeline_lib = VK_NULL_HANDLE; |
| |
| // The library could have destroyed its pipelineLayout, but will have a valid, compatible, version when linking |
| if (new_lib_ci.layout != VK_NULL_HANDLE && !Get<vvl::PipelineLayout>(new_lib_ci.layout)) { |
| new_lib_ci.layout = modified_pipeline_ci.layout; |
| } |
| |
| const VkResult result = DispatchCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, new_lib_ci.ptr(), pAllocator, |
| &instrumented_pipeline_lib); |
| if (result != VK_SUCCESS || instrumented_pipeline_lib == VK_NULL_HANDLE) { |
| // could just check result, but being extra cautious around GPL and checking handle as well |
| InternalError(device, loc, "Failed to recreate instrumented pipeline library."); |
| return false; |
| } |
| |
| modified_lib->instrumentation_data.instrumented_pipeline_lib = instrumented_pipeline_lib; |
| modified_lib->instrumentation_data.was_instrumented = true; |
| linked_pipeline_state.instrumentation_data.was_instrumented = true; |
| |
| const_cast<VkPipeline*>(modified_library_ci->pLibraries)[modified_lib_i] = instrumented_pipeline_lib; |
| } |
| } |
| return true; |
| } |
| |
| void GpuShaderInstrumentor::PostCallRecordPipelineCreationShaderInstrumentationGPL( |
| vvl::Pipeline& pipeline_state, std::vector<chassis::ShaderInstrumentationMetadata>& shader_instrumentation_metadata) { |
| // if we return early from NeedPipelineCreationShaderInstrumentation, will need to skip at this point in PostCall |
| if (shader_instrumentation_metadata.empty()) { |
| return; |
| } |
| |
| uint32_t shader_index = 0; |
| // Update instrumented_shaders_map_ |
| // #ARNO_TODO should be done at instrumentation time, why postpone? We don't need the linked pipeline handle |
| for (uint32_t library_i = 0; library_i < pipeline_state.library_create_info->libraryCount; ++library_i) { |
| const auto lib = Get<vvl::Pipeline>(pipeline_state.library_create_info->pLibraries[library_i]); |
| if (!lib) { |
| continue; |
| } |
| if (lib->stage_states.empty()) { |
| continue; |
| } |
| |
| for (uint32_t stage_state_i = 0; stage_state_i < static_cast<uint32_t>(lib->stage_states.size()); ++stage_state_i) { |
| auto& instrumentation_metadata = shader_instrumentation_metadata[shader_index++]; |
| |
| // if the shader for some reason was not instrumented, there is nothing to save |
| if (!instrumentation_metadata.IsInstrumented()) { |
| continue; |
| } |
| |
| const auto& stage_state = lib->stage_states[stage_state_i]; |
| auto& module_state = stage_state.module_state; |
| |
| // We currently need to store a copy of the original, non-instrumented shader so if there is debug information, |
| // we can reference it by the instruction number printed out in the shader. Since the application can destroy the |
| // original VkShaderModule, there is a chance this will be gone, we need to copy it now. |
| // TODO - in the instrumentation, instead of printing the instruction number only, if we print out debug info, we |
| // can remove this copy |
| std::vector<uint32_t> code; |
| if (module_state && module_state->spirv) { |
| code = module_state->spirv->words_; |
| } |
| |
| VkShaderModule shader_module_handle = module_state->VkHandle(); |
| if (shader_module_handle == VK_NULL_HANDLE && instrumentation_metadata.passed_in_shader_stage_ci) { |
| shader_module_handle = kPipelineStageInfoHandle; |
| } |
| |
| instrumented_shaders_map_.insert_or_assign(instrumentation_metadata.unique_shader_id, lib->VkHandle(), |
| shader_module_handle, VK_NULL_HANDLE, std::move(code)); |
| } |
| } |
| } |
| |
| static bool GpuValidateShader(const std::vector<uint32_t>& input, spv_target_env target_env, std::string& error) { |
| // Use SPIRV-Tools validator to try and catch any issues with the module |
| spv_context ctx = spvContextCreate(target_env); |
| spv_const_binary_t binary{input.data(), input.size()}; |
| spv_diagnostic diag = nullptr; |
| spv_validator_options options = spvValidatorOptionsCreate(); |
| // Everything is running scalar block layout now (only old 1.0 mobile GPU can't support this) |
| spvValidatorOptionsSetScalarBlockLayout(options, true); |
| spv_result_t result = spvValidateWithOptions(ctx, options, &binary, &diag); |
| if (result != SPV_SUCCESS && diag) error = diag->error; |
| return (result == SPV_SUCCESS); |
| } |
| |
| bool GpuShaderInstrumentor::InstrumentShader(const vvl::span<const uint32_t>& input_spirv, |
| const spirv::InstrumentationInterface& interface, |
| std::vector<uint32_t>& out_instrumented_spirv) { |
| if (input_spirv[0] != spv::MagicNumber) { |
| return false; |
| } |
| |
| if (interface.unique_shader_id >= glsl::kMaxInstrumentedShaders) { |
| InternalWarning(device, interface.loc, "kMaxInstrumentedShaders limit has been hit, no shaders can be instrumented."); |
| return false; |
| } else if ((input_spirv.size() * sizeof(uint32_t)) > (1 << glsl::kStageId_Shift)) { |
| // If we are hitting this, will need to rethink limit (if someone hits this, please raise an issue!) |
| InternalWarning( |
| device, interface.loc, |
| "The shader is larger than 128MB and there are only 27 bits to store the offset into the spirv where an error occurs."); |
| return false; |
| } |
| |
| if (gpuav_settings.debug_dump_instrumented_shaders) { |
| const auto non_instrumented_spirv_file = fs::absolute("dump_" + std::to_string(interface.unique_shader_id) + "_before.spv"); |
| DumpSpirvToFile(non_instrumented_spirv_file.string(), input_spirv.data(), input_spirv.size()); |
| } |
| |
| spirv::Module module(input_spirv, debug_report, instrumentation_device_settings_, interface, modified_features); |
| |
| bool modified = false; |
| |
| // If descriptor indexing is enabled, enable length checks and updated descriptor checks |
| if (gpuav_settings.shader_instrumentation.descriptor_checks) { |
| if (interface.descriptor_mode == vvl::DescriptorModeClassic) { |
| // Will wrap descriptor indexing with if/else to prevent crashing if OOB |
| spirv::DescriptorIndexingOOBPass oob_pass(module); |
| modified |= oob_pass.Run(); |
| |
| // Depending on the DescriptorClass, will add dedicated check |
| if (!modified_features.robustBufferAccess) { |
| // This check is for catching OOB in a UBO/SSBO which is caught with robustBufferAccess |
| spirv::DescriptorClassGeneralBufferPass general_buffer_pass(module); |
| modified |= general_buffer_pass.Run(); |
| |
| // Details being worked out in https://gitlab.khronos.org/vulkan/vulkan/-/issues/3977 |
| // But for what we are checking for, can rely on robustBufferAccess |
| spirv::DescriptorClassTexelBufferPass texel_buffer_pass(module); |
| modified |= texel_buffer_pass.Run(); |
| } else if (modified_features.cooperativeMatrix && !modified_features.cooperativeMatrixRobustBufferAccess) { |
| // Cooperative Matrix OOB rules are unique and have their own robustness feature, so still need to run the pass |
| spirv::DescriptorClassGeneralBufferPass general_buffer_pass(module); |
| modified |= general_buffer_pass.Run(); |
| } |
| } |
| } |
| |
| if (gpuav_settings.shader_instrumentation.buffer_device_address) { |
| spirv::BufferDeviceAddressPass pass(module); |
| modified |= pass.Run(); |
| } |
| |
| if (gpuav_settings.shader_instrumentation.ray_query) { |
| spirv::RayQueryPass pass(module); |
| modified |= pass.Run(); |
| } |
| |
| if (gpuav_settings.shader_instrumentation.trace_ray) { |
| spirv::TraceRayPass pass(module); |
| modified |= pass.Run(); |
| } |
| |
| if (gpuav_settings.shader_instrumentation.shared_memory_data_race) { |
| spirv::SharedMemoryDataRacePass pass(module); |
| modified |= pass.Run(); |
| } |
| |
| if (gpuav_settings.shader_instrumentation.mesh_shading) { |
| spirv::MeshShading pass(module); |
| modified |= pass.Run(); |
| } |
| |
| // Post Process instrumentation passes assume the things inside are valid, but putting at the end, things above will wrap checks |
| // in a if/else, this means they will be gaurded as if they were inside the above passes |
| if (gpuav_settings.shader_instrumentation.post_process_descriptor_indexing) { |
| if (interface.descriptor_mode == vvl::DescriptorModeClassic) { |
| spirv::PostProcessDescriptorIndexingPass pass(module); |
| modified |= pass.Run(); |
| } |
| } |
| |
| if (gpuav_settings.shader_instrumentation.vertex_attribute_fetch_oob) { |
| if (!modified_features.robustBufferAccess) { |
| spirv::VertexAttributeFetchOobPass pass(module); |
| modified |= pass.Run(); |
| } |
| } |
| |
| // Currently only runs in Safe Mode until have perf numbers how costly it is to enable |
| if (gpuav_settings.shader_instrumentation.sanitizer && gpuav_settings.safe_mode) { |
| spirv::SanitizerPass pass(module); |
| modified |= pass.Run(); |
| } |
| |
| // If we have passes that require inject LogError before the shader end we do it now. |
| // We have a dedicated pass to ensure the LogError is only added once |
| if (module.need_log_error_) { |
| spirv::LogErrorPass log_error_pass(module); |
| modified |= log_error_pass.Run(); |
| } |
| |
| // Currently we don't link anything in this pass, could move after linking if desired |
| // If we do decide to link things, we need to add ABOVE the LogErrorPass |
| if (gpuav_settings.debug_descriptor_enabled) { |
| spirv::DebugDescriptorPass pass(module, glsl::kBindingInstDebugDescriptor); |
| modified |= pass.Run(); |
| } |
| |
| // If there were GLSL written function injected, we will grab them and link them in here |
| for (const auto& info : module.link_infos_) { |
| module.LinkFunctions(info); |
| } |
| |
| // DebugPrintf goes at the end for 2 reasons: |
| // 1. We use buffer device address in it and we don't want to validate the inside of this pass |
| // 2. We might want to debug the above passes and want to inject our own debug printf calls |
| if (gpuav_settings.debug_printf_enabled) { |
| // binding slot allows debug printf to be slotted in the same set as GPU-AV if needed |
| spirv::DebugPrintfPass pass(module, internal_only_debug_printf_, glsl::kBindingInstDebugPrintf); |
| modified |= pass.Run(); |
| } |
| |
| // If nothing was instrumented, leave early to save time |
| if (!modified) { |
| return false; |
| } |
| |
| // some small cleanup to make sure SPIR-V is legal |
| module.PostProcess(); |
| // translate internal representation of SPIR-V into legal SPIR-V binary |
| module.ToBinary(out_instrumented_spirv); |
| |
| // (Maybe) validate the instrumented and linked shader |
| bool is_instrumented_spirv_valid = true; |
| if (gpuav_settings.debug_validate_instrumented_shaders) { |
| std::string spirv_val_error; |
| |
| spv_target_env target_env = PickSpirvEnv(api_version, IsExtEnabled(extensions.vk_khr_spirv_1_4)); |
| is_instrumented_spirv_valid = GpuValidateShader(out_instrumented_spirv, target_env, spirv_val_error); |
| if (!is_instrumented_spirv_valid) { |
| if (!gpuav_settings.debug_dump_instrumented_shaders) { |
| const auto non_instrumented_spirv_file = |
| fs::absolute("dump_" + std::to_string(interface.unique_shader_id) + "_before.spv"); |
| DumpSpirvToFile(non_instrumented_spirv_file.string(), input_spirv.data(), input_spirv.size()); |
| } |
| |
| const auto instrumented_spirv_file = |
| fs::absolute("dump_" + std::to_string(interface.unique_shader_id) + "_after_invalid.spv"); |
| DumpSpirvToFile(instrumented_spirv_file.string(), out_instrumented_spirv.data(), out_instrumented_spirv.size()); |
| |
| std::ostringstream strm; |
| const auto invalid_file_path = std::filesystem::absolute(instrumented_spirv_file); |
| strm << "Instrumented shader (id " << interface.unique_shader_id << ") is invalid, spirv-val error:\n" |
| << spirv_val_error << "\nInvalid spirv dumped to " << invalid_file_path |
| << "\nProceeding with non instrumented shader."; |
| InternalError(device, interface.loc, strm.str().c_str()); |
| return false; |
| } |
| } |
| if (is_instrumented_spirv_valid && gpuav_settings.debug_dump_instrumented_shaders) { |
| const auto instrumented_spirv_file = fs::absolute("dump_" + std::to_string(interface.unique_shader_id) + "_after.spv"); |
| DumpSpirvToFile(instrumented_spirv_file.string(), out_instrumented_spirv.data(), out_instrumented_spirv.size()); |
| } |
| |
| return true; |
| } |
| |
| void GpuShaderInstrumentor::InternalError(LogObjectList objlist, const Location& loc, const char* const specific_message) const { |
| aborted_ = true; |
| std::string error_message = specific_message; |
| |
| const char* layer_name = gpuav_settings.debug_printf_only ? "DebugPrintf" : "GPU-AV"; |
| const char* vuid = gpuav_settings.debug_printf_only ? "UNASSIGNED-DEBUG-PRINTF" : "UNASSIGNED-GPU-Assisted-Validation"; |
| |
| LogError(vuid, objlist, loc, "Internal Error, %s is being disabled. Details:\n%s", layer_name, error_message.c_str()); |
| |
| // Once we encounter an internal issue disconnect everything. |
| // This prevents need to check "if (aborted)" (which is awful when we easily forget to check somewhere and the user gets spammed |
| // with errors making it hard to see the first error with the real source of the problem). |
| dispatch_device_->ReleaseValidationObject(LayerObjectTypeGpuAssisted); |
| } |
| |
| // Dedicated warning VUID that likely can be ignored. |
| // We want to always warn the user when adjusting settings/limits/features/etc on them |
| void GpuShaderInstrumentor::AdjustmentWarning(LogObjectList objlist, const Location& loc, |
| const char* const specific_message) const { |
| LogWarning("WARNING-Setting-Limit-Adjusted", objlist, loc, "Warning that validation is adjusting settings:\n%s", |
| specific_message); |
| } |
| |
| void GpuShaderInstrumentor::InternalWarning(LogObjectList objlist, const Location& loc, const char* const specific_message) const { |
| const char* vuid = gpuav_settings.debug_printf_only ? "WARNING-DEBUG-PRINTF" : "WARNING-GPU-Assisted-Validation"; |
| LogWarning(vuid, objlist, loc, "Internal Warning: %s", specific_message); |
| } |
| |
| void GpuShaderInstrumentor::InternalInfo(LogObjectList objlist, const Location& loc, const char* const specific_message) const { |
| const char* vuid = gpuav_settings.debug_printf_only ? "INFO-DEBUG-PRINTF" : "INFO-GPU-Assisted-Validation"; |
| LogInfo(vuid, objlist, loc, "Internal Info: %s", specific_message); |
| } |
| |
| // The lock (debug_output_mutex) is held by the caller, |
| // because the latter has code paths that make multiple calls of this function, |
| // and all such calls have to access the same debug reporting state to ensure consistency of output information. |
| static std::string LookupDebugUtilsNameNoLock(const DebugReport* debug_report, const uint64_t object) { |
| auto object_label = debug_report->GetUtilsObjectNameNoLock(object); |
| if (object_label != "") { |
| object_label = "(" + object_label + ")"; |
| } |
| return object_label; |
| } |
| |
| // Generate the stage-specific part of the message. |
| static void GenerateStageMessage(std::ostringstream& ss, const uint32_t* error_record, const std::vector<uint32_t>& instructions) { |
| const uint32_t stage_id = error_record[glsl::kHeader_StageInstructionIdOffset] >> glsl::kStageId_Shift; |
| const uint32_t stage_info_0 = error_record[glsl::kHeader_StageInfoOffset_0]; |
| const uint32_t stage_info_1 = error_record[glsl::kHeader_StageInfoOffset_1]; |
| const uint32_t stage_info_2 = error_record[glsl::kHeader_StageInfoOffset_2]; |
| |
| switch (stage_id) { |
| case glsl::kExecutionModel_Vertex: { |
| ss << "Stage = Vertex. Vertex Index = " << stage_info_0 << " Instance Index = " << stage_info_1 << ". "; |
| } break; |
| case glsl::kExecutionModel_TessellationControl: { |
| ss << "Stage = Tessellation Control. Invocation ID = " << stage_info_0 << ", Primitive ID = " << stage_info_1; |
| } break; |
| case glsl::kExecutionModel_TessellationEvaluation: { |
| ss << "Stage = Tessellation Eval. Primitive ID = " << stage_info_0 << ", TessCoord (u, v) = (" << stage_info_1 << ", " |
| << stage_info_2 << "). "; |
| } break; |
| case glsl::kExecutionModel_Geometry: { |
| ss << "Stage = Geometry. Primitive ID = " << stage_info_0 << " Invocation ID = " << stage_info_1 << ". "; |
| } break; |
| case glsl::kExecutionModel_Fragment: { |
| // Should use std::bit_cast but requires c++20 |
| // need memcpy or -Wstrict-aliasing will yell |
| float x_coord; |
| float y_coord; |
| std::memcpy(&x_coord, &stage_info_0, sizeof(float)); |
| std::memcpy(&y_coord, &stage_info_1, sizeof(float)); |
| ss << "Stage = Fragment. Fragment coord (x,y) = (" << x_coord << ", " << y_coord << "). "; |
| } break; |
| case glsl::kExecutionModel_GLCompute: { |
| ss << "Stage = Compute. Global invocation ID (x, y, z) = (" << stage_info_0 << ", " << stage_info_1 << ", " |
| << stage_info_2 << ")"; |
| } break; |
| case glsl::kExecutionModel_RayGenerationKHR: { |
| ss << "Stage = Ray Generation. Global Launch ID (x,y,z) = (" << stage_info_0 << ", " << stage_info_1 << ", " |
| << stage_info_2 << "). "; |
| } break; |
| case glsl::kExecutionModel_IntersectionKHR: { |
| ss << "Stage = Intersection. Global Launch ID (x,y,z) = (" << stage_info_0 << ", " << stage_info_1 << ", " |
| << stage_info_2 << "). "; |
| } break; |
| case glsl::kExecutionModel_AnyHitKHR: { |
| ss << "Stage = Any Hit. Global Launch ID (x,y,z) = (" << stage_info_0 << ", " << stage_info_1 << ", " << stage_info_2 |
| << "). "; |
| } break; |
| case glsl::kExecutionModel_ClosestHitKHR: { |
| ss << "Stage = Closest Hit. Global Launch ID (x,y,z) = (" << stage_info_0 << ", " << stage_info_1 << ", " |
| << stage_info_2 << "). "; |
| } break; |
| case glsl::kExecutionModel_MissKHR: { |
| ss << "Stage = Miss. Global Launch ID (x,y,z) = (" << stage_info_0 << ", " << stage_info_1 << ", " << stage_info_2 |
| << "). "; |
| } break; |
| case glsl::kExecutionModel_CallableKHR: { |
| ss << "Stage = Callable. Global Launch ID (x,y,z) = (" << stage_info_0 << ", " << stage_info_1 << ", " << stage_info_2 |
| << "). "; |
| } break; |
| case glsl::kExecutionModel_TaskEXT: { |
| ss << "Stage = TaskEXT. Global invocation ID (x, y, z) = (" << stage_info_0 << ", " << stage_info_1 << ", " |
| << stage_info_2 << ")"; |
| } break; |
| case glsl::kExecutionModel_MeshEXT: { |
| ss << "Stage = MeshEXT. Global invocation ID (x, y, z) = (" << stage_info_0 << ", " << stage_info_1 << ", " |
| << stage_info_2 << ")"; |
| } break; |
| case glsl::kExecutionModel_TaskNV: { |
| ss << "Stage = TaskNV. Global invocation ID (x, y, z) = (" << stage_info_0 << ", " << stage_info_1 << ", " |
| << stage_info_2 << ")"; |
| } break; |
| case glsl::kExecutionModel_MeshNV: { |
| ss << "Stage = MeshNV. Global invocation ID (x, y, z) = (" << stage_info_0 << ", " << stage_info_1 << ", " |
| << stage_info_2 << ")"; |
| } break; |
| default: { |
| ss << "Internal Error (unexpected stage = " << stage_id << "). "; |
| assert(false); |
| } break; |
| } |
| ss << '\n'; |
| } |
| |
| // Where we build up the error message with all the useful debug information about where the error occurred |
| std::string GpuShaderInstrumentor::GenerateDebugInfoMessage(VkCommandBuffer commandBuffer, const uint32_t* error_record, |
| const InstrumentedShader* instrumented_shader, |
| VkPipelineBindPoint pipeline_bind_point, |
| uint32_t action_command_index) const { |
| std::ostringstream ss; |
| if (!instrumented_shader || instrumented_shader->original_spirv.empty()) { |
| ss << "[Internal Error] - Can't get instructions from shader_map\n"; |
| return ss.str(); |
| } |
| |
| GenerateStageMessage(ss, error_record, instrumented_shader->original_spirv); |
| |
| ss << std::hex << std::showbase; |
| if (instrumented_shader->shader_module == VK_NULL_HANDLE && instrumented_shader->shader_object == VK_NULL_HANDLE) { |
| std::unique_lock<std::mutex> lock(debug_report->debug_output_mutex); |
| ss << "[Internal Error] - Unable to locate shader/pipeline handles used in command buffer " |
| << LookupDebugUtilsNameNoLock(debug_report, HandleToUint64(commandBuffer)) << "(" << HandleToUint64(commandBuffer) |
| << ")\n"; |
| assert(true); |
| } else { |
| std::unique_lock<std::mutex> lock(debug_report->debug_output_mutex); |
| ss << "Command buffer " << LookupDebugUtilsNameNoLock(debug_report, HandleToUint64(commandBuffer)) << "(" |
| << HandleToUint64(commandBuffer) << ")\n"; |
| ss << std::dec << std::noshowbase; |
| ss << '\t'; // helps to show that the index is expressed with respect to the command buffer |
| if (pipeline_bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) { |
| ss << "Draw "; |
| } else if (pipeline_bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) { |
| ss << "Compute Dispatch "; |
| } else if (pipeline_bind_point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR) { |
| ss << "Ray Trace "; |
| } else { |
| assert(false); |
| ss << "Unknown Pipeline Operation "; |
| } |
| |
| uint32_t invalid_index_command = dispatch_instance_->settings.gpuav_settings.invalid_index_command; |
| if (action_command_index == invalid_index_command) { |
| ss << "Index Unknown (After " << invalid_index_command |
| << " commands, we stop tracking) \nThis can be adjusted setting env var VK_LAYER_GPUAV_MAX_INDICES_COUNT to a " |
| "higher value"; |
| } else { |
| ss << "Index " << action_command_index << '\n'; |
| } |
| ss << std::hex << std::noshowbase; |
| |
| const uint32_t unique_shader_id = error_record[glsl::kHeader_ShaderIdErrorOffset] & glsl::kShaderIdMask; |
| if (instrumented_shader->shader_module == VK_NULL_HANDLE) { |
| ss << "Shader Object " << LookupDebugUtilsNameNoLock(debug_report, HandleToUint64(instrumented_shader->shader_object)) |
| << "(0x" << HandleToUint64(instrumented_shader->shader_object) << ") (internal ID " << std::dec << unique_shader_id |
| << ")\n"; |
| } else { |
| if (instrumented_shader->shader_module == kPipelineStageInfoHandle) { |
| ss << "Shader Module was passed in via VkPipelineShaderStageCreateInfo::pNext (internal ID " << std::dec |
| << unique_shader_id << ")\n"; |
| } else { |
| ss << "Shader Module " |
| << LookupDebugUtilsNameNoLock(debug_report, HandleToUint64(instrumented_shader->shader_module)) << "(0x" |
| << HandleToUint64(instrumented_shader->shader_module) << ") (internal ID " << std::dec << unique_shader_id |
| << ")\n"; |
| } |
| } |
| } |
| ss << std::dec << std::noshowbase; |
| |
| const uint32_t instruction_position_offset = error_record[glsl::kHeader_StageInstructionIdOffset] & glsl::kInstructionId_Mask; |
| ::spirv::FindShaderSource(ss, instrumented_shader->original_spirv, instruction_position_offset, |
| gpuav_settings.debug_printf_only); |
| |
| return ss.str(); |
| } |
| |
| } // namespace gpuav |