blob: fa81712509a667b3cb5e46b35a6f989f7837d3d6 [file]
/* 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_instrumentation.h"
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <spirv/unified1/spirv.hpp>
#include <vulkan/utility/vk_struct_helper.hpp>
#include "chassis/chassis_modification_state.h"
#include "containers/small_vector.h"
#include "gpuav/core/gpuav.h"
#include "gpuav/core/gpuav_constants.h"
#include "gpuav/resources/gpuav_vulkan_objects.h"
#include "gpuav/shaders/gpuav_shaders_constants.h"
#include "gpuav/resources/gpuav_state_trackers.h"
#include "gpuav/shaders/gpuav_error_header.h"
#include "gpuav/debug_printf/debug_printf.h"
#include "containers/limits.h"
#include "state_tracker/cmd_buffer_state.h"
#include "state_tracker/descriptor_sets.h"
#include "state_tracker/last_bound_state.h"
#include "state_tracker/shader_object_state.h"
#include "state_tracker/pipeline_state.h"
#include "state_tracker/pipeline_layout_state.h"
#include "state_tracker/descriptor_mode.h"
namespace gpuav {
enum class PipelineLayoutSource { NoPipelineLayout, LastBoundPipeline, LastBoundDescriptorSet, LastPushedConstants };
struct InstBindingPipeLayout {
std::shared_ptr<const vvl::PipelineLayout> state;
VkPipelineLayout handle = VK_NULL_HANDLE;
PipelineLayoutSource source = PipelineLayoutSource::NoPipelineLayout;
};
struct CommonInstrumentationErrorInfo {
uint32_t action_command_index = 0;
// Used to know if from draw, dispatch, or traceRays
VkPipelineBindPoint pipeline_bind_point = VK_PIPELINE_BIND_POINT_MAX_ENUM;
};
// If application is using shader objects, bindings count will be computed from bound shaders
static uint32_t LastBoundPipelineOrShaderDescSetBindingsCount(const LastBound& last_bound) {
// App uses pipeline or graphics pipeline libraries
if (last_bound.pipeline_state && last_bound.pipeline_state->PipelineLayoutState()) {
return uint32_t(last_bound.pipeline_state->PipelineLayoutState()->set_layouts.list.size());
}
// App uses shader objects
if (const vvl::ShaderObject* main_bound_shader = last_bound.GetFirstShader()) {
return static_cast<uint32_t>(main_bound_shader->set_layouts.list.size());
}
// Should not get there, it would mean no pipeline nor shader object was bound
assert(false);
return 0;
}
// If application is using shader objects, bindings count will be computed from bound shaders
static uint32_t LastBoundPipelineOrShaderPushConstantsRangesCount(const LastBound& last_bound) {
if (last_bound.pipeline_state && last_bound.pipeline_state->PreRasterPipelineLayoutState()) {
return static_cast<uint32_t>(
last_bound.pipeline_state->PreRasterPipelineLayoutState()->push_constant_ranges_layout->size());
}
if (const vvl::ShaderObject* main_bound_shader = last_bound.GetFirstShader()) {
return static_cast<uint32_t>(main_bound_shader->push_constant_ranges->size());
}
// Should not get there, it would mean no pipeline nor shader object was bound
assert(false);
return 0;
}
static VkPipelineLayout CreateInstrumentationPipelineLayout(Validator& gpuav, const Location& loc, const LastBound& last_bound,
VkDescriptorSetLayout dummy_desc_set_layout,
VkDescriptorSetLayout instrumentation_desc_set_layout,
uint32_t inst_desc_set_binding) {
// If not using shader objects, GPU-AV should be able to retrieve a pipeline layout from last bound pipeline
VkPipelineLayoutCreateInfo pipe_layout_ci = vku::InitStructHelper();
std::shared_ptr<const vvl::PipelineLayout> last_bound_pipeline_pipe_layout;
if (last_bound.pipeline_state && last_bound.pipeline_state->PreRasterPipelineLayoutState()) {
last_bound_pipeline_pipe_layout = last_bound.pipeline_state->PreRasterPipelineLayoutState();
}
if (last_bound_pipeline_pipe_layout) {
// Application is using classic pipelines, compose a pipeline layout from last bound pipeline
// ---
pipe_layout_ci.flags = last_bound_pipeline_pipe_layout->create_flags;
std::vector<VkPushConstantRange> ranges;
if (last_bound_pipeline_pipe_layout->push_constant_ranges_layout) {
ranges.reserve(last_bound_pipeline_pipe_layout->push_constant_ranges_layout->size());
for (const VkPushConstantRange& range : *last_bound_pipeline_pipe_layout->push_constant_ranges_layout) {
ranges.push_back(range);
}
}
pipe_layout_ci.pushConstantRangeCount = static_cast<uint32_t>(ranges.size());
pipe_layout_ci.pPushConstantRanges = ranges.data();
std::vector<VkDescriptorSetLayout> set_layouts;
set_layouts.reserve(inst_desc_set_binding + 1);
for (const auto& set_layout : last_bound_pipeline_pipe_layout->set_layouts.list) {
set_layouts.push_back(set_layout->VkHandle());
}
for (uint32_t set_i = static_cast<uint32_t>(last_bound_pipeline_pipe_layout->set_layouts.list.size());
set_i < inst_desc_set_binding; ++set_i) {
set_layouts.push_back(dummy_desc_set_layout);
}
set_layouts.push_back(instrumentation_desc_set_layout);
pipe_layout_ci.setLayoutCount = static_cast<uint32_t>(set_layouts.size());
pipe_layout_ci.pSetLayouts = set_layouts.data();
VkPipelineLayout pipe_layout_handle;
VkResult result = DispatchCreatePipelineLayout(gpuav.device, &pipe_layout_ci, VK_NULL_HANDLE, &pipe_layout_handle);
if (result != VK_SUCCESS) {
gpuav.InternalError(gpuav.device, loc, "Failed to create instrumentation pipeline layout");
return VK_NULL_HANDLE;
}
return pipe_layout_handle;
} else {
// Application is using shader objects, compose a pipeline layout from bound shaders
// ---
const vvl::ShaderObject* main_bound_shader = last_bound.GetFirstShader();
if (!main_bound_shader) {
// Should not get there, it would mean no pipeline nor shader object was bound
gpuav.InternalError(gpuav.device, loc, "Could not retrieve last bound computer/vertex/mesh shader");
return VK_NULL_HANDLE;
}
// From those VUIDs:
// VUID-vkCmdDraw-None-08878
// - All bound graphics shader objects must have been created with identical or identically defined push constant ranges
// VUID-vkCmdDraw-None-08879
// - All bound graphics shader objects must have been created with identical or identically defined arrays of descriptor set
// layouts
// => To compose a VkPipelineLayout, only need to get compute or vertex/mesh shader and look at their bindings,
// no need to check other shaders.
const vvl::DescriptorSetLayoutList& set_layouts = main_bound_shader->set_layouts;
PushConstantRangesId push_constants_layouts = main_bound_shader->push_constant_ranges;
if (last_bound.desc_set_pipeline_layout) {
pipe_layout_ci.flags = last_bound.desc_set_pipeline_layout->CreateFlags();
}
std::vector<VkDescriptorSetLayout> set_layout_handles;
{
set_layout_handles.reserve(inst_desc_set_binding + 1);
for (const auto& set_layout : set_layouts.list) {
set_layout_handles.push_back(set_layout->VkHandle());
}
for (uint32_t set_i = static_cast<uint32_t>(set_layouts.list.size()); set_i < inst_desc_set_binding; ++set_i) {
set_layout_handles.push_back(dummy_desc_set_layout);
}
set_layout_handles.push_back(instrumentation_desc_set_layout);
pipe_layout_ci.setLayoutCount = static_cast<uint32_t>(set_layout_handles.size());
pipe_layout_ci.pSetLayouts = set_layout_handles.data();
}
if (push_constants_layouts) {
pipe_layout_ci.pushConstantRangeCount = static_cast<uint32_t>(push_constants_layouts->size());
pipe_layout_ci.pPushConstantRanges = push_constants_layouts->data();
}
VkPipelineLayout pipe_layout_handle;
VkResult result = DispatchCreatePipelineLayout(gpuav.device, &pipe_layout_ci, VK_NULL_HANDLE, &pipe_layout_handle);
if (result != VK_SUCCESS) {
gpuav.InternalError(gpuav.device, loc, "Failed to create instrumentation pipeline layout");
return VK_NULL_HANDLE;
}
return pipe_layout_handle;
}
}
void UpdateInstrumentationDescBuffer(Validator& gpuav, CommandBufferSubState& cb_state, const LastBound& last_bound,
uint8_t* gloabl_descriptor_buffer_ptr, uint32_t action_command_index_offset,
uint32_t resource_index_offset, const Location& loc) {
uint8_t* descriptor_start = gloabl_descriptor_buffer_ptr;
VkDescriptorGetInfoEXT get_info = vku::InitStructHelper();
get_info.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
VkDescriptorAddressInfoEXT address_info = vku::InitStructHelper();
get_info.data.pStorageBuffer = &address_info;
VkDeviceSize binding_offset = 0;
if (gpuav.gpuav_settings.IsShaderInstrumentationEnabled()) {
address_info.address = cb_state.GetErrorOutputBufferRange().offset_address;
address_info.range = cb_state.GetErrorOutputBufferRange().size;
binding_offset = gpuav.resource_descriptor_buffer_offsets_[glsl::kBindingInstErrorBuffer];
uint8_t* descriptor_offset = descriptor_start + binding_offset;
DispatchGetDescriptorEXT(gpuav.device, &get_info,
gpuav.phys_dev_ext_props.descriptor_buffer_props.storageBufferDescriptorSize, descriptor_offset);
address_info.address = gpuav.global_indices_buffer_.Address() + action_command_index_offset;
address_info.range = 4;
binding_offset = gpuav.resource_descriptor_buffer_offsets_[glsl::kBindingInstActionIndex];
descriptor_offset = descriptor_start + binding_offset;
DispatchGetDescriptorEXT(gpuav.device, &get_info,
gpuav.phys_dev_ext_props.descriptor_buffer_props.storageBufferDescriptorSize, descriptor_offset);
address_info.address = gpuav.global_indices_buffer_.Address() + resource_index_offset;
address_info.range = 4;
binding_offset = gpuav.resource_descriptor_buffer_offsets_[glsl::kBindingInstCmdResourceIndex];
descriptor_offset = descriptor_start + binding_offset;
DispatchGetDescriptorEXT(gpuav.device, &get_info,
gpuav.phys_dev_ext_props.descriptor_buffer_props.storageBufferDescriptorSize, descriptor_offset);
address_info.address = cb_state.cmd_errors_counts_buffer_.Address();
address_info.range = cb_state.cmd_errors_counts_buffer_.Size();
binding_offset = gpuav.resource_descriptor_buffer_offsets_[glsl::kBindingInstCmdErrorsCount];
descriptor_offset = descriptor_start + binding_offset;
DispatchGetDescriptorEXT(gpuav.device, &get_info,
gpuav.phys_dev_ext_props.descriptor_buffer_props.storageBufferDescriptorSize, descriptor_offset);
}
for (const auto& func : vvl::make_span(cb_state.on_instrumentation_common_desc_update_functions)) {
CommonDescriptorUpdate common_update;
func(cb_state, last_bound, loc, common_update);
if (common_update.address == 0) {
continue; // case like VertexAttributeFetchOff returning without a binding
}
address_info.address = common_update.address;
address_info.range = common_update.range;
binding_offset = gpuav.resource_descriptor_buffer_offsets_[common_update.binding];
uint8_t* descriptor_offset = descriptor_start + binding_offset;
DispatchGetDescriptorEXT(gpuav.device, &get_info,
gpuav.phys_dev_ext_props.descriptor_buffer_props.storageBufferDescriptorSize, descriptor_offset);
}
}
void UpdateInstrumentationDescHeap(Validator& gpuav, CommandBufferSubState& cb_state, const LastBound& last_bound,
VkDeviceAddress* indirect_memory, uint32_t action_command_index_offset,
uint32_t resource_index_offset, const Location& loc) {
if (gpuav.gpuav_settings.IsShaderInstrumentationEnabled()) {
// Error output buffer
indirect_memory[glsl::kBindingInstErrorBuffer] = cb_state.GetErrorOutputBufferRange().offset_address;
// Buffer holding action command index in command buffer
indirect_memory[glsl::kBindingInstActionIndex] = gpuav.global_indices_buffer_.Address() + action_command_index_offset;
// Buffer holding a resource index from the per command buffer command resources list
indirect_memory[glsl::kBindingInstCmdResourceIndex] = gpuav.global_indices_buffer_.Address() + resource_index_offset;
// Errors count buffer
indirect_memory[glsl::kBindingInstCmdErrorsCount] = cb_state.cmd_errors_counts_buffer_.Address();
}
for (const auto& func : vvl::make_span(cb_state.on_instrumentation_common_desc_update_functions)) {
CommonDescriptorUpdate common_update;
func(cb_state, last_bound, loc, common_update);
if (common_update.address == 0) {
continue; // case like VertexAttributeFetchOff returning without a binding
}
indirect_memory[common_update.binding] = common_update.address;
}
}
void UpdateInstrumentationDescSet(Validator& gpuav, CommandBufferSubState& cb_state, const LastBound& last_bound,
VkDescriptorSet instrumentation_desc_set, const Location& loc) {
small_vector<VkWriteDescriptorSet, 8> desc_writes = {};
VkDescriptorBufferInfo error_output_desc_buffer_info = {};
VkDescriptorBufferInfo indices_desc_buffer_info = {};
VkDescriptorBufferInfo cmd_errors_counts_desc_buffer_info = {};
if (gpuav.gpuav_settings.IsShaderInstrumentationEnabled()) {
// Error output buffer
{
error_output_desc_buffer_info.buffer = cb_state.GetErrorOutputBufferRange().buffer;
error_output_desc_buffer_info.offset = cb_state.GetErrorOutputBufferRange().offset;
error_output_desc_buffer_info.range = cb_state.GetErrorOutputBufferRange().size;
VkWriteDescriptorSet wds = vku::InitStructHelper();
wds.dstBinding = glsl::kBindingInstErrorBuffer;
wds.descriptorCount = 1;
wds.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
wds.pBufferInfo = &error_output_desc_buffer_info;
wds.dstSet = instrumentation_desc_set;
desc_writes.emplace_back(wds);
}
// Buffer holding action command index in command buffer
{
indices_desc_buffer_info.range = sizeof(uint32_t);
indices_desc_buffer_info.buffer = gpuav.global_indices_buffer_.VkHandle();
indices_desc_buffer_info.offset = 0;
VkWriteDescriptorSet wds = vku::InitStructHelper();
wds.dstBinding = glsl::kBindingInstActionIndex;
wds.descriptorCount = 1;
wds.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
wds.pBufferInfo = &indices_desc_buffer_info;
wds.dstSet = instrumentation_desc_set;
desc_writes.emplace_back(wds);
}
// Buffer holding a resource index from the per command buffer command resources list
{
VkWriteDescriptorSet wds = vku::InitStructHelper();
wds.dstBinding = glsl::kBindingInstCmdResourceIndex;
wds.descriptorCount = 1;
wds.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
wds.pBufferInfo = &indices_desc_buffer_info;
wds.dstSet = instrumentation_desc_set;
desc_writes.emplace_back(wds);
}
// Errors count buffer
{
cmd_errors_counts_desc_buffer_info.range = VK_WHOLE_SIZE;
cmd_errors_counts_desc_buffer_info.buffer = cb_state.GetCmdErrorsCountsBuffer();
cmd_errors_counts_desc_buffer_info.offset = 0;
VkWriteDescriptorSet wds = vku::InitStructHelper();
wds.dstBinding = glsl::kBindingInstCmdErrorsCount;
wds.descriptorCount = 1;
wds.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
wds.pBufferInfo = &cmd_errors_counts_desc_buffer_info;
wds.dstSet = instrumentation_desc_set;
desc_writes.emplace_back(wds);
}
}
std::vector<VkDescriptorBufferInfo> buffer_infos(cb_state.on_instrumentation_common_desc_update_functions.size());
for (const auto [func_i, func] : vvl::enumerate(cb_state.on_instrumentation_common_desc_update_functions)) {
CommonDescriptorUpdate common_update;
func(cb_state, last_bound, loc, common_update);
if (common_update.buffer == VK_NULL_HANDLE) {
continue; // case like VertexAttributeFetchOff returning without a binding
}
VkWriteDescriptorSet wds = vku::InitStructHelper();
wds.dstSet = instrumentation_desc_set;
wds.dstBinding = common_update.binding;
wds.descriptorCount = 1;
wds.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
buffer_infos[func_i] = {common_update.buffer, common_update.offset, common_update.range};
wds.pBufferInfo = &buffer_infos[func_i];
desc_writes.emplace_back(wds);
}
DispatchUpdateDescriptorSets(gpuav.device, static_cast<uint32_t>(desc_writes.size()), desc_writes.data(), 0, nullptr);
}
static bool WasInstrumented(const LastBound& last_bound) {
if (last_bound.pipeline_state) {
const PipelineSubState& pipeline_sub_state = SubState(*last_bound.pipeline_state);
return pipeline_sub_state.status.host.is_instrumented;
}
for (uint32_t i = 0; i < kShaderObjectStageCount; ++i) {
const auto stage = static_cast<ShaderObjectStage>(i);
if (!last_bound.IsValidShaderObjectBound(stage)) {
continue;
}
if (const vvl::ShaderObject* shader_object_state = last_bound.GetShaderObjectState(stage)) {
auto& sub_state = SubState(*shader_object_state);
if (sub_state.instrumented_status.host.is_instrumented) {
return true;
}
}
}
return false;
}
// Pull together all the information from the debug record to build the error message strings,
// and then assemble them into a single message string.
// Retrieve the shader program referenced by the unique shader ID provided in the debug record.
// We had to keep a copy of the shader program with the same lifecycle as the pipeline to make
// sure it is available when the pipeline is submitted. (The ShaderModule tracking object also
// keeps a copy, but it can be destroyed after the pipeline is created and before it is submitted.)
//
bool LogInstrumentationError(Validator& gpuav, const VkCommandBuffer cb_handle, const LogObjectList& objlist,
const CommonInstrumentationErrorInfo& error_info, const uint32_t* error_record,
const Location& loc_with_debug_region,
const std::vector<CommandBufferSubState::InstrumentationErrorLogger>& error_loggers) {
// The second word in the debug output buffer is the number of words that would have
// been written by the shader instrumentation, if there was enough room in the buffer we provided.
// The number of words actually written by the shaders is determined by the size of the buffer
// we provide via the descriptor. So, we process only the number of words that can fit in the
// buffer.
// Each "report" written by the shader instrumentation is considered a "record". This function
// is hard-coded to process only one record because it expects the buffer to be large enough to
// hold only one record. If there is a desire to process more than one record, this function needs
// to be modified to loop over records and the buffer size increased.
std::string error_msg;
std::string vuid_msg;
bool error_found = false;
// Lookup the VkShaderModule handle and SPIR-V code used to create the shader, using the unique shader ID value returned
// by the instrumented shader.
const InstrumentedShader* instrumented_shader = nullptr;
const uint32_t unique_shader_id = error_record[glsl::kHeader_ShaderIdErrorOffset] & glsl::kShaderIdMask;
auto it = gpuav.instrumented_shaders_map_.find(unique_shader_id);
if (it != gpuav.instrumented_shaders_map_.end()) {
instrumented_shader = &it->second;
}
for (const CommandBufferSubState::InstrumentationErrorLogger& error_logger : error_loggers) {
error_found = error_logger(gpuav, loc_with_debug_region, error_record, instrumented_shader, error_msg, vuid_msg);
if (error_found) {
break;
}
}
// We should find an error, otherwise this means we are not registering a check somewhere
assert(error_found);
if (error_found) {
std::string debug_info_message = gpuav.GenerateDebugInfoMessage(
cb_handle, error_record, instrumented_shader, error_info.pipeline_bind_point, error_info.action_command_index);
gpuav.LogError(vuid_msg.c_str(), objlist, loc_with_debug_region, "%s\n%s", error_msg.c_str(), debug_info_message.c_str());
}
return error_found;
}
void PreCallSetupShaderInstrumentationResourcesClassic(Validator& gpuav, CommandBufferSubState& cb_state,
const LastBound& last_bound,
const CommonInstrumentationErrorInfo& common_error_info,
const InstBindingPipeLayout& inst_binding_pipe_layout, const Location& loc) {
const VkCommandBuffer cb_handle = cb_state.VkHandle();
VkDescriptorSet instrumentation_desc_set =
cb_state.gpu_resources_manager.GetManagedDescriptorSet(cb_state.GetInstrumentationDescriptorSetLayout());
if (!instrumentation_desc_set) {
gpuav.InternalError(cb_handle, loc, "Unable to allocate instrumentation descriptor sets.");
return;
}
UpdateInstrumentationDescSet(gpuav, cb_state, last_bound, instrumentation_desc_set, loc);
// Bind instrumentation descriptor set, using an appropriate pipeline layout
// ---
const uint32_t error_logger_index = cb_state.GetErrorLoggerIndex();
assert(error_logger_index < gpuav.gpuav_settings.indices_buffer_count);
assert(common_error_info.action_command_index < gpuav.gpuav_settings.indices_buffer_count);
const std::array<uint32_t, 2> dynamic_offsets = {{common_error_info.action_command_index * gpuav.indices_buffer_alignment_,
error_logger_index * gpuav.indices_buffer_alignment_}};
if (inst_binding_pipe_layout.handle != VK_NULL_HANDLE) {
if (inst_binding_pipe_layout.state &&
(uint32_t)inst_binding_pipe_layout.state->set_layouts.list.size() > gpuav.instrumentation_desc_set_bind_index_) {
gpuav.InternalWarning(cb_handle, loc,
"Unable to bind instrumentation descriptor set, it would override application's bound set");
return;
}
switch (inst_binding_pipe_layout.source) {
case PipelineLayoutSource::NoPipelineLayout:
// should not get there, because inst_desc_set_binding_pipe_layout_state is not null
assert(false);
break;
case PipelineLayoutSource::LastBoundPipeline:
DispatchCmdBindDescriptorSets(cb_handle, last_bound.bind_point, inst_binding_pipe_layout.handle,
gpuav.instrumentation_desc_set_bind_index_, 1, &instrumentation_desc_set,
static_cast<uint32_t>(dynamic_offsets.size()), dynamic_offsets.data());
break;
case PipelineLayoutSource::LastBoundDescriptorSet:
case PipelineLayoutSource::LastPushedConstants: {
// Currently bound pipeline/set of shader objects may have bindings that are not compatible with last
// bound descriptor sets: GPU-AV may create this incompatibility by adding its empty padding descriptor sets.
// To alleviate that, since we could not get a pipeline layout from last pipeline binding (it was either
// destroyed, or never has been created if using shader objects), a pipeline layout matching bindings of last
// bound pipeline or
// last bound shader objects is created and used.
// If will also be cached: heuristic is next action command will likely need the same.
const uint32_t last_pipe_bindings_count = LastBoundPipelineOrShaderDescSetBindingsCount(last_bound);
const uint32_t last_pipe_pcr_count = LastBoundPipelineOrShaderPushConstantsRangesCount(last_bound);
// If the number of binding of the currently bound pipeline's layout (or the equivalent for shader objects) is
// less that the number of bindings in the pipeline layout used to bind descriptor sets,
// GPU-AV needs to create a temporary pipeline layout matching the the currently bound pipeline's layout
// to bind the instrumentation descriptor set
if (last_pipe_bindings_count < (uint32_t)inst_binding_pipe_layout.state->set_layouts.list.size() ||
last_pipe_pcr_count < (uint32_t)inst_binding_pipe_layout.state->push_constant_ranges_layout->size()) {
VkPipelineLayout instrumentation_pipe_layout = CreateInstrumentationPipelineLayout(
gpuav, loc, last_bound, gpuav.dummy_desc_layout_[vvl::DescriptorModeClassic],
gpuav.GetInstrumentationDescriptorSetLayout(vvl::DescriptorModeClassic),
gpuav.instrumentation_desc_set_bind_index_);
if (instrumentation_pipe_layout != VK_NULL_HANDLE) {
DispatchCmdBindDescriptorSets(cb_handle, last_bound.bind_point, instrumentation_pipe_layout,
gpuav.instrumentation_desc_set_bind_index_, 1, &instrumentation_desc_set,
static_cast<uint32_t>(dynamic_offsets.size()), dynamic_offsets.data());
DispatchDestroyPipelineLayout(gpuav.device, instrumentation_pipe_layout, nullptr);
} else {
// Could not create instrumentation pipeline layout
return;
}
} else {
// No incompatibility detected, safe to use pipeline layout for last bound descriptor set/push constants.
DispatchCmdBindDescriptorSets(cb_handle, last_bound.bind_point, inst_binding_pipe_layout.state->VkHandle(),
gpuav.instrumentation_desc_set_bind_index_, 1, &instrumentation_desc_set,
static_cast<uint32_t>(dynamic_offsets.size()), dynamic_offsets.data());
}
} break;
}
} else {
// If no pipeline layout was bound when using shader objects that don't use any descriptor set, and no push constants, bind
// the instrumentation pipeline layout
DispatchCmdBindDescriptorSets(cb_handle, last_bound.bind_point,
gpuav.GetInstrumentationPipelineLayout(vvl::DescriptorModeClassic),
gpuav.instrumentation_desc_set_bind_index_, 1, &instrumentation_desc_set,
static_cast<uint32_t>(dynamic_offsets.size()), dynamic_offsets.data());
}
}
void PreCallSetupShaderInstrumentationResourcesDescriptorHeap(Validator& gpuav, CommandBufferSubState& cb_state,
const LastBound& last_bound,
const CommonInstrumentationErrorInfo& common_error_info,
const Location& loc) {
const VkDeviceSize indirect_buffer_offset = gpuav.heap_indirect_buffer_stride_ * common_error_info.action_command_index;
const auto& indirect_buffer = gpuav.GetGlobalDescriptorHeap();
uint8_t* indirect_buffer_ptr = static_cast<uint8_t*>(indirect_buffer.GetMappedPtr());
indirect_buffer_ptr += indirect_buffer_offset;
const uint32_t error_logger_index = cb_state.GetErrorLoggerIndex();
assert(error_logger_index < gpuav.gpuav_settings.indices_buffer_count);
assert(common_error_info.action_command_index < gpuav.gpuav_settings.indices_buffer_count);
const uint32_t action_command_index_offset = common_error_info.action_command_index * gpuav.indices_buffer_alignment_;
const uint32_t resource_index_offset = error_logger_index * gpuav.indices_buffer_alignment_;
UpdateInstrumentationDescHeap(gpuav, cb_state, last_bound, (VkDeviceAddress*)indirect_buffer_ptr, action_command_index_offset,
resource_index_offset, loc);
VkDeviceAddress gpuav_data_address = indirect_buffer.Address() + indirect_buffer_offset;
VkPushDataInfoEXT push_data_info = vku::InitStructHelper();
push_data_info.offset = gpuav.push_data_offset_;
push_data_info.data.address = &gpuav_data_address;
push_data_info.data.size = sizeof(VkDeviceAddress);
DispatchCmdPushDataEXT(cb_state.VkHandle(), &push_data_info);
}
void PreCallSetupShaderInstrumentationResourcesDescriptorBuffer(Validator& gpuav, CommandBufferSubState& cb_state,
const LastBound& last_bound,
const CommonInstrumentationErrorInfo& common_error_info,
const InstBindingPipeLayout& inst_binding_pipe_layout,
const Location& loc) {
VkPipelineLayout bind_pipeline_layout_handle = inst_binding_pipe_layout.handle;
if (inst_binding_pipe_layout.handle == VK_NULL_HANDLE) {
// This can only happen if there are no pipeline layout bound when using shader objects, with no descriptor set nor push
// constants and the user called vkCmdBindDescriptorBuffersEXT but never vkCmdSetDescriptorBufferOffsetsEXT
bind_pipeline_layout_handle = gpuav.GetInstrumentationPipelineLayout(vvl::DescriptorModeBuffer);
} else if (inst_binding_pipe_layout.state &&
(uint32_t)inst_binding_pipe_layout.state->set_layouts.list.size() > gpuav.instrumentation_desc_set_bind_index_) {
gpuav.InternalWarning(cb_state.Handle(), loc,
"Unable to bind instrumentation descriptor set, it would override application's bound set");
return;
}
const auto& gloabl_descriptor_buffer = gpuav.GetGlobalDescriptorBuffer();
// There likely is only Push Constants (and BDA) used, but because the VkDescriptorSetLayout is using VK_EXT_descriptor_buffer,
// and GPU-AV requires using a previous vkCmdBindDescriptorBuffersEXT to inject our code, so we have to inject it ourselves
if (cb_state.base.descriptor_buffer.binding_info.empty()) {
VkDescriptorBufferBindingInfoEXT binding_info = vku::InitStructHelper();
binding_info.usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT;
binding_info.address = gloabl_descriptor_buffer.Address();
DispatchCmdBindDescriptorBuffersEXT(cb_state.VkHandle(), 1, &binding_info);
cb_state.resource_descriptor_buffer_index_ = 0;
}
const VkDeviceSize gloabl_descriptor_buffer_offset =
gpuav.resource_descriptor_buffer_stride_ * common_error_info.action_command_index;
uint8_t* gloabl_descriptor_buffer_ptr = static_cast<uint8_t*>(gloabl_descriptor_buffer.GetMappedPtr());
gloabl_descriptor_buffer_ptr += gloabl_descriptor_buffer_offset;
DispatchCmdSetDescriptorBufferOffsetsEXT(cb_state.VkHandle(), last_bound.bind_point, bind_pipeline_layout_handle,
gpuav.instrumentation_desc_set_bind_index_, 1,
&cb_state.resource_descriptor_buffer_index_, &gloabl_descriptor_buffer_offset);
const uint32_t error_logger_index = cb_state.GetErrorLoggerIndex();
assert(error_logger_index < gpuav.gpuav_settings.indices_buffer_count);
assert(common_error_info.action_command_index < gpuav.gpuav_settings.indices_buffer_count);
const uint32_t action_command_index_offset = common_error_info.action_command_index * gpuav.indices_buffer_alignment_;
const uint32_t resource_index_offset = error_logger_index * gpuav.indices_buffer_alignment_;
UpdateInstrumentationDescBuffer(gpuav, cb_state, last_bound, gloabl_descriptor_buffer_ptr, action_command_index_offset,
resource_index_offset, loc);
}
void PreCallSetupShaderInstrumentationResources(Validator& gpuav, CommandBufferSubState& cb_state, const LastBound& last_bound,
const Location& loc) {
if (!gpuav.gpuav_settings.IsSpirvModified()) {
return;
}
// If nothing was updated, we don't want to bind anything
if (!WasInstrumented(last_bound)) {
return;
}
if (!last_bound.pipeline_state && !last_bound.HasShaderObjects()) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Neither pipeline state nor shader object states were found.");
return;
}
InstBindingPipeLayout inst_binding_pipe_layout;
// App uses regular pipelines or graphics pipeline libraries
const vvl::DescriptorMode mode = last_bound.GetActionDescriptorMode();
if (mode != vvl::DescriptorMode::DescriptorModeHeap) {
if (last_bound.pipeline_state) {
const PipelineSubState& pipeline_sub_state = SubState(*last_bound.pipeline_state);
inst_binding_pipe_layout.handle = pipeline_sub_state.GetPipelineLayoutUnion(last_bound, loc, mode);
assert(inst_binding_pipe_layout.handle != VK_NULL_HANDLE);
if (gpuav.aborted_) {
return;
}
inst_binding_pipe_layout.source = PipelineLayoutSource::LastBoundPipeline;
} else {
// App uses shader objects
const vvl::BindPoint vvl_bind_point = ConvertToVvlBindPoint(last_bound.bind_point);
if (last_bound.desc_set_pipeline_layout) {
inst_binding_pipe_layout.state = last_bound.desc_set_pipeline_layout;
inst_binding_pipe_layout.handle = inst_binding_pipe_layout.state->VkHandle();
inst_binding_pipe_layout.source = PipelineLayoutSource::LastBoundDescriptorSet;
} else if (cb_state.push_constant_latest_used_layout[vvl_bind_point] != VK_NULL_HANDLE) {
inst_binding_pipe_layout.state =
gpuav.Get<vvl::PipelineLayout>(cb_state.push_constant_latest_used_layout[vvl_bind_point]);
inst_binding_pipe_layout.handle = inst_binding_pipe_layout.state->VkHandle();
inst_binding_pipe_layout.source = PipelineLayoutSource::LastPushedConstants;
}
}
}
CommonInstrumentationErrorInfo common_error_info;
common_error_info.action_command_index = cb_state.GetActionCommandIndex(last_bound.bind_point);
common_error_info.pipeline_bind_point = last_bound.bind_point;
if (mode == vvl::DescriptorModeHeap) {
PreCallSetupShaderInstrumentationResourcesDescriptorHeap(gpuav, cb_state, last_bound, common_error_info, loc);
} else if (mode == vvl::DescriptorModeBuffer) {
PreCallSetupShaderInstrumentationResourcesDescriptorBuffer(gpuav, cb_state, last_bound, common_error_info,
inst_binding_pipe_layout, loc);
} else {
PreCallSetupShaderInstrumentationResourcesClassic(gpuav, cb_state, last_bound, common_error_info, inst_binding_pipe_layout,
loc);
}
// We have a single set of InstrumentationErrorLogger for ALL the instrumented shaders
// (there is one for each validation_cmd callback)
{
// TODO - we could reduce adding the loggers we know will never be called
std::vector<CommandBufferSubState::InstrumentationErrorLogger> error_loggers = {};
for (const auto& func : vvl::make_span(cb_state.on_instrumentation_error_logger_register_functions)) {
error_loggers.emplace_back(func(gpuav, cb_state, last_bound));
}
const VkCommandBuffer cb_handle = cb_state.VkHandle();
CommandBufferSubState::ErrorLoggerFunc error_logger =
[&gpuav, cb_handle, common_error_info, error_loggers = std::move(error_loggers)](
const uint32_t* error_record, const Location& loc_with_debug_region, const LogObjectList& objlist) {
bool skip = false;
skip |= LogInstrumentationError(gpuav, cb_handle, objlist, common_error_info, error_record, loc_with_debug_region,
error_loggers);
return skip;
};
cb_state.AddCommandErrorLogger(loc, &last_bound, std::move(error_logger));
}
}
void PostCallSetupShaderInstrumentationResourcesClassic(Validator& gpuav, CommandBufferSubState& cb_state,
const LastBound& last_bound) {
// Only need to rebind application desc sets if they have been disturbed by GPU-AV binding its instrumentation desc set.
// - Can happen if the pipeline layout used to bind instrumentation descriptor set is not compatible with the one used by the
// app to bind the last/all the last desc set.
// => We create this incompatibility when we add our empty descriptor set.
// See PositiveGpuAVDescriptorIndexing.SharedPipelineLayoutSubsetGraphics for instance
if (last_bound.desc_set_pipeline_layout) {
const uint32_t desc_set_bindings_counts_from_last_pipeline = LastBoundPipelineOrShaderDescSetBindingsCount(last_bound);
const bool any_disturbed_desc_sets_bindings =
desc_set_bindings_counts_from_last_pipeline <
static_cast<uint32_t>(last_bound.desc_set_pipeline_layout->set_layouts.list.size());
if (any_disturbed_desc_sets_bindings) {
const uint32_t disturbed_bindings_count = static_cast<uint32_t>(
last_bound.desc_set_pipeline_layout->set_layouts.list.size() - desc_set_bindings_counts_from_last_pipeline);
const uint32_t first_disturbed_set = desc_set_bindings_counts_from_last_pipeline;
for (uint32_t set_i = 0; set_i < disturbed_bindings_count; ++set_i) {
const uint32_t last_bound_set_i = set_i + first_disturbed_set;
const auto& last_bound_set_state = last_bound.ds_slots[last_bound_set_i].ds_state;
// last_bound.ds_slot is a LUT, and descriptor sets before the last one could be unbound.
if (!last_bound_set_state) {
continue;
}
VkDescriptorSet last_bound_set = last_bound_set_state->VkHandle();
const std::vector<uint32_t>& dynamic_offset = last_bound.ds_slots[last_bound_set_i].dynamic_offsets;
const uint32_t dynamic_offset_count = static_cast<uint32_t>(dynamic_offset.size());
DispatchCmdBindDescriptorSets(cb_state.VkHandle(), last_bound.bind_point,
last_bound.desc_set_pipeline_layout->VkHandle(), last_bound_set_i, 1, &last_bound_set,
dynamic_offset_count, dynamic_offset.data());
}
}
}
}
void PostCallSetupShaderInstrumentationResources(Validator& gpuav, CommandBufferSubState& cb_state, const LastBound& last_bound) {
if (!gpuav.gpuav_settings.IsSpirvModified()) {
return;
}
// If nothing was updated, we don't want to bind anything
if (!WasInstrumented(last_bound)) {
return;
}
if (last_bound.GetActionDescriptorMode() == vvl::DescriptorModeClassic) {
PostCallSetupShaderInstrumentationResourcesClassic(gpuav, cb_state, last_bound);
}
}
} // namespace gpuav