/* 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;
    }
    // TODO https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/12657
    if (last_bound.GetDescriptorMode() == vvl::DescriptorModeBuffer || last_bound.GetDescriptorMode() == vvl::DescriptorModeHeap) {
        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->GetSize() > 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->GetSize() - 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,
                                          uint32_t stride, 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, stride, 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, uint32_t stride, 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, stride, 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;
    }
    // TODO https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/12657
    if (last_bound.GetDescriptorMode() == vvl::DescriptorModeBuffer || last_bound.GetDescriptorMode() == vvl::DescriptorModeHeap) {
        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->GetSize(), 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->GetSize(), 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;
    }
    // TODO https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/12657
    if (last_bound.GetDescriptorMode() == vvl::DescriptorModeBuffer || last_bound.GetDescriptorMode() == vvl::DescriptorModeHeap) {
        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->GetSize(), 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 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 = is_task_shader ? "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07322"
                                                  : "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07326";
                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, 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 = is_task_shader ? "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07323"
                                                  : "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07327";
                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, 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 = is_task_shader ? "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07324"
                                                  : "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07328";
                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, 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 = is_task_shader ? "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07325"
                                                  : "VUID-VkDrawMeshTasksIndirectCommandEXT-TaskEXT-07329";
                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, 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;
        }
    }
    // TODO https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/12657
    if (last_bound.GetDescriptorMode() == vvl::DescriptorModeBuffer || last_bound.GetDescriptorMode() == vvl::DescriptorModeHeap) {
        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 = IndexTypeByteSize(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;

                std::stringstream indirect_buffer_info;
                indirect_buffer_info << "VkDrawIndexedIndirectCommand buffer:\n";
                if (loc_with_debug_region.function == vvl::Func::vkCmdDrawIndexedIndirect2KHR) {
                    auto draw_buffer_state = gpuav.Get<vvl::Buffer>(api_buffer);
                    if (!draw_buffer_state) {
                        gpuav.InternalError(api_buffer, loc_with_debug_region,
                                            "Could not retrieve buffer state during error logging - most likely a GPU-AV bug.");
                        return skip;
                    }

                    indirect_buffer_info << "- Offset address: 0x" << std::hex << draw_buffer_state->deviceAddress + api_offset
                                         << '\n';
                } else {
                    indirect_buffer_info << "- Buffer: " << gpuav.FormatHandle(api_buffer) << '\n';
                    indirect_buffer_info << "- Buffer offset: " << api_offset << '\n';
                }
                indirect_buffer_info << "- Stride: " << api_stride << '\n';

                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"

                                       "%s\n"

                                       "%s\n",

                                       // OOB index info
                                       highest_accessed_index, draw_i, first_index, index_count,

                                       // Draw parameters buffer
                                       indirect_buffer_info.str().c_str(),

                                       // Index buffer binding info
                                       index_buffer_binding.String(*gpuav.device_state).c_str());
                break;
            }

            default:
                assert(false);
                return skip;
        }

        return skip;
    };

    cb_state.AddCommandErrorLogger(loc, &last_bound, std::move(error_logger));
}

}  // namespace valcmd
}  // namespace gpuav
