gpuav: Select pipeline to instrument post creation
diff --git a/layers/CMakeLists.txt b/layers/CMakeLists.txt index 65b92e2..8c00152 100644 --- a/layers/CMakeLists.txt +++ b/layers/CMakeLists.txt
@@ -211,6 +211,7 @@ chassis/chassis_modification_state.h chassis/chassis_manual.cpp chassis/dispatch_object_manual.cpp + chassis/dispatch_object.h containers/range.h containers/range_map.h core_checks/cc_android.cpp
diff --git a/layers/VkLayer_khronos_validation.json.in b/layers/VkLayer_khronos_validation.json.in index de5cebb..4f267c7 100644 --- a/layers/VkLayer_khronos_validation.json.in +++ b/layers/VkLayer_khronos_validation.json.in
@@ -817,7 +817,7 @@ { "key": "gpuav_select_instrumented_shaders", "label": "Enable instrumenting shaders selectively", - "description": "Select which shaders to instrument by passing a VkValidationFeaturesEXT struct with GPU-AV enabled in the VkShaderModuleCreateInfo pNext or using a regex matching a shader/pipeline debug name. Because this only validates the selected shaders, it will allow GPU-AV to run much faster.", + "description": "Select which shaders to instrument by passing a VkValidationFeaturesEXT struct with GPU-AV enabled in the VkShaderModuleCreateInfo pNext or using a regex matching a shader/pipeline debug name. Because this only validates the selected shaders/pipelines, it will allow GPU-AV to run much faster.", "url": "https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/gpu_av_selective_shader.md", "type": "BOOL", "default": false,
diff --git a/layers/chassis/dispatch_object.h b/layers/chassis/dispatch_object.h index f9bba74..26763db 100644 --- a/layers/chassis/dispatch_object.h +++ b/layers/chassis/dispatch_object.h
@@ -231,7 +231,20 @@ } } + // Replaces the "driver handle" in the "wrapped handle" to "driver handle" mapping. + // Returns the old "driver handle" if found. + template <typename HandleType> + HandleType Replace(HandleType wrapped_handle, HandleType new_driver_handle) { + const HandleType old_driver_handle = Find(wrapped_handle); + const uint64_t wrapped_handle_id = CastToUint64(wrapped_handle); + assert(wrapped_handle_id != 0); // can't be 0, otherwise unwrap will apply special rule for VK_NULL_HANDLE + unique_id_mapping.insert_or_assign(wrapped_handle_id, CastToUint64(new_driver_handle)); + return old_driver_handle; + } + void UnwrapPnextChainHandles(const void* pNext); + void UnwrapComputePipelineCreateInfoHandles(vku::safe_VkComputePipelineCreateInfo& safe_ci); + void UnwrapGraphicsPipelineCreateInfoHandles(vku::safe_VkGraphicsPipelineCreateInfo& safe_ci); static std::atomic<uint64_t> global_unique_id; static vvl::concurrent_unordered_map<uint64_t, uint64_t, 4, HashedUint64> unique_id_mapping;
diff --git a/layers/chassis/dispatch_object_manual.cpp b/layers/chassis/dispatch_object_manual.cpp index a038372..432608f 100644 --- a/layers/chassis/dispatch_object_manual.cpp +++ b/layers/chassis/dispatch_object_manual.cpp
@@ -1095,6 +1095,61 @@ } } +void HandleWrapper::UnwrapGraphicsPipelineCreateInfoHandles(vku::safe_VkGraphicsPipelineCreateInfo& safe_ci) { + if (safe_ci.basePipelineHandle) { + safe_ci.basePipelineHandle = Unwrap(safe_ci.basePipelineHandle); + } + if (safe_ci.layout) { + safe_ci.layout = Unwrap(safe_ci.layout); + } + + if (safe_ci.pStages) { + for (uint32_t idx1 = 0; idx1 < safe_ci.stageCount; ++idx1) { + UnwrapMappingInfo(this, safe_ci.pStages[idx1].pNext); + + if (safe_ci.pStages[idx1].module) { + safe_ci.pStages[idx1].module = Unwrap(safe_ci.pStages[idx1].module); + } + } + } + if (safe_ci.renderPass) { + safe_ci.renderPass = Unwrap(safe_ci.renderPass); + } + + auto* link_info = vku::FindStructInPNextChain<VkPipelineLibraryCreateInfoKHR>(safe_ci.pNext); + if (link_info) { + auto* unwrapped_libs = const_cast<VkPipeline*>(link_info->pLibraries); + for (uint32_t idx1 = 0; idx1 < link_info->libraryCount; ++idx1) { + unwrapped_libs[idx1] = Unwrap(link_info->pLibraries[idx1]); + } + } + + auto device_generated_commands = vku::FindStructInPNextChain<VkGraphicsPipelineShaderGroupsCreateInfoNV>(safe_ci.pNext); + if (device_generated_commands) { + for (uint32_t idx1 = 0; idx1 < device_generated_commands->groupCount; ++idx1) { + for (uint32_t idx2 = 0; idx2 < device_generated_commands->pGroups[idx1].stageCount; ++idx2) { + auto unwrapped_stage = + const_cast<VkPipelineShaderStageCreateInfo*>(&device_generated_commands->pGroups[idx1].pStages[idx2]); + if (device_generated_commands->pGroups[idx1].pStages[idx2].module) { + unwrapped_stage->module = Unwrap(device_generated_commands->pGroups[idx1].pStages[idx2].module); + } + } + } + auto unwrapped_pipelines = const_cast<VkPipeline*>(device_generated_commands->pPipelines); + for (uint32_t idx1 = 0; idx1 < device_generated_commands->pipelineCount; ++idx1) { + unwrapped_pipelines[idx1] = Unwrap(device_generated_commands->pPipelines[idx1]); + } + } + + auto* binary_info = vku::FindStructInPNextChain<VkPipelineBinaryInfoKHR>(safe_ci.pNext); + if (binary_info) { + auto* unwrapped_binaries = const_cast<VkPipelineBinaryKHR*>(binary_info->pPipelineBinaries); + for (uint32_t idx1 = 0; idx1 < binary_info->binaryCount; ++idx1) { + unwrapped_binaries[idx1] = Unwrap(binary_info->pPipelineBinaries[idx1]); + } + } +} + VkResult DispatchDevice::CreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkGraphicsPipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) { @@ -1118,7 +1173,6 @@ uses_depthstencil_attachment = true; } } - // We only want to find the case where the user is possibly building non-fragment output libraries bool has_fragment_output_state = true; if (auto lib_info = vku::FindStructInPNextChain<VkGraphicsPipelineLibraryCreateInfoEXT>(pCreateInfos[idx0].pNext)) { @@ -1153,59 +1207,7 @@ original_color_attachment_formats; } - if (pCreateInfos[idx0].basePipelineHandle) { - local_pCreateInfos[idx0].basePipelineHandle = Unwrap(pCreateInfos[idx0].basePipelineHandle); - } - if (pCreateInfos[idx0].layout) { - local_pCreateInfos[idx0].layout = Unwrap(pCreateInfos[idx0].layout); - } - - if (pCreateInfos[idx0].pStages) { - for (uint32_t idx1 = 0; idx1 < pCreateInfos[idx0].stageCount; ++idx1) { - UnwrapMappingInfo(this, local_pCreateInfos[idx0].pStages[idx1].pNext); - - if (pCreateInfos[idx0].pStages[idx1].module) { - local_pCreateInfos[idx0].pStages[idx1].module = Unwrap(pCreateInfos[idx0].pStages[idx1].module); - } - } - } - if (pCreateInfos[idx0].renderPass) { - local_pCreateInfos[idx0].renderPass = Unwrap(pCreateInfos[idx0].renderPass); - } - - auto* link_info = vku::FindStructInPNextChain<VkPipelineLibraryCreateInfoKHR>(local_pCreateInfos[idx0].pNext); - if (link_info) { - auto* unwrapped_libs = const_cast<VkPipeline*>(link_info->pLibraries); - for (uint32_t idx1 = 0; idx1 < link_info->libraryCount; ++idx1) { - unwrapped_libs[idx1] = Unwrap(link_info->pLibraries[idx1]); - } - } - - auto device_generated_commands = - vku::FindStructInPNextChain<VkGraphicsPipelineShaderGroupsCreateInfoNV>(local_pCreateInfos[idx0].pNext); - if (device_generated_commands) { - for (uint32_t idx1 = 0; idx1 < device_generated_commands->groupCount; ++idx1) { - for (uint32_t idx2 = 0; idx2 < device_generated_commands->pGroups[idx1].stageCount; ++idx2) { - auto unwrapped_stage = - const_cast<VkPipelineShaderStageCreateInfo*>(&device_generated_commands->pGroups[idx1].pStages[idx2]); - if (device_generated_commands->pGroups[idx1].pStages[idx2].module) { - unwrapped_stage->module = Unwrap(device_generated_commands->pGroups[idx1].pStages[idx2].module); - } - } - } - auto unwrapped_pipelines = const_cast<VkPipeline*>(device_generated_commands->pPipelines); - for (uint32_t idx1 = 0; idx1 < device_generated_commands->pipelineCount; ++idx1) { - unwrapped_pipelines[idx1] = Unwrap(device_generated_commands->pPipelines[idx1]); - } - } - - auto* binary_info = vku::FindStructInPNextChain<VkPipelineBinaryInfoKHR>(local_pCreateInfos[idx0].pNext); - if (binary_info) { - auto* unwrapped_binaries = const_cast<VkPipelineBinaryKHR*>(binary_info->pPipelineBinaries); - for (uint32_t idx1 = 0; idx1 < binary_info->binaryCount; ++idx1) { - unwrapped_binaries[idx1] = Unwrap(binary_info->pPipelineBinaries[idx1]); - } - } + UnwrapGraphicsPipelineCreateInfoHandles(local_pCreateInfos[idx0]); } } if (pipelineCache) { @@ -2411,6 +2413,20 @@ return device_dispatch_table.WriteResourceDescriptorsEXT(device, resourceCount, local_pResources[0].ptr(), pDescriptors); } +void HandleWrapper::UnwrapComputePipelineCreateInfoHandles(vku::safe_VkComputePipelineCreateInfo& safe_ci) { + UnwrapPnextChainHandles(safe_ci.pNext); + if (safe_ci.stage.module) { + safe_ci.stage.module = Unwrap(safe_ci.stage.module); + } + UnwrapPnextChainHandles(safe_ci.stage.pNext); + if (safe_ci.layout) { + safe_ci.layout = Unwrap(safe_ci.layout); + } + if (safe_ci.basePipelineHandle) { + safe_ci.basePipelineHandle = Unwrap(safe_ci.basePipelineHandle); + } +} + VkResult DispatchDevice::CreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkComputePipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) { @@ -2424,17 +2440,7 @@ local_pCreateInfos = new vku::safe_VkComputePipelineCreateInfo[createInfoCount]; for (uint32_t index0 = 0; index0 < createInfoCount; ++index0) { local_pCreateInfos[index0].initialize(&pCreateInfos[index0]); - UnwrapPnextChainHandles(local_pCreateInfos[index0].pNext); - if (pCreateInfos[index0].stage.module) { - local_pCreateInfos[index0].stage.module = Unwrap(pCreateInfos[index0].stage.module); - } - UnwrapPnextChainHandles(local_pCreateInfos[index0].stage.pNext); - if (pCreateInfos[index0].layout) { - local_pCreateInfos[index0].layout = Unwrap(pCreateInfos[index0].layout); - } - if (pCreateInfos[index0].basePipelineHandle) { - local_pCreateInfos[index0].basePipelineHandle = Unwrap(pCreateInfos[index0].basePipelineHandle); - } + UnwrapComputePipelineCreateInfoHandles(local_pCreateInfos[index0]); } } }
diff --git a/layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp b/layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp index 242a1a6..8077321 100644 --- a/layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp +++ b/layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp
@@ -19,6 +19,7 @@ #include <vulkan/vulkan_core.h> #include <cstdint> +#include "containers/container_utils.h" #include "error_message/error_location.h" #include "generated/vk_extension_helper.h" #include "generated/dispatch_functions.h" @@ -405,6 +406,128 @@ } } +void GpuShaderInstrumentor::PreCallRecordSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo, + const RecordObject& record_obj) { + // Warning: This function is not thread safe WRT pipeline state accesses. + // It is assumed that in most cases, objects are named right after being created, + // and concurrent accesses should not be a worry. + + if (!gpuav_settings.select_instrumented_shaders) { + return; + } + + if (!gpuav_settings.IsSpirvModified()) { + return; + } + if (pNameInfo->objectType != VK_OBJECT_TYPE_PIPELINE || !pNameInfo->pObjectName) { + return; + } + + if (!gpuav_settings.MatchesAnyShaderSelectionRegex(pNameInfo->pObjectName)) { + return; + } + + if (disabled[handle_wrapping]) { + InternalError(LogObjectList(), record_obj.location, + "For GPU-AV selective pipeline instrumentation post creation to work, handle wrapping needs to be enabled."); + return; + } + + VkPipeline wrapped_pipeline = CastFromUint64<VkPipeline>(pNameInfo->objectHandle); + auto pipeline_state = Get<vvl::Pipeline>(wrapped_pipeline); + ASSERT_AND_RETURN(pipeline_state); + + if (pipeline_state->instrumentation_data.was_instrumented) { + return; + } + + if (!NeedPipelineCreationShaderInstrumentation(*pipeline_state, record_obj.location)) { + return; + } + + auto layer_data = vvl::GetDispatchDevice(device); + ASSERT_AND_RETURN(layer_data); + + // The pipeline was selected by name, not by individual shader name, so force all its shaders to be instrumented + for (const auto& stage_state : pipeline_state->stage_states) { + if (stage_state.module_state && stage_state.module_state->VkHandle() != VK_NULL_HANDLE) { + selected_instrumented_shaders.insert(stage_state.module_state->VkHandle()); + } + } + + VkPipeline instrumented_pipeline = VK_NULL_HANDLE; + // Can't instrument ray tracing pipeline post creation, + // As corresponding shader binding tables may have already been created. + if (pipeline_state->linking_shaders == 0 && + IsValueIn(pipeline_state->pipeline_type, {VK_PIPELINE_BIND_POINT_GRAPHICS, VK_PIPELINE_BIND_POINT_COMPUTE})) { + std::vector<chassis::ShaderInstrumentationMetadata> shader_instrumentation_metadata; + if (pipeline_state->pipeline_type == VK_PIPELINE_BIND_POINT_GRAPHICS) { + vku::safe_VkGraphicsPipelineCreateInfo new_pipeline_ci(pipeline_state->GraphicsCreateInfo()); + new_pipeline_ci.flags &= ~VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT; + const bool success = PreCallRecordPipelineCreationShaderInstrumentation( + nullptr, *pipeline_state, new_pipeline_ci, uint32_t(pipeline_state->stage_states.size()), record_obj.location, + shader_instrumentation_metadata); + if (!success) { + InternalError(device, record_obj.location, "Failed to instrument graphics pipeline in SetDebugUtilsObjectNameEXT."); + return; + } + + layer_data->UnwrapGraphicsPipelineCreateInfoHandles(new_pipeline_ci); + const VkResult result = layer_data->device_dispatch_table.CreateGraphicsPipelines( + device, VK_NULL_HANDLE, 1, new_pipeline_ci.ptr(), nullptr, &instrumented_pipeline); + if (result != VK_SUCCESS || instrumented_pipeline == VK_NULL_HANDLE) { + InternalError(device, record_obj.location, + "Failed to create instrumented graphics pipeline in SetDebugUtilsObjectNameEXT."); + return; + } + } else if (pipeline_state->pipeline_type == VK_PIPELINE_BIND_POINT_COMPUTE) { + vku::safe_VkComputePipelineCreateInfo new_pipeline_ci(pipeline_state->ComputeCreateInfo()); + new_pipeline_ci.flags &= ~VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT; + const bool success = PreCallRecordPipelineCreationShaderInstrumentation( + nullptr, *pipeline_state, new_pipeline_ci, uint32_t(pipeline_state->stage_states.size()), record_obj.location, + shader_instrumentation_metadata); + if (!success) { + InternalError(device, record_obj.location, "Failed to instrument compute pipeline in SetDebugUtilsObjectNameEXT."); + return; + } + + layer_data->UnwrapComputePipelineCreateInfoHandles(new_pipeline_ci); + const VkResult result = layer_data->device_dispatch_table.CreateComputePipelines( + device, VK_NULL_HANDLE, 1, new_pipeline_ci.ptr(), nullptr, &instrumented_pipeline); + if (result != VK_SUCCESS || instrumented_pipeline == VK_NULL_HANDLE) { + InternalError(device, record_obj.location, + "Failed to create instrumented compute pipeline in SetDebugUtilsObjectNameEXT."); + return; + } + } + + PostCallRecordPipelineCreationShaderInstrumentation(*pipeline_state, uint32_t(pipeline_state->stage_states.size()), + shader_instrumentation_metadata); + } else { + vku::safe_VkGraphicsPipelineCreateInfo new_pipeline_ci(pipeline_state->GraphicsCreateInfo()); + const bool success = + PreCallRecordPipelineCreationShaderInstrumentationGPL(nullptr, *pipeline_state, new_pipeline_ci, record_obj.location); + if (!success) { + InternalError(device, record_obj.location, + "Failed to instrument graphics pipeline library in SetDebugUtilsObjectNameEXT."); + return; + } + + layer_data->UnwrapGraphicsPipelineCreateInfoHandles(new_pipeline_ci); + const VkResult result = layer_data->device_dispatch_table.CreateGraphicsPipelines( + device, VK_NULL_HANDLE, 1, new_pipeline_ci.ptr(), nullptr, &instrumented_pipeline); + if (result != VK_SUCCESS || instrumented_pipeline == VK_NULL_HANDLE) { + InternalError(device, record_obj.location, + "Failed to create instrumented graphics pipeline in SetDebugUtilsObjectNameEXT."); + return; + } + } + + const VkPipeline old_pipeline = layer_data->Replace(pipeline_state->VkHandle(), instrumented_pipeline); + gpuav::PipelineSubState& pipeline_sub_state = SubState(*pipeline_state); + pipeline_sub_state.AddHandleToDestroy(old_pipeline); +} + void GpuShaderInstrumentor::PostCallRecordCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule, const RecordObject& record_obj, @@ -1117,7 +1240,8 @@ } } -bool GpuShaderInstrumentor::IsPipelineSelectedForInstrumentation(VkPipeline pipeline, const Location& loc) { +bool GpuShaderInstrumentor::IsPipelineSelectedForInstrumentation(const void* pipeline_ci_pnext, VkPipeline pipeline, + const Location& loc) { if (!gpuav_settings.select_instrumented_shaders) { return true; } @@ -1125,12 +1249,17 @@ bool should_instrument_pipeline = false; { std::string pipeline_debug_name; - { + if (auto debug = vku::FindStructInPNextChain<VkDebugUtilsObjectNameInfoEXT>(pipeline_ci_pnext)) { + if (debug->pObjectName) { + pipeline_debug_name = debug->pObjectName; + } + } else if (pipeline != VK_NULL_HANDLE) { 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 (!pipeline_debug_name.empty()) { + 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.", @@ -1237,6 +1366,9 @@ // Can set this once for all shaders in the pipeline BuildDescriptorSetLayoutInfo(pipeline_state, interface.instrumentation_dsl); + bool is_pipeline_selected_for_instrumentation = + IsPipelineSelectedForInstrumentation(modified_pipeline_ci.pNext, VK_NULL_HANDLE, loc); + 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); @@ -1259,8 +1391,9 @@ 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))) { + if (!(is_pipeline_selected_for_instrumentation || + IsShaderSelectedForInstrumentation(modified_shader_module_ci, modified_module_state->VkHandle(), + loc.dot(vvl::Field::pStages, stage_state_i).dot(vvl::Field::module)))) { continue; } } @@ -1416,10 +1549,15 @@ // creation process no matter caching state. new_lib_ci.flags &= ~VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT; bool is_library_instrumented = false; - // If pipeline library is selected for instrumentation, force instrumentation of all its shaders - const bool force_pipeline_instrumentation = - IsPipelineSelectedForInstrumentation(modified_lib->VkHandle(), loc.dot(vvl::Field::pLibraries, modified_lib_i)); + bool is_pipeline_selected_for_instrumentation = IsPipelineSelectedForInstrumentation( + modified_pipeline_ci.pNext, modified_lib->VkHandle(), loc.dot(vvl::Field::pLibraries, modified_lib_i)); + // Case where pipeline is instrumented post creation. Otherwise, pipeline handle has yet to be created! + // currently only useful when this function is called in PreCallRecordSetDebugUtilsObjectNameEXT + if (linked_pipeline_state.VkHandle() != VK_NULL_HANDLE) { + is_pipeline_selected_for_instrumentation |= + IsPipelineSelectedForInstrumentation(modified_pipeline_ci.pNext, linked_pipeline_state.VkHandle(), loc); + } for (uint32_t stage_state_i = 0; stage_state_i < static_cast<uint32_t>(modified_lib->stage_states.size()); ++stage_state_i) { @@ -1457,9 +1595,9 @@ 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 (!force_pipeline_instrumentation && - !IsShaderSelectedForInstrumentation(modified_shader_module_ci, modified_module_state->VkHandle(), - loc.dot(vvl::Field::pStages, stage_state_i).dot(vvl::Field::module))) { + if (!(is_pipeline_selected_for_instrumentation || + IsShaderSelectedForInstrumentation(modified_shader_module_ci, modified_module_state->VkHandle(), + loc.dot(vvl::Field::pStages, stage_state_i).dot(vvl::Field::module)))) { continue; }
diff --git a/layers/gpuav/instrumentation/gpuav_shader_instrumentor.h b/layers/gpuav/instrumentation/gpuav_shader_instrumentor.h index 3949760..5720a76 100644 --- a/layers/gpuav/instrumentation/gpuav_shader_instrumentor.h +++ b/layers/gpuav/instrumentation/gpuav_shader_instrumentor.h
@@ -103,6 +103,9 @@ const VkAllocationCallbacks *pAllocator, VkPipelineLayout *pPipelineLayout, const RecordObject &record_obj, chassis::CreatePipelineLayout &chassis_state) override; + void PreCallRecordSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo, + const RecordObject& record_obj) override; + void PostCallRecordCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkShaderModule *pShaderModule, const RecordObject &record_obj, chassis::CreateShaderModule &chassis_state) override; @@ -246,7 +249,7 @@ bool set_null_descriptors_ = false; private: - bool IsPipelineSelectedForInstrumentation(VkPipeline pipeline, const Location &loc); + bool IsPipelineSelectedForInstrumentation(const void* pipeline_ci_pnext, VkPipeline pipeline, const Location& loc); bool IsShaderSelectedForInstrumentation(vku::safe_VkShaderModuleCreateInfo *modified_shader_module_ci, VkShaderModule modified_shader, const Location &loc); void AddDescriptorHeapMappings(VkBaseOutStructure *create_info);
diff --git a/layers/gpuav/resources/gpuav_state_trackers.cpp b/layers/gpuav/resources/gpuav_state_trackers.cpp index 2742c2e..0a2fe8f 100644 --- a/layers/gpuav/resources/gpuav_state_trackers.cpp +++ b/layers/gpuav/resources/gpuav_state_trackers.cpp
@@ -671,9 +671,9 @@ PipelineSubState::PipelineSubState(Validator& gpuav, vvl::Pipeline& pipeline) : vvl::PipelineSubState(pipeline), gpuav_(gpuav) {} VkPipelineLayout PipelineSubState::GetPipelineLayoutUnion(const Location& loc, vvl::DescriptorMode mode) const { - std::unique_lock<std::mutex> recreated_layout_lock(recreated_layout_mutex); - if (recreated_layout != VK_NULL_HANDLE) { - return recreated_layout; + std::unique_lock<std::mutex> recreated_layout_lock(mutex_); + if (recreated_layout_ != VK_NULL_HANDLE) { + return recreated_layout_; } const std::shared_ptr<const vvl::PipelineLayout> pipeline_layout_state = base.PipelineLayoutState(); @@ -719,7 +719,7 @@ pipeline_layout_ci.pPushConstantRanges = pipeline_layout_state->push_constant_ranges_layout->data(); } - const VkResult result = DispatchCreatePipelineLayout(gpuav_.device, &pipeline_layout_ci, nullptr, &recreated_layout); + const VkResult result = DispatchCreatePipelineLayout(gpuav_.device, &pipeline_layout_ci, nullptr, &recreated_layout_); (void)result; assert(result == VK_SUCCESS); @@ -727,15 +727,27 @@ DispatchDestroyDescriptorSetLayout(gpuav_.device, set_layout_handles[i], nullptr); } - return recreated_layout; + return recreated_layout_; } void PipelineSubState::Destroy() { - std::unique_lock<std::mutex> recreated_layout_lock(recreated_layout_mutex); - if (recreated_layout != VK_NULL_HANDLE) { - DispatchDestroyPipelineLayout(gpuav_.device, recreated_layout, nullptr); - recreated_layout = VK_NULL_HANDLE; + std::unique_lock<std::mutex> recreated_layout_lock(mutex_); + if (recreated_layout_ != VK_NULL_HANDLE) { + DispatchDestroyPipelineLayout(gpuav_.device, recreated_layout_, nullptr); + recreated_layout_ = VK_NULL_HANDLE; } + if (uninstrumented_pipeline != VK_NULL_HANDLE) { + // vkDestroyPipeline expects an unwrapped handle, + // so cannot use DispatchDestroyPipeline as it will try to unwrap supplied pipeline handle + auto layer_data = vvl::GetDispatchDevice(gpuav_.device); + layer_data->device_dispatch_table.DestroyPipeline(gpuav_.device, uninstrumented_pipeline, nullptr); + } +} + +void PipelineSubState::AddHandleToDestroy(VkPipeline pipeline) { + std::unique_lock<std::mutex> lock(mutex_); + assert(uninstrumented_pipeline == VK_NULL_HANDLE); + uninstrumented_pipeline = pipeline; } } // namespace gpuav
diff --git a/layers/gpuav/resources/gpuav_state_trackers.h b/layers/gpuav/resources/gpuav_state_trackers.h index 82d3e49..7bdd158 100644 --- a/layers/gpuav/resources/gpuav_state_trackers.h +++ b/layers/gpuav/resources/gpuav_state_trackers.h
@@ -397,15 +397,19 @@ explicit PipelineSubState(Validator &gpuav, vvl::Pipeline &pipeline); void Destroy() override; - + // Specifically for pipeline instrumented post original creation: + // The old pipeline could be in use at time of instrumentation, + // so defer old pipeline destroy to Destroy() call. + void AddHandleToDestroy(VkPipeline pipeline); VkPipelineLayout GetPipelineLayoutUnion(const Location &loc, vvl::DescriptorMode mode) const; private: // Multiple threads can record multiple commands using the same pipeline, - // so pipeline layout recreation has to be thread safe - mutable std::mutex recreated_layout_mutex{}; - mutable VkPipelineLayout recreated_layout = VK_NULL_HANDLE; + // so layout recreation and deferred pipeline destruction have to be thread safe + mutable std::mutex mutex_{}; + mutable VkPipelineLayout recreated_layout_ = VK_NULL_HANDLE; Validator &gpuav_; + VkPipeline uninstrumented_pipeline = VK_NULL_HANDLE; }; static inline PipelineSubState &SubState(vvl::Pipeline &pipeline) {
diff --git a/layers/layer_options.cpp b/layers/layer_options.cpp index 6644d82..0790149 100644 --- a/layers/layer_options.cpp +++ b/layers/layer_options.cpp
@@ -1360,6 +1360,17 @@ } } + // Only truly needed for pipelines (see GpuShaderInstrumentor::PreCallRecordSetDebugUtilsObjectNameEXT) + // In the future, could be removed in favor of debug names supplied at pipeline creation time. + if (gpuav_settings.select_instrumented_shaders) { + if (settings_data->disabled[handle_wrapping]) { + setting_warnings.emplace_back( + "Handle wrapping has been disabled, but GPU-AV selective shader instrumentation is enabled. Forcing activation of " + "handle wrapping."); + settings_data->disabled[handle_wrapping] = false; + } + } + // After checking the various ways to enable both DebugPrintf and GPU-AV, disable non-DebugPrintf portion if not used if (settings_data->enabled[debug_printf_validation] && !settings_data->enabled[gpu_validation]) { gpuav_settings.SetOnlyDebugPrintf();
diff --git a/layers/vk_layer_settings.txt b/layers/vk_layer_settings.txt index c3870bd..fef56bf 100644 --- a/layers/vk_layer_settings.txt +++ b/layers/vk_layer_settings.txt
@@ -167,7 +167,7 @@ # Enable instrumenting shaders selectively # ===================== -# Select which shaders to instrument by passing a VkValidationFeaturesEXT struct with GPU-AV enabled in the VkShaderModuleCreateInfo pNext or using a regex matching a shader/pipeline debug name. Because this only validates the selected shaders, it will allow GPU-AV to run much faster. +# Select which shaders to instrument by passing a VkValidationFeaturesEXT struct with GPU-AV enabled in the VkShaderModuleCreateInfo pNext or using a regex matching a shader/pipeline debug name. Because this only validates the selected shaders/pipelines, it will allow GPU-AV to run much faster. khronos_validation.gpuav_select_instrumented_shaders = false # Shader instrumentation
diff --git a/tests/unit/gpu_av.cpp b/tests/unit/gpu_av.cpp index f7165c5..095dae3 100644 --- a/tests/unit/gpu_av.cpp +++ b/tests/unit/gpu_av.cpp
@@ -126,7 +126,7 @@ shader_regexes.data()}; RETURN_IF_SKIP(InitGpuAvFramework(layer_settings)); - InitState(); + RETURN_IF_SKIP(InitState()); InitRenderTarget(); vkt::Buffer write_buffer(*m_device, 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, kHostVisibleMemProps); @@ -181,6 +181,227 @@ m_errorMonitor->VerifyFound(); } +TEST_F(NegativeGpuAV, SelectInstrumentedComputePipelineRegex) { + TEST_DESCRIPTION("Selectively instrument a compute pipeline for validation, using regexes"); + SetTargetApiVersion(VK_API_VERSION_1_2); + AddRequiredExtensions(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + std::vector<VkLayerSettingEXT> layer_settings(2); + layer_settings[0] = {OBJECT_LAYER_NAME, "gpuav_select_instrumented_shaders", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &kVkTrue}; + std::array<const char*, 1> shader_regexes = {{"pipeline_foo"}}; + layer_settings[1] = {OBJECT_LAYER_NAME, "gpuav_shaders_to_instrument", VK_LAYER_SETTING_TYPE_STRING_EXT, size32(shader_regexes), + shader_regexes.data()}; + + RETURN_IF_SKIP(InitGpuAvFramework(layer_settings)); + RETURN_IF_SKIP(InitState()); + + vkt::Buffer write_buffer(*m_device, 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, kHostVisibleMemProps); + OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}}); + + const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_}); + descriptor_set.WriteDescriptorBufferInfo(0, write_buffer, 0, 4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + descriptor_set.UpdateDescriptorSets(); + + const char cs_source[] = R"glsl( + #version 450 + layout(set = 0, binding = 0) buffer StorageBuffer { uint data[]; } Data; + void main() { + Data.data[4] = 0xdeadca71; + } + )glsl"; + + CreateComputePipelineHelper pipe(*this); + pipe.cs_ = VkShaderObj(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_GLSL, nullptr, "main"); + pipe.cp_ci_.layout = pipeline_layout; + pipe.CreateComputePipeline(); + + VkDebugUtilsObjectNameInfoEXT name_info = vku::InitStructHelper(); + name_info.objectType = VK_OBJECT_TYPE_PIPELINE; + name_info.pObjectName = "pipeline_foo"; + name_info.objectHandle = uint64_t(pipe.Handle()); + vk::SetDebugUtilsObjectNameEXT(device(), &name_info); + + m_command_buffer.Begin(); + vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &descriptor_set.set_, 0, + nullptr); + vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe); + vk::CmdDispatch(m_command_buffer, 1, 1, 1); + m_command_buffer.End(); + + m_errorMonitor->SetDesiredErrorRegex("VUID-vkCmdDispatch-storageBuffers-06936", "pipeline_foo"); + m_default_queue->SubmitAndWait(m_command_buffer); + m_errorMonitor->VerifyFound(); +} + +TEST_F(NegativeGpuAV, SelectInstrumentedPipelineLibrariesRegex) { + TEST_DESCRIPTION( + "Use one library to link two pipelines. Destroy first one before creating second one. Second one should be instrumented."); + AddRequiredExtensions(VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME); + AddRequiredFeature(vkt::Feature::graphicsPipelineLibrary); + AddRequiredFeature(vkt::Feature::vertexPipelineStoresAndAtomics); + AddRequiredExtensions(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + std::vector<VkLayerSettingEXT> layer_settings(2); + layer_settings[0] = {OBJECT_LAYER_NAME, "gpuav_select_instrumented_shaders", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &kVkTrue}; + std::array<const char*, 1> shader_regexes = {{"pipeline_foo"}}; + layer_settings[1] = {OBJECT_LAYER_NAME, "gpuav_shaders_to_instrument", VK_LAYER_SETTING_TYPE_STRING_EXT, size32(shader_regexes), + shader_regexes.data()}; + + RETURN_IF_SKIP(InitGpuAvFramework(layer_settings)); + RETURN_IF_SKIP(InitState()); + InitRenderTarget(); + + vkt::Buffer offset_buffer(*m_device, 4, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, kHostVisibleMemProps); + vkt::Buffer write_buffer(*m_device, 16, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, kHostVisibleMemProps); + + OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}}); + const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_}); + descriptor_set.WriteDescriptorBufferInfo(0, offset_buffer, 0, VK_WHOLE_SIZE); + descriptor_set.WriteDescriptorBufferInfo(1, write_buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + descriptor_set.UpdateDescriptorSets(); + + uint32_t* data = (uint32_t*)offset_buffer.Memory().Map(); + *data = 8; + + const char vertshader[] = R"glsl( + #version 450 + layout(set = 0, binding = 0) uniform Foo { uint index[]; }; + layout(set = 0, binding = 1) buffer StorageBuffer { uint data[]; }; + void main() { + uint index = index[0]; + data[index] = 0xdeadca71; + } + )glsl"; + + // Create VkShaderModule to pass in + VkShaderObj vs(*m_device, vertshader, VK_SHADER_STAGE_VERTEX_BIT); + VkShaderObj fs(*m_device, kFragmentMinimalGlsl, VK_SHADER_STAGE_FRAGMENT_BIT); + + CreatePipelineHelper vertex_input_lib(*this); + vertex_input_lib.InitVertexInputLibInfo(); + vertex_input_lib.CreateGraphicsPipeline(false); + + // For GPU-AV tests this shrinks things so only a single fragment is executed + VkViewport viewport = {0, 0, 1, 1, 0, 1}; + VkRect2D scissor = {{0, 0}, {1, 1}}; + + CreatePipelineHelper pre_raster_lib(*this); + { + pre_raster_lib.InitPreRasterLibInfo(&vs.GetStageCreateInfo()); + pre_raster_lib.vp_state_ci_.pViewports = &viewport; + pre_raster_lib.vp_state_ci_.pScissors = &scissor; + pre_raster_lib.gp_ci_.layout = pipeline_layout; + pre_raster_lib.CreateGraphicsPipeline(); + } + + CreatePipelineHelper frag_shader_lib(*this); + { + frag_shader_lib.InitFragmentLibInfo(&fs.GetStageCreateInfo()); + frag_shader_lib.gp_ci_.layout = pipeline_layout; + frag_shader_lib.CreateGraphicsPipeline(false); + } + + CreatePipelineHelper frag_out_lib(*this); + frag_out_lib.InitFragmentOutputLibInfo(); + frag_out_lib.CreateGraphicsPipeline(false); + + VkPipeline libraries[4] = { + vertex_input_lib, + pre_raster_lib, + frag_shader_lib, + frag_out_lib, + }; + VkPipelineLibraryCreateInfoKHR link_info = vku::InitStructHelper(); + link_info.libraryCount = size32(libraries); + link_info.pLibraries = libraries; + + VkGraphicsPipelineCreateInfo exe_pipe_ci = vku::InitStructHelper(&link_info); + exe_pipe_ci.layout = pipeline_layout; + { + vkt::Pipeline exe_pipe(*m_device, exe_pipe_ci); + } + vkt::Pipeline exe_pipe_2(*m_device, exe_pipe_ci); + + VkDebugUtilsObjectNameInfoEXT name_info = vku::InitStructHelper(); + name_info.objectType = VK_OBJECT_TYPE_PIPELINE; + name_info.pObjectName = "pipeline_foo"; + name_info.objectHandle = uint64_t(exe_pipe_2.handle()); + vk::SetDebugUtilsObjectNameEXT(device(), &name_info); + + m_command_buffer.Begin(); + m_command_buffer.BeginRenderPass(m_renderPassBeginInfo); + vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, exe_pipe_2); + vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set.set_, 0, + nullptr); + vk::CmdDraw(m_command_buffer, 3, 1, 0, 0); + m_command_buffer.EndRenderPass(); + m_command_buffer.End(); + + m_errorMonitor->SetDesiredErrorRegex("VUID-vkCmdDraw-storageBuffers-06936", "pipeline_foo", 3); + + m_default_queue->SubmitAndWait(m_command_buffer); + m_errorMonitor->VerifyFound(); +} + +TEST_F(NegativeGpuAV, SelectInstrumentedGraphicsPipelineRegex) { + TEST_DESCRIPTION("Selectively instrument a pipeline for validation, using regexes"); + SetTargetApiVersion(VK_API_VERSION_1_2); + AddRequiredExtensions(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + AddRequiredFeature(vkt::Feature::vertexPipelineStoresAndAtomics); + std::vector<VkLayerSettingEXT> layer_settings(2); + layer_settings[0] = {OBJECT_LAYER_NAME, "gpuav_select_instrumented_shaders", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &kVkTrue}; + std::array<const char*, 1> shader_regexes = {{"pipeline_foo"}}; + layer_settings[1] = {OBJECT_LAYER_NAME, "gpuav_shaders_to_instrument", VK_LAYER_SETTING_TYPE_STRING_EXT, size32(shader_regexes), + shader_regexes.data()}; + + RETURN_IF_SKIP(InitGpuAvFramework(layer_settings)); + RETURN_IF_SKIP(InitState()); + InitRenderTarget(); + + vkt::Buffer write_buffer(*m_device, 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, kHostVisibleMemProps); + OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}}); + + const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_}); + descriptor_set.WriteDescriptorBufferInfo(0, write_buffer, 0, 4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + descriptor_set.UpdateDescriptorSets(); + const char vertshader[] = R"glsl( + #version 450 + layout(set = 0, binding = 0) buffer StorageBuffer { uint data[]; } Data; + void main() { + Data.data[4] = 0xdeadca71; + } + )glsl"; + + VkShaderObj vs(*m_device, vertshader, VK_SHADER_STAGE_VERTEX_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_GLSL, nullptr, "main"); + VkShaderObj fs(*m_device, kFragmentMinimalGlsl, VK_SHADER_STAGE_FRAGMENT_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_GLSL, nullptr, + "main"); + + CreatePipelineHelper pipe(*this); + pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()}; + + pipe.gp_ci_.layout = pipeline_layout; + pipe.CreateGraphicsPipeline(); + + VkDebugUtilsObjectNameInfoEXT name_info = vku::InitStructHelper(); + name_info.objectType = VK_OBJECT_TYPE_PIPELINE; + + name_info.pObjectName = "pipeline_foo"; + name_info.objectHandle = uint64_t(pipe.Handle()); + vk::SetDebugUtilsObjectNameEXT(device(), &name_info); + + m_command_buffer.Begin(); + vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + m_command_buffer.BeginRenderPass(m_renderPassBeginInfo); + vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set.set_, 0, + nullptr); + vk::CmdDraw(m_command_buffer, 3, 1, 0, 0); + m_command_buffer.EndRenderPass(); + m_command_buffer.End(); + + m_errorMonitor->SetDesiredErrorRegex("VUID-vkCmdDraw-storageBuffers-06936", "pipeline_foo", 3); + m_default_queue->SubmitAndWait(m_command_buffer); + m_errorMonitor->VerifyFound(); +} + TEST_F(NegativeGpuAV, SelectInstrumentedShadersRegexDestroyedShaders) { TEST_DESCRIPTION( "Selectively instrument shaders for validation, using regexes: all shaders matching regexes must be instrumented. Here it " @@ -195,7 +416,7 @@ shader_regexes.data()}; RETURN_IF_SKIP(InitGpuAvFramework(layer_settings)); - InitState(); + RETURN_IF_SKIP(InitState()); InitRenderTarget(); vkt::Buffer write_buffer(*m_device, 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, kHostVisibleMemProps);