blob: 3e18209fa26909007106d4e25d01ab19b8741af1 [file] [log] [blame]
/* Copyright (c) 2018-2026 The Khronos Group Inc.
* Copyright (c) 2018-2026 Valve Corporation
* Copyright (c) 2018-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/validation_cmd/gpuav_draw.h"
#include <vulkan/vulkan_core.h>
#include "gpuav/core/gpuav.h"
#include "gpuav/core/gpuav_validation_pipeline.h"
#include "gpuav/validation_cmd/gpuav_validation_cmd_common.h"
#include "gpuav/resources/gpuav_vulkan_objects.h"
#include "gpuav/resources/gpuav_state_trackers.h"
#include "gpuav/shaders/gpuav_error_header.h"
#include "state_tracker/pipeline_state.h"
#include "gpuav/shaders/gpuav_error_header.h"
#include "gpuav/shaders/validation_cmd/push_data.h"
#include "generated/gpuav_offline_spirv.h"
namespace gpuav {
namespace valcmd {
using ErrorLoggerFunc = CommandBufferSubState::ErrorLoggerFunc;
struct SharedDrawValidationResources {
vko::Buffer dummy_buffer; // Used to fill unused buffer bindings in validation pipelines
bool valid = false;
SharedDrawValidationResources(Validator &gpuav) : 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 = {};
alloc_info.usage = VMA_MEMORY_USAGE_AUTO;
if (gpuav.IsAllDeviceLocalMappable()) {
alloc_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
}
const VkResult result = dummy_buffer.Create(&dummy_buffer_info, &alloc_info);
if (result != VK_SUCCESS) {
valid = false;
return;
}
valid = true;
}
~SharedDrawValidationResources() { dummy_buffer.Destroy(); }
};
using ValidationCommandFunc = stdext::inplace_function<void(Validator &gpuav, CommandBufferSubState &cb_state), 192>;
struct ValidationCmdCbState {
std::vector<ValidationCommandFunc> per_render_pass_validation_commands;
};
void FlushValidationCmds(Validator &gpuav, CommandBufferSubState &cb_state) {
ValidationCmdCbState *val_cmd_cb_state = cb_state.shared_resources_cache.TryGet<ValidationCmdCbState>();
if (!val_cmd_cb_state) {
return;
}
valpipe::RestorablePipelineState restorable_state(cb_state, VK_PIPELINE_BIND_POINT_COMPUTE);
for (auto &validation_cmd : val_cmd_cb_state->per_render_pass_validation_commands) {
validation_cmd(gpuav, cb_state);
}
val_cmd_cb_state->per_render_pass_validation_commands.clear();
}
struct FirstInstanceValidationShader {
static size_t GetSpirvSize() { return validation_cmd_first_instance_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return validation_cmd_first_instance_comp; }
glsl::FirstInstancePushData push_constants{};
valpipe::BoundStorageBuffer draw_buffer_binding = {glsl::kPreDrawBinding_IndirectBuffer};
valpipe::BoundStorageBuffer count_buffer_binding = {glsl::kPreDrawBinding_CountBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
return {{glsl::kPreDrawBinding_IndirectBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
{glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}};
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites() const {
std::vector<VkWriteDescriptorSet> desc_writes(2);
desc_writes[0] = vku::InitStructHelper();
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].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, CommandBufferSubState &cb_state, const Location &loc, const LastBound &last_bound,
VkBuffer api_buffer, VkDeviceSize api_offset, uint32_t api_stride, vvl::Struct api_struct_name,
uint32_t first_instance_member_pos, uint32_t api_draw_count, VkBuffer api_count_buffer,
VkDeviceSize api_count_buffer_offset, const FirstInstanceValidationVuidSelector &vuid_selector) {
if (!gpuav.gpuav_settings.validate_indirect_draws_buffers) {
return;
}
const char* vuid = vuid_selector(gpuav, last_bound);
if (!vuid) {
return; // No reason to validate, there is no possible VUID to report
}
ValidationCommandFunc validation_cmd = [api_buffer, api_offset, api_stride, first_instance_member_pos, api_draw_count,
api_count_buffer, api_count_buffer_offset, draw_i = cb_state.draw_index,
error_logger_i = cb_state.GetErrorLoggerIndex(),
loc](Validator &gpuav, CommandBufferSubState &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_cache.GetOrCreate<SharedDrawValidationResources>(gpuav);
if (!shared_draw_validation_resources.valid) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to create SharedDrawValidationResources.");
return;
}
ValidationCommandsGpuavState &val_cmd_gpuav_state =
gpuav.shared_resources_cache.GetOrCreate<ValidationCommandsGpuavState>(gpuav, loc);
valpipe::ComputePipeline<FirstInstanceValidationShader> &validation_pipeline =
gpuav.shared_resources_cache.GetOrCreate<valpipe::ComputePipeline<FirstInstanceValidationShader>>(
gpuav, loc, val_cmd_gpuav_state.error_logging_desc_set_layout_);
if (!validation_pipeline.valid) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to create FirstInstanceValidationShader.");
return;
}
auto draw_buffer_state = gpuav.Get<vvl::Buffer>(api_buffer);
if (!draw_buffer_state) {
gpuav.InternalError(LogObjectList(cb_state.VkHandle(), api_buffer), loc, "buffer must be a valid VkBuffer handle");
return;
}
// Setup shader resources
// ---
{
FirstInstanceValidationShader shader_resources;
shader_resources.push_constants.api_stride_dwords = api_stride / sizeof(uint32_t);
shader_resources.push_constants.api_draw_count = api_draw_count;
shader_resources.push_constants.first_instance_member_pos = first_instance_member_pos;
shader_resources.draw_buffer_binding.info = {api_buffer, 0, VK_WHOLE_SIZE};
shader_resources.push_constants.api_offset_dwords = (uint32_t)api_offset / sizeof(uint32_t);
if (api_count_buffer) {
shader_resources.push_constants.flags |= glsl::kFirstInstanceFlags_DrawCountFromBuffer;
shader_resources.count_buffer_binding.info = {api_count_buffer, 0, sizeof(uint32_t)};
shader_resources.push_constants.api_count_buffer_offset_dwords =
uint32_t(api_count_buffer_offset / sizeof(uint32_t));
} else {
shader_resources.count_buffer_binding.info = {shared_draw_validation_resources.dummy_buffer.VkHandle(), 0,
VK_WHOLE_SIZE};
}
if (!BindShaderResources(validation_pipeline, gpuav, cb_state, draw_i, error_logger_i, shader_resources)) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to GetManagedDescriptorSet in BindShaderResources");
return;
}
}
// 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 > api_offset) {
// If drawCount is less than or equal to one, stride is ignored
if (api_draw_count > 1) {
max_held_draw_cmds = static_cast<uint32_t>((draw_buffer_state->create_info.size - api_offset) / api_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(api_draw_count, max_held_draw_cmds);
if (work_group_count == 0) {
return;
}
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 = api_buffer;
buffer_memory_barriers[0].offset = api_offset;
buffer_memory_barriers[0].size = work_group_count * sizeof(uint32_t);
if (api_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 = api_count_buffer;
buffer_memory_barriers[1].offset = api_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);
}
};
ValidationCmdCbState &val_cmd_cb_state = cb_state.shared_resources_cache.GetOrCreate<ValidationCmdCbState>();
val_cmd_cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
// Register error logger. Happens per command GPU-AV intercepts
// ---
ErrorLoggerFunc error_logger = [&gpuav, vuid, api_struct_name](const uint32_t *error_record,
const Location &loc_with_debug_region,
const LogObjectList &objlist) {
bool skip = false;
using namespace glsl;
if (GetErrorGroup(error_record) != kErrorGroup_GpuPreDraw) {
return skip;
}
assert(GetSubError(error_record) == kErrorSubCode_PreDraw_FirstInstance);
const uint32_t index = error_record[kValCmd_ErrorPayloadDword_0];
const uint32_t invalid_first_instance = error_record[kValCmd_ErrorPayloadDword_1];
skip |= gpuav.LogError(vuid, objlist, loc_with_debug_region,
"The firstInstance member of the %s structure at "
"index %" PRIu32 " is %" PRIu32 ".",
vvl::String(api_struct_name), index, invalid_first_instance);
return skip;
};
cb_state.AddCommandErrorLogger(loc, &last_bound, std::move(error_logger));
}
struct FirstInstanceVUIDs {
const char *vuid_09461{};
const char *vuid_09462{};
const char *vuid_00501_00554{};
};
static FirstInstanceValidationVuidSelector GetFirstInstanceValidationVuidSelector(const FirstInstanceVUIDs &first_instance_vuids) {
FirstInstanceValidationVuidSelector vuid_selector = [first_instance_vuids](Validator& gpuav, const LastBound& last_bound) {
if (!gpuav.phys_dev_props_core14.supportsNonZeroFirstInstance) {
if (last_bound.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT)) {
for (const auto& [binding, binding_state] : last_bound.cb_state.dynamic_state_value.vertex_bindings) {
if (binding_state.desc.divisor != 1) {
return first_instance_vuids.vuid_09462;
}
}
} else if (last_bound.pipeline_state && last_bound.pipeline_state->GraphicsCreateInfo().pVertexInputState) {
if (auto divisor_state_ci = vku::FindStructInPNextChain<VkPipelineVertexInputDivisorStateCreateInfo>(
last_bound.pipeline_state->GraphicsCreateInfo().pVertexInputState->pNext)) {
for (const VkVertexInputBindingDivisorDescription& divisor :
vvl::make_span(divisor_state_ci->pVertexBindingDivisors, divisor_state_ci->vertexBindingDivisorCount)) {
if (divisor.divisor != 1u) {
return first_instance_vuids.vuid_09461;
}
}
}
}
}
if (!gpuav.enabled_features.drawIndirectFirstInstance) {
return first_instance_vuids.vuid_00501_00554;
}
// Return null to show |firstInstance| could be anything and it will still be valid
return (const char *)nullptr;
};
return vuid_selector;
}
template <>
void FirstInstance<VkDrawIndirectCommand>(Validator &gpuav, CommandBufferSubState &cb_state, const Location &loc,
const LastBound &last_bound, VkBuffer buffer, VkDeviceSize offset, uint32_t draw_count,
VkBuffer count_buffer, VkDeviceSize count_buffer_offset) {
FirstInstanceVUIDs vuids;
vuids.vuid_09461 = "VUID-VkDrawIndirectCommand-pNext-09461";
vuids.vuid_09462 = "VUID-VkDrawIndirectCommand-None-09462";
vuids.vuid_00501_00554 = "VUID-VkDrawIndirectCommand-firstInstance-00501";
FirstInstanceValidationVuidSelector vuid_selector = GetFirstInstanceValidationVuidSelector(vuids);
FirstInstance(gpuav, cb_state, loc, last_bound, buffer, offset, sizeof(VkDrawIndirectCommand),
vvl::Struct::VkDrawIndirectCommand, 3, draw_count, count_buffer, count_buffer_offset, vuid_selector);
}
template <>
void FirstInstance<VkDrawIndexedIndirectCommand>(Validator &gpuav, CommandBufferSubState &cb_state, const Location &loc,
const LastBound &last_bound, VkBuffer buffer, VkDeviceSize offset,
uint32_t draw_count, VkBuffer count_buffer, VkDeviceSize count_buffer_offset) {
FirstInstanceVUIDs vuids;
vuids.vuid_09461 = "VUID-VkDrawIndexedIndirectCommand-pNext-09461";
vuids.vuid_09462 = "VUID-VkDrawIndexedIndirectCommand-None-09462";
vuids.vuid_00501_00554 = "VUID-VkDrawIndexedIndirectCommand-firstInstance-00554";
FirstInstanceValidationVuidSelector vuid_selector = GetFirstInstanceValidationVuidSelector(vuids);
FirstInstance(gpuav, cb_state, loc, last_bound, buffer, offset, sizeof(VkDrawIndexedIndirectCommand),
vvl::Struct::VkDrawIndexedIndirectCommand, 4, draw_count, count_buffer, count_buffer_offset, vuid_selector);
}
struct CountBufferValidationShader {
static size_t GetSpirvSize() { return validation_cmd_count_buffer_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return validation_cmd_count_buffer_comp; }
glsl::CountBufferPushData push_constants{};
valpipe::BoundStorageBuffer count_buffer_binding = {glsl::kPreDrawBinding_CountBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
return {{glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}};
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites() const {
std::vector<VkWriteDescriptorSet> desc_writes(1);
desc_writes[0] = vku::InitStructHelper();
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, CommandBufferSubState &cb_state, const Location &loc, const LastBound &last_bound,
VkBuffer api_buffer, VkDeviceSize api_offset, uint32_t api_struct_size_byte, vvl::Struct api_struct_name,
uint32_t api_stride, VkBuffer api_count_buffer, VkDeviceSize api_count_buffer_offset, const char *vuid) {
if (!gpuav.gpuav_settings.validate_indirect_draws_buffers) {
return;
}
auto draw_buffer_state = gpuav.Get<vvl::Buffer>(api_buffer);
if (!draw_buffer_state) {
gpuav.InternalError(LogObjectList(cb_state.VkHandle(), api_buffer), loc, "buffer must be a valid VkBuffer handle");
return;
}
ValidationCommandFunc validation_cmd = [draw_buffer_size = draw_buffer_state->create_info.size, api_offset,
api_struct_size_byte, api_stride, api_count_buffer, api_count_buffer_offset,
draw_i = cb_state.draw_index, error_logger_i = cb_state.GetErrorLoggerIndex(),
loc](Validator &gpuav, CommandBufferSubState &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_cache.GetOrCreate<SharedDrawValidationResources>(gpuav);
if (!shared_draw_validation_resources.valid) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to create SharedDrawValidationResources.");
return;
}
ValidationCommandsGpuavState &val_cmd_gpuav_state =
gpuav.shared_resources_cache.GetOrCreate<ValidationCommandsGpuavState>(gpuav, loc);
valpipe::ComputePipeline<CountBufferValidationShader> &validation_pipeline =
gpuav.shared_resources_cache.GetOrCreate<valpipe::ComputePipeline<CountBufferValidationShader>>(
gpuav, loc, val_cmd_gpuav_state.error_logging_desc_set_layout_);
if (!validation_pipeline.valid) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to create CountBufferValidationShader.");
return;
}
// Setup shader resources
// ---
{
CountBufferValidationShader shader_resources;
shader_resources.push_constants.api_stride = api_stride;
shader_resources.push_constants.api_offset = api_offset;
shader_resources.push_constants.draw_buffer_size = draw_buffer_size;
shader_resources.push_constants.api_struct_size_byte = api_struct_size_byte;
shader_resources.push_constants.device_limit_max_draw_indirect_count = gpuav.phys_dev_props.limits.maxDrawIndirectCount;
shader_resources.count_buffer_binding.info = {api_count_buffer, 0, sizeof(uint32_t)};
shader_resources.push_constants.api_count_buffer_offset_dwords = uint32_t(api_count_buffer_offset / sizeof(uint32_t));
if (!BindShaderResources(validation_pipeline, gpuav, cb_state, draw_i, error_logger_i, shader_resources)) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to GetManagedDescriptorSet in BindShaderResources");
return;
}
}
// 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 = api_count_buffer;
count_buffer_memory_barrier.offset = api_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);
}
};
ValidationCmdCbState &val_cmd_cb_state = cb_state.shared_resources_cache.GetOrCreate<ValidationCmdCbState>();
val_cmd_cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
// Register error logger
// ---
ErrorLoggerFunc error_logger = [&gpuav, api_buffer, draw_buffer_size = draw_buffer_state->create_info.size, api_offset,
api_struct_size_byte, api_stride, api_struct_name,
vuid](const uint32_t *error_record, const Location &loc_with_debug_region,
const LogObjectList &objlist) {
bool skip = false;
using namespace glsl;
const uint32_t error_sub_code = GetSubError(error_record);
switch (error_sub_code) {
case kErrorSubCode_PreDraw_DrawBufferSize: {
const uint32_t count = error_record[kValCmd_ErrorPayloadDword_0];
const VkDeviceSize draw_size = (api_stride * (count - 1) + api_offset + api_struct_size_byte);
// 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(api_buffer).c_str(), api_stride, api_offset,
vvl::String(api_struct_name), draw_size);
break;
}
case kErrorSubCode_PreDraw_DrawCountLimit: {
const uint32_t count = error_record[kValCmd_ErrorPayloadDword_0];
skip |= gpuav.LogError(vuid, 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.AddCommandErrorLogger(loc, &last_bound, std::move(error_logger));
}
struct MeshValidationShader {
static size_t GetSpirvSize() { return validation_cmd_draw_mesh_indirect_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return validation_cmd_draw_mesh_indirect_comp; }
glsl::DrawMeshPushData push_constants{};
valpipe::BoundStorageBuffer draw_buffer_binding = {glsl::kPreDrawBinding_IndirectBuffer};
valpipe::BoundStorageBuffer count_buffer_binding = {glsl::kPreDrawBinding_CountBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
return {{glsl::kPreDrawBinding_IndirectBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
{glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}};
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites() const {
std::vector<VkWriteDescriptorSet> desc_writes(2);
desc_writes[0] = vku::InitStructHelper();
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].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, CommandBufferSubState &cb_state, const Location &loc, const LastBound &last_bound,
VkBuffer api_buffer, VkDeviceSize api_offset, uint32_t api_stride, VkBuffer api_count_buffer,
VkDeviceSize api_count_buffer_offset, uint32_t api_draw_count) {
if (!gpuav.gpuav_settings.validate_indirect_draws_buffers) {
return;
}
auto draw_buffer_state = gpuav.Get<vvl::Buffer>(api_buffer);
if (!draw_buffer_state) {
gpuav.InternalError(LogObjectList(cb_state.VkHandle(), api_buffer), loc, "buffer must be a valid VkBuffer handle");
return;
}
const VkShaderStageFlags stages = last_bound.GetAllActiveBoundStages();
const bool is_task_shader = (stages & VK_SHADER_STAGE_TASK_BIT_EXT) == VK_SHADER_STAGE_TASK_BIT_EXT;
ValidationCommandFunc validation_cmd =
[api_buffer, draw_buffer_full_size = draw_buffer_state->create_info.size, api_offset, api_stride, api_count_buffer,
api_count_buffer_offset, api_draw_count, is_task_shader, draw_i = cb_state.draw_index,
error_logger_i = cb_state.GetErrorLoggerIndex(), loc](Validator &gpuav, CommandBufferSubState &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_cache.GetOrCreate<SharedDrawValidationResources>(gpuav);
if (!shared_draw_validation_resources.valid) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to create SharedDrawValidationResources.");
return;
}
ValidationCommandsGpuavState &val_cmd_gpuav_state =
gpuav.shared_resources_cache.GetOrCreate<ValidationCommandsGpuavState>(gpuav, loc);
valpipe::ComputePipeline<MeshValidationShader> &validation_pipeline =
gpuav.shared_resources_cache.GetOrCreate<valpipe::ComputePipeline<MeshValidationShader>>(
gpuav, loc, val_cmd_gpuav_state.error_logging_desc_set_layout_);
if (!validation_pipeline.valid) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to create MeshValidationShader.");
return;
}
// Setup shader resources
// ---
{
MeshValidationShader shader_resources;
shader_resources.push_constants.api_stride_dwords = api_stride / sizeof(uint32_t);
shader_resources.push_constants.api_draw_count = api_draw_count;
const auto &properties = gpuav.phys_dev_ext_props.mesh_shader_props_ext;
if (is_task_shader) {
shader_resources.push_constants.max_workgroup_count_x = properties.maxTaskWorkGroupCount[0];
shader_resources.push_constants.max_workgroup_count_y = properties.maxTaskWorkGroupCount[1];
shader_resources.push_constants.max_workgroup_count_z = properties.maxTaskWorkGroupCount[2];
shader_resources.push_constants.max_workgroup_total_count = properties.maxTaskWorkGroupTotalCount;
} else {
shader_resources.push_constants.max_workgroup_count_x = properties.maxMeshWorkGroupCount[0];
shader_resources.push_constants.max_workgroup_count_y = properties.maxMeshWorkGroupCount[1];
shader_resources.push_constants.max_workgroup_count_z = properties.maxMeshWorkGroupCount[2];
shader_resources.push_constants.max_workgroup_total_count = properties.maxMeshWorkGroupTotalCount;
}
shader_resources.draw_buffer_binding.info = {api_buffer, 0, VK_WHOLE_SIZE};
shader_resources.push_constants.api_offset_dwords = uint32_t(api_offset / sizeof(uint32_t));
if (api_count_buffer != VK_NULL_HANDLE) {
shader_resources.push_constants.flags |= glsl::kDrawMeshFlags_DrawCountFromBuffer;
shader_resources.count_buffer_binding.info = {api_count_buffer, 0, sizeof(uint32_t)};
shader_resources.push_constants.api_count_buffer_offset_dwords =
uint32_t(api_count_buffer_offset / sizeof(uint32_t));
} else {
shader_resources.count_buffer_binding.info = {shared_draw_validation_resources.dummy_buffer.VkHandle(), 0,
VK_WHOLE_SIZE};
}
if (!BindShaderResources(validation_pipeline, gpuav, cb_state, draw_i, error_logger_i, shader_resources)) {
return;
}
}
// 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 > api_offset) {
// If drawCount is less than or equal to one, stride is ignored
if (api_draw_count > 1) {
max_held_draw_cmds = static_cast<uint32_t>((draw_buffer_full_size - api_offset) / api_stride);
} else {
max_held_draw_cmds = 1;
}
}
const uint32_t work_group_count = std::min(api_draw_count, max_held_draw_cmds);
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 = api_buffer;
buffer_memory_barriers[0].offset = api_offset;
buffer_memory_barriers[0].size = work_group_count * sizeof(uint32_t);
if (api_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 = api_count_buffer;
buffer_memory_barriers[1].offset = api_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);
}
};
ValidationCmdCbState &val_cmd_cb_state = cb_state.shared_resources_cache.GetOrCreate<ValidationCmdCbState>();
val_cmd_cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
// Register error logger
// ---
ErrorLoggerFunc error_logger = [&gpuav, is_task_shader](const uint32_t *error_record, const Location &loc_with_debug_region,
const LogObjectList &objlist) {
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[kValCmd_ErrorPayloadDword_1];
const char *group_count_name = is_task_shader ? "maxTaskWorkGroupCount" : "maxMeshWorkGroupCount";
const char *group_count_total_name = is_task_shader ? "maxTaskWorkGroupTotalCount" : "maxMeshWorkGroupTotalCount";
const uint32_t error_sub_code = GetSubError(error_record);
switch (error_sub_code) {
case kErrorSubCode_PreDraw_GroupCountX: {
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[kValCmd_ErrorPayloadDword_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 kErrorSubCode_PreDraw_GroupCountY: {
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[kValCmd_ErrorPayloadDword_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 kErrorSubCode_PreDraw_GroupCountZ: {
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[kValCmd_ErrorPayloadDword_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 kErrorSubCode_PreDraw_GroupCountTotal: {
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[kValCmd_ErrorPayloadDword_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.AddCommandErrorLogger(loc, &last_bound, std::move(error_logger));
}
struct DrawIndexedIndirectIndexBufferShader {
static size_t GetSpirvSize() { return validation_cmd_draw_indexed_indirect_index_buffer_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return validation_cmd_draw_indexed_indirect_index_buffer_comp; }
glsl::DrawIndexedIndirectIndexBufferPushData push_constants{};
valpipe::BoundStorageBuffer draw_buffer_binding = {glsl::kPreDrawBinding_IndirectBuffer};
valpipe::BoundStorageBuffer count_buffer_binding = {glsl::kPreDrawBinding_CountBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
return {{glsl::kPreDrawBinding_IndirectBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
{glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
{glsl::kPreDrawBinding_IndexBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}};
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites() const {
std::vector<VkWriteDescriptorSet> desc_writes(2);
desc_writes[0] = vku::InitStructHelper();
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].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;
}
};
struct SetupDrawCountDispatchIndirectShader {
static size_t GetSpirvSize() { return validation_cmd_setup_draw_indexed_indirect_index_buffer_comp_size * sizeof(uint32_t); }
static const uint32_t *GetSpirv() { return validation_cmd_setup_draw_indexed_indirect_index_buffer_comp; }
glsl::DrawIndexedIndirectIndexBufferPushData push_constants{};
valpipe::BoundStorageBuffer count_buffer_binding = {glsl::kPreDrawBinding_CountBuffer};
valpipe::BoundStorageBuffer dispatch_indirect_buffer_binding = {glsl::kPreDrawBinding_DispatchIndirectBuffer};
static std::vector<VkDescriptorSetLayoutBinding> GetDescriptorSetLayoutBindings() {
std::vector<VkDescriptorSetLayoutBinding> bindings = {
{glsl::kPreDrawBinding_CountBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
{glsl::kPreDrawBinding_DispatchIndirectBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr},
};
return bindings;
}
std::vector<VkWriteDescriptorSet> GetDescriptorWrites() const {
std::vector<VkWriteDescriptorSet> desc_writes(2);
desc_writes[0] = vku::InitStructHelper();
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;
desc_writes[1] = vku::InitStructHelper();
desc_writes[1].dstBinding = dispatch_indirect_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 = &dispatch_indirect_buffer_binding.info;
return desc_writes;
}
};
// Use "api_" prefix to make it clear which buffer/offset/etc we are talking about
// "api" helps to distinguish it is input from the user at the API level
void DrawIndexedIndirectIndexBuffer(Validator &gpuav, CommandBufferSubState &cb_state, const Location &loc,
const LastBound &last_bound, VkBuffer api_buffer, VkDeviceSize api_offset, uint32_t api_stride,
uint32_t api_draw_count, VkBuffer api_count_buffer, VkDeviceSize api_count_buffer_offset,
const char *vuid) {
if (!gpuav.gpuav_settings.validate_index_buffers) {
return;
}
if (gpuav.modified_features.robustBufferAccess2) {
return;
}
if (gpuav.enabled_features.pipelineRobustness && last_bound.pipeline_state) {
const auto robustness_ci =
vku::FindStructInPNextChain<VkPipelineRobustnessCreateInfo>(last_bound.pipeline_state->GraphicsCreateInfo().pNext);
if (robustness_ci && robustness_ci->vertexInputs) {
return;
}
}
if (!cb_state.base.IsPrimary()) {
// TODO Unhandled for now. Potential issues with accessing the right vertex buffers
// in secondary command buffers
return;
}
if (!cb_state.base.index_buffer_binding.HasNonNullBuffer()) {
return;
}
ValidationCommandFunc validation_cmd = [index_buffer_binding = cb_state.base.index_buffer_binding, api_buffer, api_offset,
api_stride, api_draw_count, api_count_buffer, api_count_buffer_offset,
draw_i = cb_state.draw_index, error_logger_i = cb_state.GetErrorLoggerIndex(),
loc](Validator &gpuav, CommandBufferSubState &cb_state) {
SharedDrawValidationResources &shared_draw_validation_resources =
gpuav.shared_resources_cache.GetOrCreate<SharedDrawValidationResources>(gpuav);
if (!shared_draw_validation_resources.valid) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to create SharedDrawValidationResources.");
return;
}
valpipe::ComputePipeline<SetupDrawCountDispatchIndirectShader> &setup_validation_dispatch_pipeline =
gpuav.shared_resources_cache.GetOrCreate<valpipe::ComputePipeline<SetupDrawCountDispatchIndirectShader>>(gpuav, loc);
if (!setup_validation_dispatch_pipeline.valid) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to create SetupDrawCountDispatchIndirectShader.");
return;
}
ValidationCommandsGpuavState &val_cmd_gpuav_state =
gpuav.shared_resources_cache.GetOrCreate<ValidationCommandsGpuavState>(gpuav, loc);
valpipe::ComputePipeline<DrawIndexedIndirectIndexBufferShader> &validation_pipeline =
gpuav.shared_resources_cache.GetOrCreate<valpipe::ComputePipeline<DrawIndexedIndirectIndexBufferShader>>(
gpuav, loc, val_cmd_gpuav_state.error_logging_desc_set_layout_);
if (!validation_pipeline.valid) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to create DrawIndexedIndirectIndexBufferShader.");
return;
}
const uint32_t index_byte_size = IndexTypeSize(index_buffer_binding.index_type);
const uint32_t max_indices_in_buffer = static_cast<uint32_t>(index_buffer_binding.size / index_byte_size);
vko::BufferRange validation_dispatch_params_buffer_range =
cb_state.gpu_resources_manager.GetDeviceLocalIndirectBufferRange(3 * sizeof(uint32_t));
glsl::DrawIndexedIndirectIndexBufferPushData push_constants{};
if (api_count_buffer != VK_NULL_HANDLE) {
push_constants.flags |= glsl::kIndexedIndirectDrawFlags_DrawCountFromBuffer;
push_constants.api_count_buffer_offset_dwords = uint32_t(api_count_buffer_offset / sizeof(uint32_t));
}
push_constants.api_stride_dwords = api_stride / sizeof(uint32_t);
push_constants.bound_index_buffer_indices_count = max_indices_in_buffer;
push_constants.api_draw_count = api_draw_count;
push_constants.api_offset_dwords = uint32_t(api_offset / sizeof(uint32_t));
// Draw count being stored in a GPU buffer,
// setup a compute pipeline to determine the size of the validation indirect dispatch
{
SetupDrawCountDispatchIndirectShader setup_validation_shader_resources;
setup_validation_shader_resources.push_constants = push_constants;
if (api_count_buffer != VK_NULL_HANDLE) {
setup_validation_shader_resources.count_buffer_binding.info = {api_count_buffer, 0, sizeof(uint32_t)};
} else {
setup_validation_shader_resources.count_buffer_binding.info = {
shared_draw_validation_resources.dummy_buffer.VkHandle(), 0, VK_WHOLE_SIZE};
}
setup_validation_shader_resources.dispatch_indirect_buffer_binding.info =
validation_dispatch_params_buffer_range.GetDescriptorBufferInfo();
if (!setup_validation_dispatch_pipeline.BindShaderResources(gpuav, cb_state, setup_validation_shader_resources)) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to GetManagedDescriptorSet in BindShaderResources");
return;
}
DispatchCmdBindPipeline(cb_state.VkHandle(), VK_PIPELINE_BIND_POINT_COMPUTE,
setup_validation_dispatch_pipeline.pipeline);
// Sync indirect buffer writes - the same command buffer could be executed concurrently
// for all we know
{
VkBufferMemoryBarrier barrier_write_after_read = vku::InitStructHelper();
barrier_write_after_read.srcAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
barrier_write_after_read.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
barrier_write_after_read.buffer = validation_dispatch_params_buffer_range.buffer;
barrier_write_after_read.offset = validation_dispatch_params_buffer_range.offset;
barrier_write_after_read.size = validation_dispatch_params_buffer_range.size;
DispatchCmdPipelineBarrier(cb_state.VkHandle(), VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 1, &barrier_write_after_read, 0,
nullptr);
}
DispatchCmdDispatch(cb_state.VkHandle(), 1, 1, 1);
// Sync indirect buffer reads
{
VkBufferMemoryBarrier barrier_read_after_write = vku::InitStructHelper();
barrier_read_after_write.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
barrier_read_after_write.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
barrier_read_after_write.buffer = validation_dispatch_params_buffer_range.buffer;
barrier_read_after_write.offset = validation_dispatch_params_buffer_range.offset;
barrier_read_after_write.size = validation_dispatch_params_buffer_range.size;
DispatchCmdPipelineBarrier(cb_state.VkHandle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, 0, 0, nullptr, 1, &barrier_read_after_write, 0,
nullptr);
}
}
// Setup validation pipeline
{
DrawIndexedIndirectIndexBufferShader validation_shader_resources;
validation_shader_resources.push_constants = push_constants;
if (api_count_buffer != VK_NULL_HANDLE) {
validation_shader_resources.count_buffer_binding.info = {api_count_buffer, 0, sizeof(uint32_t)};
} else {
validation_shader_resources.count_buffer_binding.info = {shared_draw_validation_resources.dummy_buffer.VkHandle(),
0, VK_WHOLE_SIZE};
}
validation_shader_resources.draw_buffer_binding.info = {api_buffer, 0, VK_WHOLE_SIZE};
if (!BindShaderResources(validation_pipeline, gpuav, cb_state, draw_i, error_logger_i, validation_shader_resources)) {
gpuav.InternalError(cb_state.VkHandle(), loc, "Failed to GetManagedDescriptorSet in BindShaderResources");
return;
}
DispatchCmdBindPipeline(cb_state.VkHandle(), VK_PIPELINE_BIND_POINT_COMPUTE, validation_pipeline.pipeline);
// One draw will check all VkDrawIndexedIndirectCommand
DispatchCmdDispatchIndirect(cb_state.VkHandle(), validation_dispatch_params_buffer_range.buffer,
validation_dispatch_params_buffer_range.offset);
// 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 = api_buffer;
buffer_memory_barriers[0].offset = api_offset;
buffer_memory_barriers[0].size = VK_WHOLE_SIZE;
if (api_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 = api_count_buffer;
buffer_memory_barriers[1].offset = api_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);
}
};
ValidationCmdCbState &val_cmd_cb_state = cb_state.shared_resources_cache.GetOrCreate<ValidationCmdCbState>();
val_cmd_cb_state.per_render_pass_validation_commands.emplace_back(std::move(validation_cmd));
ErrorLoggerFunc error_logger = [&gpuav, vuid, api_buffer, api_offset, api_stride,
index_buffer_binding = cb_state.base.index_buffer_binding](
const uint32_t *error_record, const Location &loc_with_debug_region,
const LogObjectList &objlist) {
bool skip = false;
using namespace glsl;
const uint32_t error_sub_code = GetSubError(error_record);
switch (error_sub_code) {
case kErrorSubCode_OobIndexBuffer: {
const uint32_t draw_i = error_record[kValCmd_ErrorPayloadDword_0];
const uint32_t first_index = error_record[kValCmd_ErrorPayloadDword_1];
const uint32_t index_count = error_record[kValCmd_ErrorPayloadDword_2];
const uint32_t highest_accessed_index = first_index + index_count;
const uint32_t index_byte_size = IndexTypeSize(index_buffer_binding.index_type);
assert(index_byte_size != 0); // Should never be VK_INDEX_TYPE_NONE_KHR
const uint32_t max_indices_in_buffer = static_cast<uint32_t>(index_buffer_binding.size / index_byte_size);
skip |= gpuav.LogError(
vuid, objlist, loc_with_debug_region,
"Index %" PRIu32 " is not within the bound index buffer. Computed from VkDrawIndexedIndirectCommand[%" PRIu32
"] (.firstIndex = %" PRIu32 ", .indexCount = %" PRIu32
")\n"
"VkDrawIndexedIndirectCommand buffer:\n"
"- Buffer: %s\n"
"- Buffer offset: %" PRIu64
"\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,
// Draw parameters buffer
gpuav.FormatHandle(api_buffer).c_str(), api_offset,
// Index buffer binding info
gpuav.FormatHandle(index_buffer_binding.Buffer()).c_str(), string_VkIndexType(index_buffer_binding.index_type),
index_buffer_binding.BufferOffset(), index_buffer_binding.size, max_indices_in_buffer,
string_VkIndexType(index_buffer_binding.index_type),
// VkDrawIndexedIndirectCommand info
api_offset, api_stride);
break;
}
default:
assert(false);
return skip;
}
return skip;
};
cb_state.AddCommandErrorLogger(loc, &last_bound, std::move(error_logger));
}
} // namespace valcmd
} // namespace gpuav