blob: 1867575d06083eac119b39e594a406dbaceaaa97 [file] [log] [blame]
/* Copyright (c) 2018-2025 The Khronos Group Inc.
* Copyright (c) 2018-2025 Valve Corporation
* Copyright (c) 2018-2025 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 "gpu/cmd_validation/gpuav_draw.h"
#include "gpu/core/gpuav.h"
#include "gpu/cmd_validation/gpuav_cmd_validation_common.h"
#include "gpu/error_message/gpuav_vuids.h"
#include "gpu/resources/gpuav_vulkan_objects.h"
#include "gpu/resources/gpuav_state_trackers.h"
#include "gpu/shaders/gpuav_error_header.h"
#include "gpu/shaders/gpuav_shaders_constants.h"
#include "state_tracker/render_pass_state.h"
#include "gpu/shaders/gpuav_error_header.h"
#include "gpu/shaders/gpuav_shaders_constants.h"
#include "gpu/shaders/cmd_validation/draw_push_data.h"
#include "generated/cmd_validation_draw_mesh_indirect_comp.h"
#include "generated/cmd_validation_first_instance_comp.h"
#include "generated/cmd_validation_count_buffer_comp.h"
#include "generated/cmd_validation_draw_indexed_comp.h"
#include "generated/cmd_validation_draw_indexed_indirect_index_buffer_comp.h"
#include "generated/cmd_validation_draw_indexed_indirect_vertex_buffer_comp.h"
#include "profiling/profiling.h"
#include <optional>
namespace gpuav {
namespace valcmd {
struct SharedDrawValidationResources {
vko::Buffer dummy_buffer; // Used to fill unused buffer bindings in validation pipelines
bool valid = false;
SharedDrawValidationResources(Validator &gpuav, const Location &loc) : dummy_buffer(gpuav) {
VkBufferCreateInfo dummy_buffer_info = vku::InitStructHelper();
dummy_buffer_info.size = 64;// whatever
dummy_buffer_info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
VmaAllocationCreateInfo alloc_info = {};
dummy_buffer_info.size = dummy_buffer_info.size;
const bool success = dummy_buffer.Create(loc, &dummy_buffer_info, &alloc_info);
if (!success) {
valid = false;
return;
}
valid = true;
}
~SharedDrawValidationResources() { dummy_buffer.Destroy(); }
};
struct BoundStorageBuffer {
uint32_t binding = vvl::kU32Max;
VkDescriptorBufferInfo info{VK_NULL_HANDLE, vvl::kU64Max, 0};
};
template <typename ShaderResources>
struct ComputeValidationPipeline {
ComputeValidationPipeline(Validator &gpuav, const Location &loc, VkDescriptorSetLayout error_output_desc_set_layout) {
std::vector<VkDescriptorSetLayoutBinding> specific_bindings = ShaderResources::GetDescriptorSetLayoutBindings();
VkPushConstantRange push_constant_range = {};
push_constant_range.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
push_constant_range.offset = 0;
push_constant_range.size = sizeof(ShaderResources::push_constants); // 0 size is ok here
device = gpuav.device;
VkDescriptorSetLayoutCreateInfo ds_layout_ci = vku::InitStructHelper();
ds_layout_ci.bindingCount = static_cast<uint32_t>(specific_bindings.size());
ds_layout_ci.pBindings = specific_bindings.data();
VkResult result = DispatchCreateDescriptorSetLayout(device, &ds_layout_ci, nullptr, &specific_desc_set_layout);
if (result != VK_SUCCESS) {
gpuav.InternalError(device, loc, "Unable to create descriptor set layout for SharedDrawValidationResources.");
return;
}
std::array<VkDescriptorSetLayout, 2> set_layouts = {{error_output_desc_set_layout, specific_desc_set_layout}};
VkPipelineLayoutCreateInfo pipeline_layout_ci = vku::InitStructHelper();
if (push_constant_range.size > 0) {
pipeline_layout_ci.pushConstantRangeCount = 1;
pipeline_layout_ci.pPushConstantRanges = &push_constant_range;
}
pipeline_layout_ci.setLayoutCount = static_cast<uint32_t>(set_layouts.size());
pipeline_layout_ci.pSetLayouts = set_layouts.data();
result = DispatchCreatePipelineLayout(device, &pipeline_layout_ci, nullptr, &pipeline_layout);
if (result != VK_SUCCESS) {
gpuav.InternalError(device, loc, "Unable to create pipeline layout for SharedDrawValidationResources.");
return;
}
VkShaderModuleCreateInfo shader_module_ci = vku::InitStructHelper();
shader_module_ci.codeSize = ShaderResources::GetSpirvSize();
shader_module_ci.pCode = ShaderResources::GetSpirv();
result = DispatchCreateShaderModule(device, &shader_module_ci, nullptr, &shader_module);
if (result != VK_SUCCESS) {
gpuav.InternalError(device, loc, "Unable to create shader module.");
return;
}
VkComputePipelineCreateInfo compute_validation_pipeline_ci = vku::InitStructHelper();
compute_validation_pipeline_ci.stage = vku::InitStructHelper();
compute_validation_pipeline_ci.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
compute_validation_pipeline_ci.stage.module = shader_module;
compute_validation_pipeline_ci.stage.pName = "main";
compute_validation_pipeline_ci.layout = pipeline_layout;
result = DispatchCreateComputePipelines(device, VK_NULL_HANDLE, 1, &compute_validation_pipeline_ci, nullptr, &pipeline);
if (result != VK_SUCCESS) {
gpuav.InternalError(device, loc, "Unable to create compute validation pipeline.");
return;
}
valid = true;
}
~ComputeValidationPipeline() {
if (pipeline != VK_NULL_HANDLE) {
DispatchDestroyPipeline(device, pipeline, nullptr);
}
if (shader_module != VK_NULL_HANDLE) {
DispatchDestroyShaderModule(device, shader_module, nullptr);
}
if (specific_desc_set_layout != VK_NULL_HANDLE) {
DispatchDestroyDescriptorSetLayout(device, specific_desc_set_layout, nullptr);
}
if (pipeline_layout != VK_NULL_HANDLE) {
DispatchDestroyPipelineLayout(device, pipeline_layout, nullptr);
}
}
void BindShaderResources(Validator &gpuav, CommandBuffer &cb_state, uint32_t cmd_index, uint32_t error_logger_index,
const ShaderResources &shader_resources) {
// Error logging resources
BindErrorLoggingDescSet(gpuav, cb_state, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, cmd_index, error_logger_index);
// Specific resources
VkDescriptorSet desc_set = cb_state.gpu_resources_manager.GetManagedDescriptorSet(specific_desc_set_layout);
std::vector<VkWriteDescriptorSet> desc_writes = shader_resources.GetDescriptorWrites(desc_set);
DispatchUpdateDescriptorSets(gpuav.device, uint32_t(desc_writes.size()), desc_writes.data(), 0, nullptr);
DispatchCmdPushConstants(cb_state.VkHandle(), pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0,
sizeof(shader_resources.push_constants), &shader_resources.push_constants);
DispatchCmdBindDescriptorSets(cb_state.VkHandle(), VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout,
shader_resources.desc_set_id, 1, &desc_set, 0, nullptr);
}
VkDevice device = VK_NULL_HANDLE;
VkDescriptorSetLayout specific_desc_set_layout = VK_NULL_HANDLE;
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
VkShaderModule shader_module = VK_NULL_HANDLE;
VkPipeline pipeline = VK_NULL_HANDLE;
bool valid = false;
};
void FlushValidationCmds(Validator &gpuav, CommandBuffer &cb_state) {
VVL_TracyPlot("gpuav::valcmd::FlushValidationCmds", int64_t(cb_state.per_render_pass_validation_commands.size()));
VVL_ZoneScoped;
RestorablePipelineState restorable_state(cb_state, VK_PIPELINE_BIND_POINT_COMPUTE);
for (CommandBuffer::ValidationCommandFunc &validation_cmd : cb_state.per_render_pass_validation_commands) {
validation_cmd(gpuav, cb_state);
}
cb_state.per_render_pass_validation_commands.clear();
}
struct FirstInstanceValidationShader {
static size_t GetSpirvSize() { return cmd_validation_first_instance_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return cmd_validation_first_instance_comp; }
static const uint32_t desc_set_id = gpuav::glsl::kDiagPerCmdDescriptorSet;
glsl::FirstInstancePushData push_constants{};
BoundStorageBuffer draw_buffer_binding = {gpuav::glsl::kPreDrawBinding_IndirectBuffer};
BoundStorageBuffer count_buffer_binding = {gpuav::glsl::kPreDrawBinding_CountBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
std::vector<VkDescriptorSetLayoutBinding> bindings = {
{gpuav::glsl::kPreDrawBinding_IndirectBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr}, // indirect buffer
{gpuav::glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr}, // count buffer
};
return bindings;
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites(VkDescriptorSet desc_set) const {
std::vector<VkWriteDescriptorSet> desc_writes(2);
desc_writes[0] = vku::InitStructHelper();
desc_writes[0].dstSet = desc_set;
desc_writes[0].dstBinding = draw_buffer_binding.binding;
desc_writes[0].dstArrayElement = 0;
desc_writes[0].descriptorCount = 1;
desc_writes[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[0].pBufferInfo = &draw_buffer_binding.info;
desc_writes[1] = vku::InitStructHelper();
desc_writes[1].dstSet = desc_set;
desc_writes[1].dstBinding = count_buffer_binding.binding;
desc_writes[1].dstArrayElement = 0;
desc_writes[1].descriptorCount = 1;
desc_writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[1].pBufferInfo = &count_buffer_binding.info;
return desc_writes;
}
};
void FirstInstance(Validator &gpuav, CommandBuffer &cb_state, const Location &loc, VkBuffer draw_buffer,
VkDeviceSize draw_buffer_offset, uint32_t draw_cmds_byte_stride, vvl::Struct draw_indirect_struct_name,
uint32_t first_instance_member_pos, uint32_t draw_count, VkBuffer count_buffer, VkDeviceSize count_buffer_offset,
const char *vuid) {
if (!gpuav.gpuav_settings.validate_indirect_draws_buffers) {
return;
}
if (gpuav.enabled_features.drawIndirectFirstInstance) return;
CommandBuffer::ValidationCommandFunc validation_cmd = [draw_buffer, draw_buffer_offset, draw_cmds_byte_stride,
first_instance_member_pos, draw_count, count_buffer, count_buffer_offset,
draw_i = cb_state.draw_index,
error_logger_i = uint32_t(cb_state.per_command_error_loggers.size()),
loc](Validator &gpuav, CommandBuffer &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_manager.Get<SharedDrawValidationResources>(gpuav, loc);
if (!shared_draw_validation_resources.valid) return;
ComputeValidationPipeline<FirstInstanceValidationShader> &validation_pipeline =
gpuav.shared_resources_manager.Get<ComputeValidationPipeline<FirstInstanceValidationShader>>(
gpuav, loc, cb_state.GetErrorLoggingDescSetLayout());
if (!validation_pipeline.valid) return;
auto draw_buffer_state = gpuav.Get<vvl::Buffer>(draw_buffer);
if (!draw_buffer_state) {
gpuav.InternalError(LogObjectList(cb_state.VkHandle(), draw_buffer), loc, "buffer must be a valid VkBuffer handle");
return;
}
// Setup shader resources
// ---
{
FirstInstanceValidationShader shader_resources;
shader_resources.push_constants.draw_cmds_stride_dwords = draw_cmds_byte_stride / sizeof(uint32_t);
shader_resources.push_constants.cpu_draw_count = draw_count;
shader_resources.push_constants.first_instance_member_pos = first_instance_member_pos;
shader_resources.draw_buffer_binding.info = {draw_buffer, 0, VK_WHOLE_SIZE};
shader_resources.push_constants.draw_buffer_dwords_offset = (uint32_t)draw_buffer_offset / sizeof(uint32_t);
if (count_buffer) {
shader_resources.push_constants.flags |= gpuav::glsl::kFirstInstanceFlags_DrawCountFromBuffer;
shader_resources.count_buffer_binding.info = {count_buffer, 0, sizeof(uint32_t)};
shader_resources.push_constants.count_buffer_dwords_offset = (uint32_t)count_buffer_offset / sizeof(uint32_t);
} else {
shader_resources.count_buffer_binding.info = {shared_draw_validation_resources.dummy_buffer.VkHandle(), 0,
VK_WHOLE_SIZE};
}
validation_pipeline.BindShaderResources(gpuav, cb_state, draw_i, error_logger_i, shader_resources);
}
// Setup validation pipeline
// ---
{
DispatchCmdBindPipeline(cb_state.VkHandle(), VK_PIPELINE_BIND_POINT_COMPUTE, validation_pipeline.pipeline);
uint32_t max_held_draw_cmds = 0;
if (draw_buffer_state->create_info.size > draw_buffer_offset) {
// If drawCount is less than or equal to one, stride is ignored
if (draw_count > 1) {
max_held_draw_cmds =
static_cast<uint32_t>((draw_buffer_state->create_info.size - draw_buffer_offset) / draw_cmds_byte_stride);
} else {
max_held_draw_cmds = 1;
}
}
// It is assumed that the number of draws to validate is fairly low.
// Otherwise might reconsider having a warp dimension of (1, 1, 1)
// Maybe another reason to add telemetry?
const uint32_t work_group_count = std::min(draw_count, max_held_draw_cmds);
if (work_group_count == 0) {
return;
}
VVL_TracyPlot("gpuav::valcmd::FirstInstance Dispatch size", int64_t(work_group_count));
DispatchCmdDispatch(cb_state.VkHandle(), work_group_count, 1, 1);
// synchronize draw buffer validation (read) against subsequent writes
std::array<VkBufferMemoryBarrier, 2> buffer_memory_barriers = {};
uint32_t buffer_memory_barriers_count = 1;
buffer_memory_barriers[0] = vku::InitStructHelper();
buffer_memory_barriers[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
buffer_memory_barriers[0].dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
buffer_memory_barriers[0].buffer = draw_buffer;
buffer_memory_barriers[0].offset = draw_buffer_offset;
buffer_memory_barriers[0].size = work_group_count * sizeof(uint32_t);
if (count_buffer) {
buffer_memory_barriers[1] = vku::InitStructHelper();
buffer_memory_barriers[1].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
buffer_memory_barriers[1].dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
buffer_memory_barriers[1].buffer = count_buffer;
buffer_memory_barriers[1].offset = count_buffer_offset;
buffer_memory_barriers[1].size = sizeof(uint32_t);
++buffer_memory_barriers_count;
}
DispatchCmdPipelineBarrier(cb_state.VkHandle(), VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, buffer_memory_barriers_count,
buffer_memory_barriers.data(), 0, nullptr);
}
};
cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
// Register error logger. Happens per command GPU-AV intercepts
// ---
const uint32_t label_command_i =
!cb_state.GetLabelCommands().empty() ? uint32_t(cb_state.GetLabelCommands().size() - 1) : vvl::kU32Max;
CommandBuffer::ErrorLoggerFunc error_logger =
[loc, vuid, draw_indirect_struct_name, label_command_i](Validator &gpuav, const CommandBuffer &cb_state,
const uint32_t *error_record, const LogObjectList &objlist,
const std::vector<std::string> &initial_label_stack) {
bool skip = false;
using namespace glsl;
if (error_record[kHeaderErrorGroupOffset] != kErrorGroupGpuPreDraw) {
assert(false);
return skip;
}
assert(error_record[kHeaderErrorSubCodeOffset] == kErrorSubCodePreDrawFirstInstance);
const uint32_t index = error_record[kPreActionParamOffset_0];
const uint32_t invalid_first_instance = error_record[kPreActionParamOffset_1];
std::string debug_region_name = cb_state.GetDebugLabelRegion(label_command_i, initial_label_stack);
Location loc_with_debug_region(loc, debug_region_name);
skip |= gpuav.LogError(
vuid, objlist, loc_with_debug_region,
"The drawIndirectFirstInstance feature is not enabled, but the firstInstance member of the %s structure at "
"index %" PRIu32 " is %" PRIu32 ".",
vvl::String(draw_indirect_struct_name), index, invalid_first_instance);
return skip;
};
cb_state.per_command_error_loggers.emplace_back(std::move(error_logger));
}
template <>
void FirstInstance<VkDrawIndirectCommand>(Validator &gpuav, CommandBuffer &cb_state, const Location &loc, VkBuffer draw_buffer,
VkDeviceSize draw_buffer_offset, uint32_t draw_count, VkBuffer count_buffer,
VkDeviceSize count_buffer_offset, const char *vuid) {
FirstInstance(gpuav, cb_state, loc, draw_buffer, draw_buffer_offset, sizeof(VkDrawIndirectCommand), vvl::Struct::VkDrawIndirectCommand, 3,
draw_count, count_buffer, count_buffer_offset, vuid);
}
template <>
void FirstInstance<VkDrawIndexedIndirectCommand>(Validator &gpuav, CommandBuffer &cb_state, const Location &loc,
VkBuffer draw_buffer, VkDeviceSize draw_buffer_offset, uint32_t draw_count,
VkBuffer count_buffer, VkDeviceSize count_buffer_offset, const char *vuid) {
FirstInstance(gpuav, cb_state, loc, draw_buffer, draw_buffer_offset, sizeof(VkDrawIndexedIndirectCommand),
vvl::Struct::VkDrawIndexedIndirectCommand, 4, draw_count, count_buffer, count_buffer_offset, vuid);
}
struct CountBufferValidationShader {
static size_t GetSpirvSize() { return cmd_validation_count_buffer_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return cmd_validation_count_buffer_comp; }
static const uint32_t desc_set_id = gpuav::glsl::kDiagPerCmdDescriptorSet;
glsl::CountBufferPushData push_constants{};
BoundStorageBuffer count_buffer_binding = {gpuav::glsl::kPreDrawBinding_CountBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
std::vector<VkDescriptorSetLayoutBinding> bindings = {
{gpuav::glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr}, // count buffer
};
return bindings;
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites(VkDescriptorSet desc_set) const {
std::vector<VkWriteDescriptorSet> desc_writes(1);
desc_writes[0] = vku::InitStructHelper();
desc_writes[0].dstSet = desc_set;
desc_writes[0].dstBinding = count_buffer_binding.binding;
desc_writes[0].dstArrayElement = 0;
desc_writes[0].descriptorCount = 1;
desc_writes[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[0].pBufferInfo = &count_buffer_binding.info;
return desc_writes;
}
};
void CountBuffer(Validator &gpuav, CommandBuffer &cb_state, const Location &loc, VkBuffer draw_buffer,
VkDeviceSize draw_buffer_offset, uint32_t draw_indirect_struct_byte_size, vvl::Struct draw_indirect_struct_name,
uint32_t draw_cmds_byte_stride, VkBuffer count_buffer, VkDeviceSize count_buffer_offset,
const char *vuid_max_draw_count) {
if (!gpuav.gpuav_settings.validate_indirect_draws_buffers) {
return;
}
if (!gpuav.enabled_features.shaderInt64) {
return;
}
auto draw_buffer_state = gpuav.Get<vvl::Buffer>(draw_buffer);
if (!draw_buffer_state) {
gpuav.InternalError(LogObjectList(cb_state.VkHandle(), draw_buffer), loc, "buffer must be a valid VkBuffer handle");
return;
}
CommandBuffer::ValidationCommandFunc validation_cmd = [draw_buffer_size = draw_buffer_state->create_info.size,
draw_buffer_offset, draw_indirect_struct_byte_size,
draw_cmds_byte_stride, count_buffer, count_buffer_offset,
draw_i = cb_state.draw_index,
error_logger_i = uint32_t(cb_state.per_command_error_loggers.size()),
loc](Validator &gpuav, CommandBuffer &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_manager.Get<SharedDrawValidationResources>(gpuav, loc);
if (!shared_draw_validation_resources.valid) return;
ComputeValidationPipeline<CountBufferValidationShader> &validation_pipeline =
gpuav.shared_resources_manager.Get<ComputeValidationPipeline<CountBufferValidationShader>>(
gpuav, loc, cb_state.GetErrorLoggingDescSetLayout());
if (!validation_pipeline.valid) return;
// Setup shader resources
// ---
{
CountBufferValidationShader shader_resources;
shader_resources.push_constants.draw_cmds_byte_stride = draw_cmds_byte_stride;
shader_resources.push_constants.draw_buffer_offset = draw_buffer_offset;
shader_resources.push_constants.draw_buffer_size = draw_buffer_size;
shader_resources.push_constants.draw_cmd_byte_size = draw_indirect_struct_byte_size;
shader_resources.push_constants.device_limit_max_draw_indirect_count = gpuav.phys_dev_props.limits.maxDrawIndirectCount;
shader_resources.count_buffer_binding.info = {count_buffer, 0, sizeof(uint32_t)};
shader_resources.push_constants.count_buffer_dwords_offset = (uint32_t)count_buffer_offset / sizeof(uint32_t);
validation_pipeline.BindShaderResources(gpuav, cb_state, draw_i, error_logger_i, shader_resources);
}
// Setup validation pipeline
// ---
{
DispatchCmdBindPipeline(cb_state.VkHandle(), VK_PIPELINE_BIND_POINT_COMPUTE, validation_pipeline.pipeline);
DispatchCmdDispatch(cb_state.VkHandle(), 1, 1, 1);
// synchronize draw buffer validation (read) against subsequent writes
VkBufferMemoryBarrier count_buffer_memory_barrier = vku::InitStructHelper();
count_buffer_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
count_buffer_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
count_buffer_memory_barrier.buffer = count_buffer;
count_buffer_memory_barrier.offset = count_buffer_offset;
count_buffer_memory_barrier.size = sizeof(uint32_t);
DispatchCmdPipelineBarrier(cb_state.VkHandle(), VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 1, &count_buffer_memory_barrier, 0,
nullptr);
}
};
cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
// Register error logger
// ---
const uint32_t label_command_i =
!cb_state.GetLabelCommands().empty() ? uint32_t(cb_state.GetLabelCommands().size() - 1) : vvl::kU32Max;
CommandBuffer::ErrorLoggerFunc error_logger = [loc, draw_buffer, draw_buffer_size = draw_buffer_state->create_info.size,
draw_buffer_offset, draw_indirect_struct_byte_size, draw_cmds_byte_stride,
draw_indirect_struct_name, vuid_max_draw_count,
label_command_i](Validator &gpuav, const CommandBuffer &cb_state,
const uint32_t *error_record, const LogObjectList &objlist,
const std::vector<std::string> &initial_label_stack) {
bool skip = false;
using namespace glsl;
std::string debug_region_name = cb_state.GetDebugLabelRegion(label_command_i, initial_label_stack);
Location loc_with_debug_region(loc, debug_region_name);
switch (error_record[kHeaderErrorSubCodeOffset]) {
case kErrorSubCodePreDraw_DrawBufferSize: {
const uint32_t count = error_record[kPreActionParamOffset_0];
const VkDeviceSize draw_size =
(draw_cmds_byte_stride * (count - 1) + draw_buffer_offset + draw_indirect_struct_byte_size);
// Discussed that if drawCount is largeer than the buffer, it is still capped by the maxDrawCount on the CPU (which
// we would have checked is in the buffer range). We decided that we still want to give a warning, but the nothing
// is invalid here. https://gitlab.khronos.org/vulkan/vulkan/-/issues/3991
skip |= gpuav.LogWarning("WARNING-GPU-AV-drawCount", objlist, loc_with_debug_region,
"Indirect draw count of %" PRIu32 " would exceed size (%" PRIu64
") of buffer (%s). "
"stride = %" PRIu32 " offset = %" PRIu64
" (stride * (drawCount - 1) + offset + sizeof(%s)) = %" PRIu64 ".",
count, draw_buffer_size, gpuav.FormatHandle(draw_buffer).c_str(), draw_cmds_byte_stride,
draw_buffer_offset, vvl::String(draw_indirect_struct_name), draw_size);
break;
}
case kErrorSubCodePreDraw_DrawCountLimit: {
const uint32_t count = error_record[kPreActionParamOffset_0];
skip |= gpuav.LogError(vuid_max_draw_count, objlist, loc_with_debug_region,
"Indirect draw count of %" PRIu32 " would exceed maxDrawIndirectCount limit of %" PRIu32 ".",
count, gpuav.phys_dev_props.limits.maxDrawIndirectCount);
break;
}
default:
assert(false);
return skip;
}
return skip;
};
cb_state.per_command_error_loggers.emplace_back(std::move(error_logger));
}
struct MeshValidationShader {
static size_t GetSpirvSize() { return cmd_validation_draw_mesh_indirect_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return cmd_validation_draw_mesh_indirect_comp; }
static const uint32_t desc_set_id = gpuav::glsl::kDiagPerCmdDescriptorSet;
glsl::DrawMeshPushData push_constants{};
BoundStorageBuffer draw_buffer_binding = {gpuav::glsl::kPreDrawBinding_IndirectBuffer};
BoundStorageBuffer count_buffer_binding = {gpuav::glsl::kPreDrawBinding_CountBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
std::vector<VkDescriptorSetLayoutBinding> bindings = {
{gpuav::glsl::kPreDrawBinding_IndirectBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr}, // indirect buffer
{gpuav::glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr}, // count buffer
};
return bindings;
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites(VkDescriptorSet desc_set) const {
std::vector<VkWriteDescriptorSet> desc_writes(2);
desc_writes[0] = vku::InitStructHelper();
desc_writes[0].dstSet = desc_set;
desc_writes[0].dstBinding = draw_buffer_binding.binding;
desc_writes[0].dstArrayElement = 0;
desc_writes[0].descriptorCount = 1;
desc_writes[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[0].pBufferInfo = &draw_buffer_binding.info;
desc_writes[1] = vku::InitStructHelper();
desc_writes[1].dstSet = desc_set;
desc_writes[1].dstBinding = count_buffer_binding.binding;
desc_writes[1].dstArrayElement = 0;
desc_writes[1].descriptorCount = 1;
desc_writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[1].pBufferInfo = &count_buffer_binding.info;
return desc_writes;
}
};
void DrawMeshIndirect(Validator &gpuav, CommandBuffer &cb_state, const Location &loc, VkBuffer draw_buffer,
VkDeviceSize draw_buffer_offset, uint32_t draw_cmds_byte_stride, VkBuffer count_buffer,
VkDeviceSize count_buffer_offset, uint32_t draw_count) {
if (!gpuav.gpuav_settings.validate_indirect_draws_buffers) {
return;
}
auto draw_buffer_state = gpuav.Get<vvl::Buffer>(draw_buffer);
if (!draw_buffer_state) {
gpuav.InternalError(LogObjectList(cb_state.VkHandle(), draw_buffer), loc, "buffer must be a valid VkBuffer handle");
return;
}
const LvlBindPoint lv_bind_point = ConvertToLvlBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS);
const LastBound &last_bound = cb_state.lastBound[lv_bind_point];
const vvl::Pipeline *pipeline_state = last_bound.pipeline_state;
const VkShaderStageFlags stages = pipeline_state->create_info_shaders;
const bool is_task_shader = (stages & VK_SHADER_STAGE_TASK_BIT_EXT) == VK_SHADER_STAGE_TASK_BIT_EXT;
CommandBuffer::ValidationCommandFunc validation_cmd =
[draw_buffer, draw_buffer_full_size = draw_buffer_state->create_info.size, draw_buffer_offset, draw_cmds_byte_stride,
count_buffer, count_buffer_offset, draw_count, is_task_shader, draw_i = cb_state.draw_index,
error_logger_i = uint32_t(cb_state.per_command_error_loggers.size()), loc](Validator &gpuav, CommandBuffer &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_manager.Get<SharedDrawValidationResources>(gpuav, loc);
if (!shared_draw_validation_resources.valid) return;
ComputeValidationPipeline<MeshValidationShader> &validation_pipeline =
gpuav.shared_resources_manager.Get<ComputeValidationPipeline<MeshValidationShader>>(
gpuav, loc, cb_state.GetErrorLoggingDescSetLayout());
if (!validation_pipeline.valid) return;
// Setup shader resources
// ---
{
MeshValidationShader shader_resources;
shader_resources.push_constants.draw_cmds_stride_dwords = draw_cmds_byte_stride / sizeof(uint32_t);
shader_resources.push_constants.cpu_draw_count = draw_count;
if (is_task_shader) {
shader_resources.push_constants.max_workgroup_count_x =
gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxTaskWorkGroupCount[0];
shader_resources.push_constants.max_workgroup_count_y =
gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxTaskWorkGroupCount[1];
shader_resources.push_constants.max_workgroup_count_z =
gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxTaskWorkGroupCount[2];
shader_resources.push_constants.max_workgroup_total_count =
gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxTaskWorkGroupTotalCount;
} else {
shader_resources.push_constants.max_workgroup_count_x =
gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxMeshWorkGroupCount[0];
shader_resources.push_constants.max_workgroup_count_y =
gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxMeshWorkGroupCount[1];
shader_resources.push_constants.max_workgroup_count_z =
gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxMeshWorkGroupCount[2];
shader_resources.push_constants.max_workgroup_total_count =
gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxMeshWorkGroupTotalCount;
}
shader_resources.draw_buffer_binding.info = {draw_buffer, 0, VK_WHOLE_SIZE};
shader_resources.push_constants.draw_buffer_dwords_offset = (uint32_t)draw_buffer_offset / sizeof(uint32_t);
if (count_buffer != VK_NULL_HANDLE) {
shader_resources.push_constants.flags |= glsl::kDrawMeshFlags_DrawCountFromBuffer;
shader_resources.count_buffer_binding.info = {count_buffer, 0, sizeof(uint32_t)};
shader_resources.push_constants.count_buffer_dwords_offset = (uint32_t)count_buffer_offset / sizeof(uint32_t);
} else {
shader_resources.count_buffer_binding.info = {shared_draw_validation_resources.dummy_buffer.VkHandle(), 0,
VK_WHOLE_SIZE};
}
validation_pipeline.BindShaderResources(gpuav, cb_state, draw_i, error_logger_i, shader_resources);
}
// Setup validation pipeline
// ---
{
DispatchCmdBindPipeline(cb_state.VkHandle(), VK_PIPELINE_BIND_POINT_COMPUTE, validation_pipeline.pipeline);
uint32_t max_held_draw_cmds = 0;
if (draw_buffer_full_size > draw_buffer_offset) {
// If drawCount is less than or equal to one, stride is ignored
if (draw_count > 1) {
max_held_draw_cmds =
static_cast<uint32_t>((draw_buffer_full_size - draw_buffer_offset) / draw_cmds_byte_stride);
} else {
max_held_draw_cmds = 1;
}
}
const uint32_t work_group_count = std::min(draw_count, max_held_draw_cmds);
VVL_TracyPlot("gpuav::valcmd::DrawMeshIndirect Dispatch size", int64_t(work_group_count));
DispatchCmdDispatch(cb_state.VkHandle(), work_group_count, 1, 1);
// synchronize draw buffer validation (read) against subsequent writes
std::array<VkBufferMemoryBarrier, 2> buffer_memory_barriers = {};
uint32_t buffer_memory_barriers_count = 1;
buffer_memory_barriers[0] = vku::InitStructHelper();
buffer_memory_barriers[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
buffer_memory_barriers[0].dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
buffer_memory_barriers[0].buffer = draw_buffer;
buffer_memory_barriers[0].offset = draw_buffer_offset;
buffer_memory_barriers[0].size = work_group_count * sizeof(uint32_t);
if (count_buffer) {
buffer_memory_barriers[1] = vku::InitStructHelper();
buffer_memory_barriers[1].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
buffer_memory_barriers[1].dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
buffer_memory_barriers[1].buffer = count_buffer;
buffer_memory_barriers[1].offset = count_buffer_offset;
buffer_memory_barriers[1].size = sizeof(uint32_t);
++buffer_memory_barriers_count;
}
DispatchCmdPipelineBarrier(cb_state.VkHandle(), VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, buffer_memory_barriers_count,
buffer_memory_barriers.data(), 0, nullptr);
}
};
cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
// Register error logger
// ---
const uint32_t label_command_i =
!cb_state.GetLabelCommands().empty() ? uint32_t(cb_state.GetLabelCommands().size() - 1) : vvl::kU32Max;
CommandBuffer::ErrorLoggerFunc error_logger = [loc, is_task_shader, label_command_i](
Validator &gpuav, const CommandBuffer &cb_state, const uint32_t *error_record,
const LogObjectList &objlist,
const std::vector<std::string> &initial_label_stack) {
bool skip = false;
using namespace glsl;
const char *vuid_task_group_count_exceeds_max_x = "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07322";
const char *vuid_task_group_count_exceeds_max_y = "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07323";
const char *vuid_task_group_count_exceeds_max_z = "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07324";
const char *vuid_task_group_count_exceeds_max_total = "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07325";
const char *vuid_mesh_group_count_exceeds_max_x = "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07326";
const char *vuid_mesh_group_count_exceeds_max_y = "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07327";
const char *vuid_mesh_group_count_exceeds_max_z = "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07328";
const char *vuid_mesh_group_count_exceeds_max_total = "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07329";
const uint32_t draw_i = error_record[kPreActionParamOffset_1];
const char *group_count_name = is_task_shader ? "maxTaskWorkGroupCount" : "maxMeshWorkGroupCount";
const char *group_count_total_name = is_task_shader ? "maxTaskWorkGroupTotalCount" : "maxMeshWorkGroupTotalCount";
std::string debug_region_name = cb_state.GetDebugLabelRegion(label_command_i, initial_label_stack);
Location loc_with_debug_region(loc, debug_region_name);
switch (error_record[kHeaderErrorSubCodeOffset]) {
case kErrorSubCodePreDrawGroupCountX: {
const char *vuid_group_count_exceeds_max =
is_task_shader ? vuid_task_group_count_exceeds_max_x : vuid_mesh_group_count_exceeds_max_x;
const uint32_t group_count_x = error_record[kPreActionParamOffset_0];
const uint32_t limit = is_task_shader ? gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxTaskWorkGroupCount[0]
: gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxMeshWorkGroupCount[0];
skip |= gpuav.LogError(vuid_group_count_exceeds_max, objlist, loc_with_debug_region,
"In draw %" PRIu32 ", VkDrawMeshTasksIndirectCommandEXT::groupCountX is %" PRIu32
" which is greater than VkPhysicalDeviceMeshShaderPropertiesEXT::%s[0]"
" (%" PRIu32 ").",
draw_i, group_count_x, group_count_name, limit);
break;
}
case kErrorSubCodePreDrawGroupCountY: {
const char *vuid_group_count_exceeds_max =
is_task_shader ? vuid_task_group_count_exceeds_max_y : vuid_mesh_group_count_exceeds_max_y;
const uint32_t group_count_y = error_record[kPreActionParamOffset_0];
const uint32_t limit = is_task_shader ? gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxTaskWorkGroupCount[1]
: gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxMeshWorkGroupCount[1];
skip |= gpuav.LogError(vuid_group_count_exceeds_max, objlist, loc_with_debug_region,
"In draw %" PRIu32 ", VkDrawMeshTasksIndirectCommandEXT::groupCountY is %" PRIu32
" which is greater than VkPhysicalDeviceMeshShaderPropertiesEXT::%s[1]"
" (%" PRIu32 ").",
draw_i, group_count_y, group_count_name, limit);
break;
}
case kErrorSubCodePreDrawGroupCountZ: {
const char *vuid_group_count_exceeds_max =
is_task_shader ? vuid_task_group_count_exceeds_max_z : vuid_mesh_group_count_exceeds_max_z;
const uint32_t group_count_z = error_record[kPreActionParamOffset_0];
const uint32_t limit = is_task_shader ? gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxTaskWorkGroupCount[2]
: gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxMeshWorkGroupCount[2];
skip |= gpuav.LogError(vuid_group_count_exceeds_max, objlist, loc_with_debug_region,
"In draw %" PRIu32 ", VkDrawMeshTasksIndirectCommandEXT::groupCountZ is %" PRIu32
" which is greater than VkPhysicalDeviceMeshShaderPropertiesEXT::%s[2]"
" (%" PRIu32 ").",
draw_i, group_count_z, group_count_name, limit);
break;
}
case kErrorSubCodePreDrawGroupCountTotal: {
const char *vuid_group_count_exceeds_max =
is_task_shader ? vuid_task_group_count_exceeds_max_total : vuid_mesh_group_count_exceeds_max_total;
const uint32_t group_count_total = error_record[kPreActionParamOffset_0];
const uint32_t limit = is_task_shader ? gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxTaskWorkGroupTotalCount
: gpuav.phys_dev_ext_props.mesh_shader_props_ext.maxMeshWorkGroupTotalCount;
skip |= gpuav.LogError(vuid_group_count_exceeds_max, objlist, loc_with_debug_region,
"In draw %" PRIu32 ", size of VkDrawMeshTasksIndirectCommandEXT is %" PRIu32
" which is greater than VkPhysicalDeviceMeshShaderPropertiesEXT::%s"
" (%" PRIu32 ").",
draw_i, group_count_total, group_count_total_name, limit);
break;
}
default:
assert(false);
return skip;
}
return skip;
};
cb_state.per_command_error_loggers.emplace_back(std::move(error_logger));
}
struct SmallestVertexBufferBinding {
VkDeviceSize smallest_vertex_attributes_count = std::numeric_limits<VkDeviceSize>::max();
uint32_t binding = std::numeric_limits<uint32_t>::max();
vvl::VertexBufferBinding binding_info{};
struct Attribute {
uint32_t location{};
uint32_t binding{};
VkFormat format{};
uint32_t offset{};
} attribute;
};
// Computes the smallest vertex attributes count among the set of bound vertex buffers.
// Used to detect out of bounds indices in index buffers.
// If no vertex buffer is bound, smallest_vertex_attributes_count is std::numeric_limits<uint32_t>::max()
// indicating that no index can be out of bound
static std::optional<SmallestVertexBufferBinding> SmallestVertexAttributesCount(const vvl::CommandBuffer &cb_state) {
const LvlBindPoint lv_bind_point = ConvertToLvlBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS);
const LastBound &last_bound = cb_state.lastBound[lv_bind_point];
const vvl::Pipeline *pipeline_state = last_bound.pipeline_state;
const bool use_shader_objects = pipeline_state == nullptr;
const bool dynamic_vertex_input = use_shader_objects || pipeline_state->IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT);
const auto &vertex_binding_descriptions =
dynamic_vertex_input ? cb_state.dynamic_state_value.vertex_bindings : pipeline_state->vertex_input_state->bindings;
std::optional<SmallestVertexBufferBinding> smallest_vertex_buffer_binding;
for (const auto &[binding, vertex_binding_desc] : vertex_binding_descriptions) {
auto find_vbb = cb_state.current_vertex_buffer_binding_info.find(binding);
if (find_vbb == cb_state.current_vertex_buffer_binding_info.cend()) {
// This is a validation error
continue;
}
const vvl::VertexBufferBinding &vbb = find_vbb->second;
// #ARNO_TODO: Should I only loop over vertex attributes actually used by bound pipelines,
// according to its vertex shader?
for (const auto &[Location, attrib] : vertex_binding_desc.locations) {
const VkDeviceSize attribute_size = vkuFormatElementSize(attrib.desc.format);
const VkDeviceSize stride =
vbb.stride != 0 ? vbb.stride : attribute_size; // Tracked stride should already handle all possible value origin
VkDeviceSize vertex_buffer_remaining_size =
vbb.effective_size > attrib.desc.offset ? vbb.effective_size - attrib.desc.offset : 0;
VkDeviceSize vertex_attributes_count = vertex_buffer_remaining_size / stride;
if (vertex_buffer_remaining_size > vertex_attributes_count * stride) {
vertex_buffer_remaining_size -= vertex_attributes_count * stride;
} else {
vertex_buffer_remaining_size = 0;
}
// maybe room for one more attribute but not full stride - not having stride space does not matter for last element
if (vertex_buffer_remaining_size >= attribute_size) {
vertex_attributes_count += 1;
}
if (!smallest_vertex_buffer_binding.has_value()) {
smallest_vertex_buffer_binding = SmallestVertexBufferBinding{};
}
smallest_vertex_buffer_binding->smallest_vertex_attributes_count =
std::min(smallest_vertex_buffer_binding->smallest_vertex_attributes_count, vertex_attributes_count);
if (smallest_vertex_buffer_binding->smallest_vertex_attributes_count == vertex_attributes_count) {
smallest_vertex_buffer_binding->binding = binding;
smallest_vertex_buffer_binding->binding_info = vbb;
smallest_vertex_buffer_binding->attribute.location = attrib.desc.location;
smallest_vertex_buffer_binding->attribute.binding = attrib.desc.binding;
smallest_vertex_buffer_binding->attribute.format = attrib.desc.format;
smallest_vertex_buffer_binding->attribute.offset = attrib.desc.offset;
}
}
}
return smallest_vertex_buffer_binding;
}
struct IndexBufferValidationShader {
static size_t GetSpirvSize() { return cmd_validation_draw_indexed_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return cmd_validation_draw_indexed_comp; }
static const uint32_t desc_set_id = gpuav::glsl::kDiagPerCmdDescriptorSet;
glsl::IndexedDrawPushData push_constants{};
BoundStorageBuffer index_buffer_binding = {gpuav::glsl::kPreDrawBinding_IndexBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
std::vector<VkDescriptorSetLayoutBinding> bindings = {
{gpuav::glsl::kPreDrawBinding_IndexBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}};
return bindings;
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites(VkDescriptorSet desc_set) const {
std::vector<VkWriteDescriptorSet> desc_writes(1);
desc_writes[0] = vku::InitStructHelper();
desc_writes[0].dstSet = desc_set;
desc_writes[0].dstBinding = index_buffer_binding.binding;
desc_writes[0].dstArrayElement = 0;
desc_writes[0].descriptorCount = 1;
desc_writes[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[0].pBufferInfo = &index_buffer_binding.info;
return desc_writes;
}
};
void DrawIndexed(Validator &gpuav, CommandBuffer &cb_state, const Location &loc, uint32_t index_count, uint32_t first_index,
uint32_t vertex_offset, const char *vuid_oob_vertex) {
if (!gpuav.gpuav_settings.validate_index_buffers) {
return;
}
if (gpuav.enabled_features.robustBufferAccess) {
return;
}
const LvlBindPoint lv_bind_point = ConvertToLvlBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS);
const LastBound &last_bound = cb_state.lastBound[lv_bind_point];
const vvl::Pipeline *pipeline_state = last_bound.pipeline_state;
if (pipeline_state) {
const auto robustness_ci =
vku::FindStructInPNextChain<VkPipelineRobustnessCreateInfo>(pipeline_state->GraphicsCreateInfo().pNext);
if (robustness_ci && robustness_ci->vertexInputs) {
return;
}
}
if (!cb_state.IsPrimary()) {
// TODO Unhandled for now. Potential issues with accessing the right vertex buffers
// in secondary command buffers
return;
}
if (!cb_state.index_buffer_binding.buffer) {
return;
}
const std::optional<SmallestVertexBufferBinding> smallest_vertex_buffer_binding = SmallestVertexAttributesCount(cb_state);
if (!smallest_vertex_buffer_binding.has_value()) {
// cannot overrun index buffer, skip validation
return;
}
CommandBuffer::ValidationCommandFunc validation_cmd = [index_buffer_binding = cb_state.index_buffer_binding, index_count,
first_index, vertex_offset,
smallest_vertex_buffer_binding = *smallest_vertex_buffer_binding,
draw_i = cb_state.draw_index,
error_logger_i = uint32_t(cb_state.per_command_error_loggers.size()),
loc](Validator &gpuav, CommandBuffer &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_manager.Get<SharedDrawValidationResources>(gpuav, loc);
if (!shared_draw_validation_resources.valid) return;
ComputeValidationPipeline<IndexBufferValidationShader> &validation_pipeline =
gpuav.shared_resources_manager.Get<ComputeValidationPipeline<IndexBufferValidationShader>>(
gpuav, loc, cb_state.GetErrorLoggingDescSetLayout());
if (!validation_pipeline.valid) return;
const uint32_t index_bits_size = GetIndexBitsSize(index_buffer_binding.index_type);
const uint32_t max_indices_in_buffer = static_cast<uint32_t>(index_buffer_binding.size / (index_bits_size / 8u));
const uint32_t indices_to_check = std::min(index_count, max_indices_in_buffer);
{
IndexBufferValidationShader shader_resources;
shader_resources.push_constants.smallest_vertex_attributes_count =
static_cast<uint32_t>(smallest_vertex_buffer_binding.smallest_vertex_attributes_count);
shader_resources.push_constants.index_width = index_bits_size;
shader_resources.push_constants.vertex_offset = vertex_offset;
shader_resources.push_constants.first_index = first_index;
shader_resources.index_buffer_binding.info = {index_buffer_binding.buffer, 0,
index_buffer_binding.offset + index_buffer_binding.size};
shader_resources.push_constants.index_buffer_dwords_offset = (uint32_t)index_buffer_binding.offset / sizeof(uint32_t);
shader_resources.push_constants.indices_to_check = indices_to_check;
validation_pipeline.BindShaderResources(gpuav, cb_state, draw_i, error_logger_i, shader_resources);
}
{
DispatchCmdBindPipeline(cb_state.VkHandle(), VK_PIPELINE_BIND_POINT_COMPUTE, validation_pipeline.pipeline);
// Do not overrun index buffer
constexpr uint32_t work_group_size = 64;
const uint32_t work_group_count =
indices_to_check / work_group_size + uint32_t(indices_to_check % work_group_size != 0);
VVL_TracyPlot("gpuav::valcmd::DrawIndexed Dispatch size", int64_t(work_group_count));
DispatchCmdDispatch(cb_state.VkHandle(), work_group_count, 1, 1);
// synchronize draw buffer validation (read) against subsequent writes
VkBufferMemoryBarrier index_buffer_memory_barrier = vku::InitStructHelper();
index_buffer_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
index_buffer_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
index_buffer_memory_barrier.buffer = index_buffer_binding.buffer;
index_buffer_memory_barrier.offset = index_buffer_binding.offset;
index_buffer_memory_barrier.size = index_buffer_binding.size;
DispatchCmdPipelineBarrier(cb_state.VkHandle(), VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 1, &index_buffer_memory_barrier, 0,
nullptr);
}
};
cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
const uint32_t label_command_i =
!cb_state.GetLabelCommands().empty() ? uint32_t(cb_state.GetLabelCommands().size() - 1) : vvl::kU32Max;
CommandBuffer::ErrorLoggerFunc error_logger =
[loc, vuid_oob_vertex, smallest_vertex_buffer_binding = *smallest_vertex_buffer_binding,
index_buffer_binding = cb_state.index_buffer_binding,
label_command_i](Validator &gpuav, const CommandBuffer &cb_state, const uint32_t *error_record,
const LogObjectList &objlist, const std::vector<std::string> &initial_label_stack) {
bool skip = false;
using namespace glsl;
switch (error_record[kHeaderErrorSubCodeOffset]) {
case kErrorSubCode_OobVertexBuffer: {
const uint32_t index_buffer_offset = error_record[kPreActionParamOffset_0];
const int32_t vertex_offset = static_cast<int32_t>(error_record[kPreActionParamOffset_1]);
const uint32_t vertex_index = error_record[kPreActionParamOffset_2];
const uint32_t index_buffer_value = static_cast<uint32_t>(int32_t(vertex_index) - vertex_offset);
const uint32_t index_bits_size = GetIndexBitsSize(index_buffer_binding.index_type);
const uint32_t max_indices_in_buffer =
static_cast<uint32_t>(index_buffer_binding.size / (index_bits_size / 8u));
std::string debug_region_name = cb_state.GetDebugLabelRegion(label_command_i, initial_label_stack);
Location loc_with_debug_region(loc, debug_region_name);
skip |= gpuav.LogError(
vuid_oob_vertex, objlist, loc_with_debug_region,
"Vertex index %" PRIu32
" is not within the smallest bound vertex buffer.\n"
"index_buffer[ %" PRIu32 " ] (%" PRIu32 ") + vertexOffset (%" PRIi32 ") = Vertex index %" PRIu32
"\n"
"Smallest vertex buffer binding info, causing OOB access:\n"
"- Buffer: %s\n"
"- Binding: %" PRIu32
"\n"
"- Binding size (effective): %" PRIu64
" bytes\n"
"- Binding offset: %" PRIu64
" bytes\n"
"- Binding stride: %" PRIu64
" bytes\n"
"- Vertices count: %" PRIu64
"\n"
"At least the following vertex attribute caused OOB access:\n"
"- location: %" PRIu32
"\n"
"- binding: %" PRIu32
"\n"
"- format: %s\n"
"- offset: %" PRIu32
" bytes\n"
"Index buffer binding info:\n"
"- Buffer: %s\n"
"- Index type: %s\n"
"- Binding offset: %" PRIu64
" bytes\n"
"- Binding size: %" PRIu64 " bytes (or %" PRIu32
" %s)\n"
"Note: Vertex buffer binding size is the effective, valid one, based on how the VkBuffer was created and "
"vertex buffer binding parameters. So it can be clamped up to 0 if binding was invalid.\n",
// OOB vertex index info
vertex_index, index_buffer_offset, index_buffer_value, vertex_offset, vertex_index,
// Vertex buffer binding info
gpuav.FormatHandle(smallest_vertex_buffer_binding.binding_info.buffer).c_str(),
smallest_vertex_buffer_binding.binding, smallest_vertex_buffer_binding.binding_info.effective_size,
smallest_vertex_buffer_binding.binding_info.offset, smallest_vertex_buffer_binding.binding_info.stride,
smallest_vertex_buffer_binding.smallest_vertex_attributes_count,
// Vertex Attribute info
smallest_vertex_buffer_binding.attribute.location, smallest_vertex_buffer_binding.attribute.binding,
string_VkFormat(smallest_vertex_buffer_binding.attribute.format),
smallest_vertex_buffer_binding.attribute.offset,
// Index buffer binding info
gpuav.FormatHandle(index_buffer_binding.buffer).c_str(),
string_VkIndexType(index_buffer_binding.index_type), index_buffer_binding.offset, index_buffer_binding.size,
max_indices_in_buffer, string_VkIndexType(index_buffer_binding.index_type));
break;
}
default:
assert(false);
return skip;
}
return skip;
};
cb_state.per_command_error_loggers.emplace_back(std::move(error_logger));
}
struct DrawIndexedIndirectIndexBufferShader {
static size_t GetSpirvSize() { return cmd_validation_draw_indexed_indirect_index_buffer_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return cmd_validation_draw_indexed_indirect_index_buffer_comp; }
static const uint32_t desc_set_id = gpuav::glsl::kDiagPerCmdDescriptorSet;
glsl::DrawIndexedIndirectIndexBufferPushData push_constants{};
BoundStorageBuffer draw_buffer_binding = {gpuav::glsl::kPreDrawBinding_IndirectBuffer};
BoundStorageBuffer count_buffer_binding = {gpuav::glsl::kPreDrawBinding_CountBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
std::vector<VkDescriptorSetLayoutBinding> bindings = {
{gpuav::glsl::kPreDrawBinding_IndirectBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr},
{gpuav::glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
{gpuav::glsl::kPreDrawBinding_IndexBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}};
return bindings;
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites(VkDescriptorSet desc_set) const {
std::vector<VkWriteDescriptorSet> desc_writes(2);
desc_writes[0] = vku::InitStructHelper();
desc_writes[0].dstSet = desc_set;
desc_writes[0].dstBinding = draw_buffer_binding.binding;
desc_writes[0].dstArrayElement = 0;
desc_writes[0].descriptorCount = 1;
desc_writes[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[0].pBufferInfo = &draw_buffer_binding.info;
desc_writes[1] = vku::InitStructHelper();
desc_writes[1].dstSet = desc_set;
desc_writes[1].dstBinding = count_buffer_binding.binding;
desc_writes[1].dstArrayElement = 0;
desc_writes[1].descriptorCount = 1;
desc_writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[1].pBufferInfo = &count_buffer_binding.info;
return desc_writes;
}
};
void DrawIndexedIndirectIndexBuffer(Validator &gpuav, CommandBuffer &cb_state, const Location &loc, VkBuffer draw_buffer,
VkDeviceSize draw_buffer_offset, uint32_t draw_cmds_byte_stride, uint32_t draw_count,
VkBuffer count_buffer, VkDeviceSize count_buffer_offset, const char *vuid_oob_index) {
if (!gpuav.gpuav_settings.validate_index_buffers) {
return;
}
if (gpuav.enabled_features.robustBufferAccess2) {
return;
}
if (gpuav.enabled_features.pipelineRobustness) {
const LvlBindPoint lv_bind_point = ConvertToLvlBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS);
const LastBound &last_bound = cb_state.lastBound[lv_bind_point];
const vvl::Pipeline *pipeline_state = last_bound.pipeline_state;
if (pipeline_state) {
const auto robustness_ci =
vku::FindStructInPNextChain<VkPipelineRobustnessCreateInfo>(pipeline_state->GraphicsCreateInfo().pNext);
if (robustness_ci && robustness_ci->vertexInputs) {
return;
}
}
}
if (!cb_state.IsPrimary()) {
// TODO Unhandled for now. Potential issues with accessing the right vertex buffers
// in secondary command buffers
return;
}
if (!cb_state.index_buffer_binding.buffer) {
return;
}
CommandBuffer::ValidationCommandFunc validation_cmd = [index_buffer_binding = cb_state.index_buffer_binding, draw_buffer,
draw_buffer_offset, draw_cmds_byte_stride, draw_count, count_buffer,
count_buffer_offset, draw_i = cb_state.draw_index,
error_logger_i = uint32_t(cb_state.per_command_error_loggers.size()),
loc](Validator &gpuav, CommandBuffer &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_manager.Get<SharedDrawValidationResources>(gpuav, loc);
if (!shared_draw_validation_resources.valid) return;
ComputeValidationPipeline<DrawIndexedIndirectIndexBufferShader> &validation_pipeline =
gpuav.shared_resources_manager.Get<ComputeValidationPipeline<DrawIndexedIndirectIndexBufferShader>>(
gpuav, loc, cb_state.GetErrorLoggingDescSetLayout());
if (!validation_pipeline.valid) return;
const uint32_t index_bits_size = GetIndexBitsSize(index_buffer_binding.index_type);
const uint32_t max_indices_in_buffer = static_cast<uint32_t>(index_buffer_binding.size / (index_bits_size / 8u));
{
DrawIndexedIndirectIndexBufferShader shader_resources;
if (count_buffer != VK_NULL_HANDLE) {
shader_resources.push_constants.flags |= glsl::kIndexedIndirectDrawFlags_DrawCountFromBuffer;
shader_resources.count_buffer_binding.info = {count_buffer, 0, sizeof(uint32_t)};
shader_resources.push_constants.count_buffer_dwords_offset = (uint32_t)count_buffer_offset / sizeof(uint32_t);
} else {
shader_resources.count_buffer_binding.info = {shared_draw_validation_resources.dummy_buffer.VkHandle(), 0,
VK_WHOLE_SIZE};
}
shader_resources.push_constants.draw_cmds_stride_dwords = draw_cmds_byte_stride / sizeof(uint32_t);
shader_resources.push_constants.bound_index_buffer_indices_count = max_indices_in_buffer;
shader_resources.push_constants.cpu_draw_count = draw_count;
shader_resources.draw_buffer_binding.info = {draw_buffer, 0, VK_WHOLE_SIZE};
shader_resources.push_constants.draw_indexed_indirect_cmds_buffer_dwords_offset =
(uint32_t)draw_buffer_offset / sizeof(uint32_t);
validation_pipeline.BindShaderResources(gpuav, cb_state, draw_i, error_logger_i, shader_resources);
}
{
DispatchCmdBindPipeline(cb_state.VkHandle(), VK_PIPELINE_BIND_POINT_COMPUTE, validation_pipeline.pipeline);
// One draw will check all VkDrawIndexedIndirectCommand
DispatchCmdDispatch(cb_state.VkHandle(), 1, 1, 1);
// synchronize draw buffer validation (read) against subsequent writes
std::array<VkBufferMemoryBarrier, 2> buffer_memory_barriers = {};
uint32_t buffer_memory_barriers_count = 1;
buffer_memory_barriers[0] = vku::InitStructHelper();
buffer_memory_barriers[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
buffer_memory_barriers[0].dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
buffer_memory_barriers[0].buffer = draw_buffer;
buffer_memory_barriers[0].offset = draw_buffer_offset;
buffer_memory_barriers[0].size = VK_WHOLE_SIZE;
if (count_buffer) {
buffer_memory_barriers[1] = vku::InitStructHelper();
buffer_memory_barriers[1].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
buffer_memory_barriers[1].dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
buffer_memory_barriers[1].buffer = count_buffer;
buffer_memory_barriers[1].offset = count_buffer_offset;
buffer_memory_barriers[1].size = sizeof(uint32_t);
++buffer_memory_barriers_count;
}
DispatchCmdPipelineBarrier(cb_state.VkHandle(), VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, buffer_memory_barriers_count,
buffer_memory_barriers.data(), 0, nullptr);
}
};
cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
const uint32_t label_command_i =
!cb_state.GetLabelCommands().empty() ? uint32_t(cb_state.GetLabelCommands().size() - 1) : vvl::kU32Max;
CommandBuffer::ErrorLoggerFunc error_logger = [loc, vuid_oob_index, draw_buffer, draw_buffer_offset, draw_cmds_byte_stride,
index_buffer_binding = cb_state.index_buffer_binding,
label_command_i](Validator &gpuav, const CommandBuffer &cb_state,
const uint32_t *error_record, const LogObjectList &objlist,
const std::vector<std::string> &initial_label_stack) {
bool skip = false;
using namespace glsl;
switch (error_record[kHeaderErrorSubCodeOffset]) {
case kErrorSubCode_OobIndexBuffer: {
const uint32_t draw_i = error_record[kPreActionParamOffset_0];
const uint32_t first_index = error_record[kPreActionParamOffset_1];
const uint32_t index_count = error_record[kPreActionParamOffset_2];
const uint32_t highest_accessed_index = first_index + index_count;
const uint32_t index_bits_size = GetIndexBitsSize(index_buffer_binding.index_type);
const uint32_t max_indices_in_buffer = static_cast<uint32_t>(index_buffer_binding.size / (index_bits_size / 8u));
std::string debug_region_name = cb_state.GetDebugLabelRegion(label_command_i, initial_label_stack);
Location loc_with_debug_region(loc, debug_region_name);
skip |= gpuav.LogError(
vuid_oob_index, objlist, loc_with_debug_region,
"Index %" PRIu32 " is not within the bound index buffer. Computed from VkDrawIndexedIndirectCommand[%" PRIu32
"] (.firstIndex = %" PRIu32 ", .indexCount = %" PRIu32
"), stored in %s\n"
"Index buffer binding info:\n"
"- Buffer: %s\n"
"- Index type: %s\n"
"- Binding offset: %" PRIu64
"\n"
"- Binding size: %" PRIu64 " bytes (or %" PRIu32
" %s)\n"
"Supplied buffer parameters in indirect command: offset = %" PRIu64 ", stride = %" PRIu32 " bytes.",
// OOB index info
highest_accessed_index, draw_i, first_index, index_count, gpuav.FormatHandle(draw_buffer).c_str(),
// Index buffer binding info
gpuav.FormatHandle(index_buffer_binding.buffer).c_str(), string_VkIndexType(index_buffer_binding.index_type),
index_buffer_binding.offset, index_buffer_binding.size, max_indices_in_buffer,
string_VkIndexType(index_buffer_binding.index_type),
// VkDrawIndexedIndirectCommand info
draw_buffer_offset, draw_cmds_byte_stride);
break;
}
default:
assert(false);
return skip;
}
return skip;
};
cb_state.per_command_error_loggers.emplace_back(std::move(error_logger));
}
struct DrawIndexedIndirectVertexBufferShader {
static size_t GetSpirvSize() { return cmd_validation_draw_indexed_indirect_vertex_buffer_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return cmd_validation_draw_indexed_indirect_vertex_buffer_comp; }
static const uint32_t desc_set_id = gpuav::glsl::kDiagPerCmdDescriptorSet;
glsl::DrawIndexedIndirectVertexBufferPushData push_constants{};
BoundStorageBuffer draw_buffer_binding = {gpuav::glsl::kPreDrawBinding_IndirectBuffer};
BoundStorageBuffer count_buffer_binding = {gpuav::glsl::kPreDrawBinding_CountBuffer};
BoundStorageBuffer index_buffer_binding = {gpuav::glsl::kPreDrawBinding_IndexBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
std::vector<VkDescriptorSetLayoutBinding> bindings = {
{gpuav::glsl::kPreDrawBinding_IndirectBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr},
{gpuav::glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
{gpuav::glsl::kPreDrawBinding_IndexBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}};
return bindings;
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites(VkDescriptorSet desc_set) const {
std::vector<VkWriteDescriptorSet> desc_writes(3);
desc_writes[0] = vku::InitStructHelper();
desc_writes[0].dstSet = desc_set;
desc_writes[0].dstBinding = draw_buffer_binding.binding;
desc_writes[0].dstArrayElement = 0;
desc_writes[0].descriptorCount = 1;
desc_writes[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[0].pBufferInfo = &draw_buffer_binding.info;
desc_writes[1] = vku::InitStructHelper();
desc_writes[1].dstSet = desc_set;
desc_writes[1].dstBinding = count_buffer_binding.binding;
desc_writes[1].dstArrayElement = 0;
desc_writes[1].descriptorCount = 1;
desc_writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[1].pBufferInfo = &count_buffer_binding.info;
desc_writes[2] = vku::InitStructHelper();
desc_writes[2].dstSet = desc_set;
desc_writes[2].dstBinding = index_buffer_binding.binding;
desc_writes[2].dstArrayElement = 0;
desc_writes[2].descriptorCount = 1;
desc_writes[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
desc_writes[2].pBufferInfo = &index_buffer_binding.info;
return desc_writes;
}
};
void DrawIndexedIndirectVertexBuffer(Validator &gpuav, CommandBuffer &cb_state, const Location &loc, VkBuffer draw_buffer,
VkDeviceSize draw_buffer_offset, uint32_t draw_cmds_byte_stride, uint32_t draw_count,
VkBuffer count_buffer, VkDeviceSize count_buffer_offset, const char *vuid_oob_vertex) {
if (!gpuav.gpuav_settings.validate_index_buffers) {
return;
}
if (gpuav.enabled_features.robustBufferAccess) {
return;
}
if (!cb_state.IsPrimary()) {
// TODO Unhandled for now. Potential issues with accessing the right vertex buffers
// in secondary command buffers
return;
}
if (!cb_state.index_buffer_binding.buffer) {
return;
}
const std::optional<SmallestVertexBufferBinding> smallest_vertex_buffer_binding = SmallestVertexAttributesCount(cb_state);
if (!smallest_vertex_buffer_binding.has_value()) {
return;
}
CommandBuffer::ValidationCommandFunc validation_cmd =
[index_buffer_binding = cb_state.index_buffer_binding, smallest_vertex_buffer_binding = *smallest_vertex_buffer_binding,
draw_buffer, draw_buffer_offset, draw_cmds_byte_stride, draw_count, count_buffer, count_buffer_offset,
draw_i = cb_state.draw_index, error_logger_i = uint32_t(cb_state.per_command_error_loggers.size()),
loc](Validator &gpuav, CommandBuffer &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_manager.Get<SharedDrawValidationResources>(gpuav, loc);
if (!shared_draw_validation_resources.valid) return;
ComputeValidationPipeline<DrawIndexedIndirectVertexBufferShader> &validation_pipeline =
gpuav.shared_resources_manager.Get<ComputeValidationPipeline<DrawIndexedIndirectVertexBufferShader>>(
gpuav, loc, cb_state.GetErrorLoggingDescSetLayout());
if (!validation_pipeline.valid) return;
const uint32_t index_bits_size = GetIndexBitsSize(index_buffer_binding.index_type);
const uint32_t max_indices_in_buffer = static_cast<uint32_t>(index_buffer_binding.size / (index_bits_size / 8u));
{
DrawIndexedIndirectVertexBufferShader shader_resources;
if (count_buffer != VK_NULL_HANDLE) {
shader_resources.push_constants.flags |= glsl::kIndexedIndirectDrawFlags_DrawCountFromBuffer;
shader_resources.count_buffer_binding.info = {count_buffer, 0, sizeof(uint32_t)};
shader_resources.push_constants.count_buffer_dwords_offset = (uint32_t)count_buffer_offset / sizeof(uint32_t);
} else {
shader_resources.count_buffer_binding.info = {shared_draw_validation_resources.dummy_buffer.VkHandle(), 0,
VK_WHOLE_SIZE};
}
shader_resources.push_constants.index_width = index_bits_size;
shader_resources.push_constants.draw_cmds_stride_dwords = draw_cmds_byte_stride / sizeof(uint32_t);
shader_resources.push_constants.bound_index_buffer_indices_count = max_indices_in_buffer;
shader_resources.push_constants.cpu_draw_count = draw_count;
shader_resources.push_constants.smallest_vertex_attributes_count =
static_cast<uint32_t>(smallest_vertex_buffer_binding.smallest_vertex_attributes_count);
shader_resources.draw_buffer_binding.info = {draw_buffer, 0, VK_WHOLE_SIZE};
shader_resources.push_constants.draw_indexed_indirect_cmds_buffer_dwords_offset =
(uint32_t)draw_buffer_offset / sizeof(uint32_t);
shader_resources.index_buffer_binding.info = {index_buffer_binding.buffer, 0,
index_buffer_binding.offset + index_buffer_binding.size};
shader_resources.push_constants.index_buffer_dwords_offset =
(uint32_t)index_buffer_binding.offset / sizeof(uint32_t);
validation_pipeline.BindShaderResources(gpuav, cb_state, draw_i, error_logger_i, shader_resources);
}
{
DispatchCmdBindPipeline(cb_state.VkHandle(), VK_PIPELINE_BIND_POINT_COMPUTE, validation_pipeline.pipeline);
// Dispatch as many draws as there are indices in index buffer.
// Each shader invocation will inspect one index, for all draw commands.
// Shader is in charge of performing validation only for indices within the supplied ranges.
constexpr uint32_t work_group_size = 64;
const uint32_t indices_to_check = max_indices_in_buffer;
const uint32_t work_group_count =
indices_to_check / work_group_size + uint32_t(indices_to_check % work_group_size != 0);
VVL_TracyPlot("gpuav::valcmd::DrawIndexedIndirectVertexBuffer Dispatch size", int64_t(work_group_count));
DispatchCmdDispatch(cb_state.VkHandle(), work_group_count, 1, 1);
// synchronize draw buffer validation (read) against subsequent writes
std::array<VkBufferMemoryBarrier, 3> buffer_memory_barriers = {};
uint32_t buffer_memory_barriers_count = 2;
buffer_memory_barriers[0] = vku::InitStructHelper();
buffer_memory_barriers[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
buffer_memory_barriers[0].dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
buffer_memory_barriers[0].buffer = draw_buffer;
buffer_memory_barriers[0].offset = draw_buffer_offset;
buffer_memory_barriers[0].size = VK_WHOLE_SIZE;
buffer_memory_barriers[1] = vku::InitStructHelper();
buffer_memory_barriers[1].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
buffer_memory_barriers[1].dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
buffer_memory_barriers[1].buffer = index_buffer_binding.buffer;
buffer_memory_barriers[1].offset = index_buffer_binding.offset;
buffer_memory_barriers[1].size = index_buffer_binding.size;
if (count_buffer) {
buffer_memory_barriers[2] = vku::InitStructHelper();
buffer_memory_barriers[2].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
buffer_memory_barriers[2].dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
buffer_memory_barriers[2].buffer = count_buffer;
buffer_memory_barriers[2].offset = count_buffer_offset;
buffer_memory_barriers[2].size = sizeof(uint32_t);
++buffer_memory_barriers_count;
}
DispatchCmdPipelineBarrier(cb_state.VkHandle(), VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, buffer_memory_barriers_count,
buffer_memory_barriers.data(), 0, nullptr);
}
};
cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
const uint32_t label_command_i =
!cb_state.GetLabelCommands().empty() ? uint32_t(cb_state.GetLabelCommands().size() - 1) : vvl::kU32Max;
CommandBuffer::ErrorLoggerFunc error_logger =
[loc, vuid_oob_vertex, draw_buffer, smallest_vertex_buffer_binding = *smallest_vertex_buffer_binding,
index_buffer_binding = cb_state.index_buffer_binding,
label_command_i](Validator &gpuav, const CommandBuffer &cb_state, const uint32_t *error_record,
const LogObjectList &objlist, const std::vector<std::string> &initial_label_stack) {
bool skip = false;
using namespace glsl;
switch (error_record[kHeaderErrorSubCodeOffset]) {
case kErrorSubCode_OobVertexBuffer: {
const uint32_t draw_i = error_record[kPreActionParamOffset_0];
const uint32_t index_buffer_offset = error_record[kPreActionParamOffset_1];
const int32_t vertex_offset = static_cast<int32_t>(error_record[kPreActionParamOffset_2]);
const uint32_t vertex_index = error_record[kPreActionParamOffset_3];
const uint32_t index_buffer_value = static_cast<uint32_t>(int32_t(vertex_index) - vertex_offset);
const uint32_t index_bits_size = GetIndexBitsSize(index_buffer_binding.index_type);
const uint32_t max_indices_in_buffer =
static_cast<uint32_t>(index_buffer_binding.size / (index_bits_size / 8u));
std::string debug_region_name = cb_state.GetDebugLabelRegion(label_command_i, initial_label_stack);
Location loc_with_debug_region(loc, debug_region_name);
skip |= gpuav.LogError(
vuid_oob_vertex, objlist, loc_with_debug_region,
"Vertex index %" PRIu32
" is not within the smallest bound vertex buffer. Computed from VkDrawIndexedIndirectCommand[ %" PRIu32
" ], stored in %s.\n"
"index_buffer[ %" PRIu32 " ] (%" PRIu32 ") + vertexOffset (%" PRIi32 ") = Vertex index %" PRIu32
"\n"
"Smallest vertex buffer binding info, causing OOB access:\n"
"- Buffer: %s\n"
"- Binding: %" PRIu32
"\n"
"- Binding size (effective): %" PRIu64
" bytes\n"
"- Binding offset: %" PRIu64
" bytes\n"
"- Binding stride: %" PRIu64
" bytes\n"
"- Vertices count: %" PRIu64
"\n"
"At least the following vertex attribute caused OOB access:\n"
"- location: %" PRIu32
"\n"
"- binding: %" PRIu32
"\n"
"- format: %s\n"
"- offset: %" PRIu32
" bytes\n"
"Index buffer binding info:\n"
"- Buffer: %s\n"
"- Index type: %s\n"
"- Binding offset: %" PRIu64
" bytes\n"
"- Binding size: %" PRIu64 " bytes (or %" PRIu32
" %s)\n"
"Note: Vertex buffer binding size is the effective, valid one, based on how the VkBuffer was created and "
"vertex buffer binding parameters. So it can be clamped up to 0 if binding was invalid.\n",
// OOB vertex index info
vertex_index, draw_i, gpuav.FormatHandle(draw_buffer).c_str(), index_buffer_offset, index_buffer_value,
vertex_offset, vertex_index,
// Vertex buffer binding info
gpuav.FormatHandle(smallest_vertex_buffer_binding.binding_info.buffer).c_str(),
smallest_vertex_buffer_binding.binding, smallest_vertex_buffer_binding.binding_info.effective_size,
smallest_vertex_buffer_binding.binding_info.offset, smallest_vertex_buffer_binding.binding_info.stride,
smallest_vertex_buffer_binding.smallest_vertex_attributes_count,
// Vertex Attribute info
smallest_vertex_buffer_binding.attribute.location, smallest_vertex_buffer_binding.attribute.binding,
string_VkFormat(smallest_vertex_buffer_binding.attribute.format),
smallest_vertex_buffer_binding.attribute.offset,
// Index buffer binding info
gpuav.FormatHandle(index_buffer_binding.buffer).c_str(),
string_VkIndexType(index_buffer_binding.index_type), index_buffer_binding.offset, index_buffer_binding.size,
max_indices_in_buffer, string_VkIndexType(index_buffer_binding.index_type));
break;
}
default:
assert(false);
return skip;
}
return skip;
};
cb_state.per_command_error_loggers.emplace_back(std::move(error_logger));
}
} // namespace valcmd
} // namespace gpuav