/* 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 <vulkan/vulkan_core.h>
#include "chassis/chassis_modification_state.h"
#include "gpuav/core/gpuav.h"
#include "gpuav/core/gpuav_constants.h"
#include "gpuav/debug_printf/debug_printf.h"
#include "gpuav/descriptor_validation/gpuav_descriptor_validation.h"
#include "gpuav/instrumentation/descriptor_checks_classic.h"
#include "gpuav/instrumentation/descriptor_checks_heap.h"
#include "gpuav/instrumentation/descriptor_checks_buffer.h"
#include "gpuav/instrumentation/gpuav_instrumentation.h"
#include "gpuav/instrumentation/register_validation.h"
#include "gpuav/resources/gpuav_state_trackers.h"
#include "gpuav/shaders/gpuav_shaders_constants.h"
#include "gpuav/validation_cmd/gpuav_copy_buffer_to_image.h"
#include "gpuav/validation_cmd/gpuav_copy_memory_indirect.h"
#include "gpuav/validation_cmd/gpuav_dispatch.h"
#include "gpuav/validation_cmd/gpuav_draw.h"
#include "gpuav/validation_cmd/gpuav_ray_tracing.h"
#include "utils/math_utils.h"

#include <cstdint>
#include <vulkan/utility/vk_safe_struct.hpp>

namespace gpuav {

void Validator::PreCallRecordCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo,
                                          const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer,
                                          const RecordObject& record_obj, chassis::CreateBuffer& chassis_state) {
    // init here so if using just CoreCheck we don't waste time
    chassis_state.modified_create_info.initialize(pCreateInfo);

    const auto* flags2 = vku::FindStructInPNextChain<VkBufferUsageFlags2CreateInfo>(chassis_state.modified_create_info.pNext);
    const VkBufferUsageFlags2 in_usage = flags2 ? flags2->usage : chassis_state.modified_create_info.usage;

    // Ray tracing acceleration structure instance buffers also need the storage buffer usage as
    // acceleration structure build validation will find and replace invalid acceleration structure
    // handles inside of a compute shader.
    if (in_usage & VK_BUFFER_USAGE_2_SHADER_BINDING_TABLE_BIT_KHR) {
        if (flags2) {
            const_cast<VkBufferUsageFlags2CreateInfo*>(flags2)->usage |= VK_BUFFER_USAGE_2_STORAGE_BUFFER_BIT;
        } else {
            chassis_state.modified_create_info.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
        }
    }

    // Indirect buffers will require validation shader to bind the indirect buffers as a storage buffer.
    if (gpuav_settings.IsBufferValidationEnabled() &&
        (in_usage & (VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_2_INDEX_BUFFER_BIT))) {
        if (flags2) {
            const_cast<VkBufferUsageFlags2CreateInfo*>(flags2)->usage |= VK_BUFFER_USAGE_2_STORAGE_BUFFER_BIT;
        } else {
            chassis_state.modified_create_info.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
        }
    }

    if (gpuav_settings.validate_acceleration_structures_builds &&
        (in_usage & VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR)) {
        if (flags2) {
            const_cast<VkBufferUsageFlags2CreateInfo*>(flags2)->usage |= VK_BUFFER_USAGE_2_TRANSFER_SRC_BIT;
        } else {
            chassis_state.modified_create_info.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
        }
    }

    // Align index buffer size to 4: validation shader reads DWORDS
    if (gpuav_settings.IsBufferValidationEnabled() && (in_usage & (VK_BUFFER_USAGE_2_INDEX_BUFFER_BIT))) {
        chassis_state.modified_create_info.size = Align<VkDeviceSize>(chassis_state.modified_create_info.size, 4);
    }

    chassis_state.create_info_copy = chassis_state.modified_create_info.ptr();
}

void Validator::PostCallRecordCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo,
                                           const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer,
                                           const RecordObject& record_obj) {
    const auto* flags2 = vku::FindStructInPNextChain<VkBufferUsageFlags2CreateInfo>(pCreateInfo->pNext);
    const VkBufferUsageFlags2 in_usage = flags2 ? flags2->usage : pCreateInfo->usage;

    if (in_usage & VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT) {
        descriptor_buffer.resource_handles_.emplace(*pBuffer);
        hint_descriptor_buffer_ = true;
    }
}

void Validator::PreCallRecordDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator,
                                           const RecordObject& record_obj) {
    descriptor_buffer.resource_handles_.erase(buffer);
}

void Validator::PreCallRecordFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator,
                                        const RecordObject& record_obj) {
    if (descriptor_buffer.resource_memory_handles_.find(memory) != descriptor_buffer.resource_memory_handles_.end()) {
        descriptor_buffer.resource_memory_handles_.erase(memory);
    }
}

void Validator::BindBufferMemory(VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize offset, const Location&) {
    if (descriptor_buffer.resource_handles_.find(buffer) != descriptor_buffer.resource_handles_.end()) {
        descriptor_buffer.resource_memory_handles_.emplace(memory);
    }
}

void Validator::PostCallRecordBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset,
                                               const RecordObject& record_obj) {
    BindBufferMemory(buffer, memory, memoryOffset, record_obj.location);
}

void Validator::PostCallRecordBindBufferMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos,
                                                const RecordObject& record_obj) {
    for (uint32_t i = 0; i < bindInfoCount; i++) {
        BindBufferMemory(pBindInfos->buffer, pBindInfos->memory, pBindInfos->memoryOffset, record_obj.location);
    }
}

void Validator::PostCallRecordBindBufferMemory2KHR(VkDevice device, uint32_t bindInfoCount,
                                                   const VkBindBufferMemoryInfo* pBindInfos, const RecordObject& record_obj) {
    PostCallRecordBindBufferMemory2(device, bindInfoCount, pBindInfos, record_obj);
}

void Validator::PreCallRecordCmdBindDescriptorBuffersEXT(VkCommandBuffer commandBuffer, uint32_t bufferCount,
                                                         const VkDescriptorBufferBindingInfoEXT* pBindingInfos,
                                                         const RecordObject& record_obj,
                                                         chassis::CmdBindDescriptorBuffers& chassis_state) {
    // Resize here so if using just CoreCheck we don't waste time allocating this
    chassis_state.modified_binding_infos.resize(bufferCount + 1);
    for (uint32_t i = 0; i < bufferCount; ++i) {
        vku::safe_VkDescriptorBufferBindingInfoEXT& new_bind_info = chassis_state.modified_binding_infos[i];
        new_bind_info.initialize(&pBindingInfos[i]);
    }
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);
    CommandBufferSubState& gpuav_cb_state = SubState(*cb_state);

    vku::safe_VkDescriptorBufferBindingInfoEXT& modified_binding_info = chassis_state.modified_binding_infos[bufferCount];
    modified_binding_info.usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT;
    modified_binding_info.address = gpuav_cb_state.GetInternalDescriptorBuffer().Address();

    gpuav_cb_state.resource_descriptor_buffer_index_ = bufferCount;

    // Set the pointer the chassis will use
    chassis_state.pBindInfos = reinterpret_cast<VkDescriptorBufferBindingInfoEXT*>(chassis_state.modified_binding_infos.data());
}

void Validator::PostCallRecordGetDescriptorEXT(VkDevice device, const VkDescriptorGetInfoEXT* pDescriptorInfo, size_t dataSize,
                                               void* pDescriptor, const RecordObject& record_obj) {
    hint_descriptor_buffer_ = true;
}

void Validator::PreCallRecordBeginCommandBuffer(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo,
                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    CommandBufferSubState& gpuav_cb_state = SubState(*cb_state);
    RegisterDescriptorChecksClassicValidation(*this, gpuav_cb_state);
    RegisterDescriptorChecksBufferValidation(*this, gpuav_cb_state);
    RegisterDescriptorChecksHeapValidation(*this, gpuav_cb_state);
    RegisterPostProcessingValidation(*this, gpuav_cb_state);
    RegisterBufferDeviceAddressValidation(*this, gpuav_cb_state);
    RegisterVertexAttributeFetchOobValidation(*this, gpuav_cb_state);
    RegisterMeshShadingValidation(*this, gpuav_cb_state);
    RegisterSharedMemoryDataRaceValidation(*this, gpuav_cb_state);
    RegisterSanitizer(*this, gpuav_cb_state);
    RegisterTraceRayValidation(*this, gpuav_cb_state);
    debug_printf::RegisterDebugPrintf(*this, gpuav_cb_state);
}

// Dedicated warning VUID that likely can be ignored.
// We want to always warn the user when adjusting settings/limits/features/etc on them
void Instance::AdjustmentWarning(LogObjectList objlist, const Location& loc, const char* const specific_message) const {
    LogWarning("WARNING-Setting-Limit-Adjusted", objlist, loc, "Warning that validation is adjusting settings:\n%s",
               specific_message);
}

void Instance::InternalWarning(LogObjectList objlist, const Location& loc, const char* const specific_message) const {
    const char* vuid = gpuav_settings.debug_printf_only ? "WARNING-DEBUG-PRINTF" : "WARNING-GPU-Assisted-Validation";
    LogWarning(vuid, objlist, loc, "Internal Warning: %s", specific_message);
}

void Instance::ReserveBindingSlot(VkPhysicalDevice physicalDevice, VkPhysicalDeviceLimits& limits, const Location& loc) {
    // There is an implicit layer that can cause this call to return 0 for maxBoundDescriptorSets - Ignore such calls
    if (limits.maxBoundDescriptorSets == 0) {
        return;
    }

    if (limits.maxBoundDescriptorSets > kMaxAdjustedBoundDescriptorSet) {
        std::ostringstream ss;
        ss << "A descriptor binding slot is required to store GPU-side information, but the device maxBoundDescriptorSets is "
           << limits.maxBoundDescriptorSets << " which is too large, so we will be trying to use slot "
           << kMaxAdjustedBoundDescriptorSet;
        InternalWarning(physicalDevice, loc, ss.str().c_str());
    }

    if (enabled[gpu_validation_reserve_binding_slot]) {
        if (limits.maxBoundDescriptorSets > 1) {
            limits.maxBoundDescriptorSets -= 1;
        } else {
            InternalWarning(physicalDevice, loc, "Unable to reserve descriptor binding slot on a device with only one slot.");
        }
    }
}

void Instance::PostCallRecordGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* device_props,
                                                         const RecordObject& record_obj) {
    ReserveBindingSlot(physicalDevice, device_props->limits, record_obj.location);
}

void Instance::PostCallRecordGetPhysicalDeviceProperties2(VkPhysicalDevice physicalDevice,
                                                          VkPhysicalDeviceProperties2* device_props2,
                                                          const RecordObject& record_obj) {
    std::string adjustment_warnings;

    // override all possible places maxUpdateAfterBindDescriptorsInAllPools can be set
    auto* desc_indexing_props = vku::FindStructInPNextChain<VkPhysicalDeviceDescriptorIndexingProperties>(device_props2->pNext);
    if (desc_indexing_props &&
        desc_indexing_props->maxUpdateAfterBindDescriptorsInAllPools > glsl::kDebugInputBindlessMaxDescriptors) {
        std::ostringstream ss;
        ss << "\tSetting VkPhysicalDeviceDescriptorIndexingProperties::maxUpdateAfterBindDescriptorsInAllPools to "
           << glsl::kDebugInputBindlessMaxDescriptors;
        adjustment_warnings += ss.str();
        adjustment_warnings += '\n';
        desc_indexing_props->maxUpdateAfterBindDescriptorsInAllPools = glsl::kDebugInputBindlessMaxDescriptors;
    }

    auto* vk12_props = vku::FindStructInPNextChain<VkPhysicalDeviceVulkan12Properties>(device_props2->pNext);
    if (vk12_props && vk12_props->maxUpdateAfterBindDescriptorsInAllPools > glsl::kDebugInputBindlessMaxDescriptors) {
        std::ostringstream ss;
        ss << "\tSetting VkPhysicalDeviceVulkan12Properties::maxUpdateAfterBindDescriptorsInAllPools to "
           << glsl::kDebugInputBindlessMaxDescriptors;
        adjustment_warnings += ss.str();
        adjustment_warnings += '\n';
        vk12_props->maxUpdateAfterBindDescriptorsInAllPools = glsl::kDebugInputBindlessMaxDescriptors;
    }

    if (auto* desc_buffer_props =
            vku::FindStructInPNextChain<VkPhysicalDeviceDescriptorBufferPropertiesEXT>(device_props2->pNext)) {
        if (desc_buffer_props->maxResourceDescriptorBufferBindings > 1) {
            desc_buffer_props->maxResourceDescriptorBufferBindings -= 1;

            std::ostringstream ss;
            ss << "\tSetting VkPhysicalDeviceDescriptorBufferPropertiesEXT::maxResourceDescriptorBufferBindings to "
               << desc_buffer_props->maxResourceDescriptorBufferBindings;
            adjustment_warnings += ss.str();
            adjustment_warnings += '\n';
        }

        // Currently set regardless if we fallback on maxResourceDescriptorBufferBindings only being 1
        {
            // This is only a best estimate, we really need to match it up with the value returned from
            // vkGetDescriptorSetLayoutSizeEXT, but can't at this point. This in practice shouldn't be an issue unless they try to
            // allocate every possible byte on the GPU and our estimate is too small.
            //
            // storageBufferDescriptorSize is almost always 64 bytes
            VkDeviceSize buffer_indirect_buffer_stride = desc_buffer_props->storageBufferDescriptorSize * glsl::kTotalBindings;
            const VkDeviceSize bytes_to_reserve = buffer_indirect_buffer_stride * gpuav_settings.indices_buffer_count;
            desc_buffer_props->resourceDescriptorBufferAddressSpaceSize -= bytes_to_reserve;
            desc_buffer_props->descriptorBufferAddressSpaceSize -= bytes_to_reserve;

            std::ostringstream ss;
            ss << "\tSetting VkPhysicalDeviceDescriptorBufferPropertiesEXT::descriptorBufferAddressSpaceSize to "
               << desc_buffer_props->resourceDescriptorBufferAddressSpaceSize << " and resourceDescriptorBufferAddressSpaceSize to "
               << desc_buffer_props->descriptorBufferAddressSpaceSize << " (reserving " << bytes_to_reserve << " bytes)";
            adjustment_warnings += ss.str();
            adjustment_warnings += '\n';
        }
    }
    if (!adjustment_warnings.empty() && !reported_gpdp2_warning_) {
        reported_gpdp2_warning_ = true;
        AdjustmentWarning(physicalDevice, record_obj.location, adjustment_warnings.c_str());
    }

    ReserveBindingSlot(physicalDevice, device_props2->properties.limits, record_obj.location);
}

// Clean up device-related resources
void Validator::PreCallRecordDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator,
                                           const RecordObject& record_obj) {
    // Need to destroy substate before memory backed in things like shared_resources_cache are cleared
    DestroySubstate();

    shared_resources_cache.Clear();
    gpu_resources_manager_.DestroyResources();

    global_indices_buffer_.Destroy();

    GpuShaderInstrumentor::PreCallRecordDestroyDevice(device, pAllocator, record_obj);

    // State Tracker (BaseClass) can end up making vma calls through callbacks - so destroy allocator last
    if (vma_allocator_) {
        vmaDestroyAllocator(vma_allocator_);
    }

    desc_set_manager_.reset();
}

// Common logic before any draw/dispatch/traceRays
void Validator::PreCallActionCommand(Validator& gpuav, CommandBufferSubState& cb_state, const LastBound& last_bound,
                                     const Location& loc) {
    PreCallSetupShaderInstrumentationResources(gpuav, cb_state, last_bound, loc);
}

void Validator::PreCallRecordCmdDraw(VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount,
                                     uint32_t firstVertex, uint32_t firstInstance, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);

    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawMultiEXT(VkCommandBuffer commandBuffer, uint32_t drawCount,
                                             const VkMultiDrawInfoEXT* pVertexInfo, uint32_t instanceCount, uint32_t firstInstance,
                                             uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawIndexed(VkCommandBuffer commandBuffer, uint32_t indexCount, uint32_t instanceCount,
                                            uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance,
                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawMultiIndexedEXT(VkCommandBuffer commandBuffer, uint32_t drawCount,
                                                    const VkMultiDrawIndexedInfoEXT* pIndexInfo, uint32_t instanceCount,
                                                    uint32_t firstInstance, uint32_t stride, const int32_t* pVertexOffset,
                                                    const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t count,
                                             uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto indirect_buffer_state = Get<vvl::Buffer>(buffer);
    if (!indirect_buffer_state) {
        InternalError(commandBuffer, record_obj.location, "buffer must be a valid VkBuffer handle");
        return;
    }
    auto& sub_state = SubState(*cb_state);

    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    valcmd::FirstInstance<VkDrawIndirectCommand>(*this, sub_state, record_obj.location, last_bound, buffer, offset, count, stride,
                                                 VK_NULL_HANDLE, 0);
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawIndexedIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                    uint32_t count, uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);

    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    valcmd::DrawIndexedIndirectIndexBuffer(*this, sub_state, record_obj.location, last_bound, buffer, offset, stride, count,
                                           VK_NULL_HANDLE, 0, "VUID-VkDrawIndexedIndirectCommand-robustBufferAccess2-08798");

    valcmd::FirstInstance<VkDrawIndexedIndirectCommand>(*this, sub_state, record_obj.location, last_bound, buffer, offset, count,
                                                        stride, VK_NULL_HANDLE, 0);
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawIndirectCountKHR(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                     VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                     uint32_t stride, const RecordObject& record_obj) {
    PreCallRecordCmdDrawIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                      record_obj);
}

void Validator::PreCallRecordCmdDrawIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                  VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                  uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto indirect_buffer_state = Get<vvl::Buffer>(buffer);
    if (!indirect_buffer_state) {
        InternalError(commandBuffer, record_obj.location, "buffer must be a valid VkBuffer handle");
        return;
    }
    auto& sub_state = SubState(*cb_state);

    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    const char* vuid = (record_obj.location.function == vvl::Func::vkCmdDrawIndirectCount2KHR ||
                        record_obj.location.function == vvl::Func::vkCmdDrawIndexedIndirectCount2KHR ||
                        record_obj.location.function == vvl::Func::vkCmdDrawMeshTasksIndirectCount2EXT)
                           ? "VUID-VkDrawIndirectCount2InfoKHR-countAddressRange-13116"
                           : "VUID-vkCmdDrawIndirectCount-countBuffer-02717";
    valcmd::CountBuffer(*this, sub_state, record_obj.location, last_bound, buffer, offset, sizeof(VkDrawIndirectCommand),
                        vvl::Struct::VkDrawIndirectCommand, stride, countBuffer, countBufferOffset, vuid);
    valcmd::FirstInstance<VkDrawIndirectCommand>(*this, sub_state, record_obj.location, last_bound, buffer, offset, maxDrawCount,
                                                 stride, countBuffer, countBufferOffset);
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawIndirect2KHR(VkCommandBuffer commandBuffer, const VkDrawIndirect2InfoKHR* pInfo,
                                                 const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    const auto buffer_states = GetBuffersByAddressRange(
        VkDeviceAddressRangeKHR{pInfo->addressRange.address, pInfo->addressRange.size}, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
    if (buffer_states.empty()) {
        return;
    }

    const VkBuffer buffer = buffer_states[0]->VkHandle();
    const VkDeviceSize offset = pInfo->addressRange.address - buffer_states[0]->deviceAddress;
    const uint32_t draw_count = pInfo->drawCount;
    const uint32_t stride = static_cast<uint32_t>(pInfo->addressRange.stride);
    auto& sub_state = SubState(*cb_state);

    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    valcmd::FirstInstance<VkDrawIndirectCommand>(*this, sub_state, record_obj.location, last_bound, buffer, offset, draw_count,
                                                 stride, VK_NULL_HANDLE, 0);
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
    return;
}

void Validator::PreCallRecordCmdDrawIndexedIndirect2KHR(VkCommandBuffer commandBuffer, const VkDrawIndirect2InfoKHR* pInfo,
                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    const auto buffer_states = GetBuffersByAddressRange(
        VkDeviceAddressRangeKHR{pInfo->addressRange.address, pInfo->addressRange.size}, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
    if (buffer_states.empty()) {
        return;
    }

    const VkBuffer buffer = buffer_states[0]->VkHandle();
    const VkDeviceSize offset = pInfo->addressRange.address - buffer_states[0]->deviceAddress;
    const uint32_t draw_count = pInfo->drawCount;
    const uint32_t stride = static_cast<uint32_t>(pInfo->addressRange.stride);

    auto& sub_state = SubState(*cb_state);

    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    valcmd::DrawIndexedIndirectIndexBuffer(*this, sub_state, record_obj.location, last_bound, buffer, offset, stride, draw_count,
                                           VK_NULL_HANDLE, 0, "VUID-VkDrawIndexedIndirectCommand-robustBufferAccess2-08798");

    valcmd::FirstInstance<VkDrawIndexedIndirectCommand>(*this, sub_state, record_obj.location, last_bound, buffer, offset,
                                                        draw_count, stride, VK_NULL_HANDLE, 0);
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
    return;
}

void Validator::PreCallRecordCmdDrawIndirectCount2KHR(VkCommandBuffer commandBuffer, const VkDrawIndirectCount2InfoKHR* pInfo,
                                                      const RecordObject& record_obj) {
    const auto buffer_states = GetBuffersByAddressRange(
        VkDeviceAddressRangeKHR{pInfo->addressRange.address, pInfo->addressRange.size}, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
    if (buffer_states.empty()) {
        return;
    }

    // TODO - https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11879
    const auto count_buffer_states = GetBuffersByAddressRange(pInfo->countAddressRange, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
    if (count_buffer_states.empty()) {
        return;
    }

    const VkBuffer buffer = buffer_states[0]->VkHandle();
    const VkDeviceSize offset = pInfo->addressRange.address - buffer_states[0]->deviceAddress;
    const VkBuffer countBuffer = count_buffer_states[0]->VkHandle();
    const VkDeviceSize countBufferOffset = pInfo->countAddressRange.address - count_buffer_states[0]->deviceAddress;
    const uint32_t maxDrawCount = pInfo->maxDrawCount;
    const uint32_t stride = static_cast<uint32_t>(pInfo->addressRange.stride);
    PreCallRecordCmdDrawIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                      record_obj);
    return;
}

void Validator::PreCallRecordCmdDrawIndexedIndirectCount2KHR(VkCommandBuffer commandBuffer,
                                                             const VkDrawIndirectCount2InfoKHR* pInfo,
                                                             const RecordObject& record_obj) {
    PreCallRecordCmdDrawIndirectCount2KHR(commandBuffer, pInfo, record_obj);
}

void Validator::PreCallRecordCmdDrawMeshTasksIndirectCount2EXT(VkCommandBuffer commandBuffer,
                                                               const VkDrawIndirectCount2InfoKHR* pInfo,
                                                               const RecordObject& record_obj) {
    PreCallRecordCmdDrawIndirectCount2KHR(commandBuffer, pInfo, record_obj);
}

void Validator::PreCallRecordCmdDrawIndirectByteCount(VkCommandBuffer commandBuffer, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);
    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawIndirectByteCount2EXT(VkCommandBuffer commandBuffer, uint32_t instanceCount,
                                                          uint32_t firstInstance,
                                                          const VkBindTransformFeedbackBuffer2InfoEXT* pCounterInfo,
                                                          uint32_t counterOffset, uint32_t vertexStride,
                                                          const RecordObject& record_obj) {
    PreCallRecordCmdDrawIndirectByteCount(commandBuffer, record_obj);
}

void Validator::PreCallRecordCmdDrawIndirectByteCountEXT(VkCommandBuffer commandBuffer, uint32_t instanceCount,
                                                         uint32_t firstInstance, VkBuffer counterBuffer,
                                                         VkDeviceSize counterBufferOffset, uint32_t counterOffset,
                                                         uint32_t vertexStride, const RecordObject& record_obj) {
    PreCallRecordCmdDrawIndirectByteCount(commandBuffer, record_obj);
}

void Validator::PreCallRecordCmdDrawIndexedIndirectCountKHR(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                            VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                            uint32_t maxDrawCount, uint32_t stride,
                                                            const RecordObject& record_obj) {
    PreCallRecordCmdDrawIndexedIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                             record_obj);
}

void Validator::PreCallRecordCmdDrawIndexedIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                         VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                         uint32_t maxDrawCount, uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    valcmd::CountBuffer(*this, sub_state, record_obj.location, last_bound, buffer, offset, sizeof(VkDrawIndexedIndirectCommand),
                        vvl::Struct::VkDrawIndexedIndirectCommand, stride, countBuffer, countBufferOffset,
                        "VUID-vkCmdDrawIndexedIndirectCount-countBuffer-02717");
    valcmd::FirstInstance<VkDrawIndexedIndirectCommand>(*this, sub_state, record_obj.location, last_bound, buffer, offset,
                                                        maxDrawCount, stride, countBuffer, countBufferOffset);

    valcmd::DrawIndexedIndirectIndexBuffer(*this, sub_state, record_obj.location, last_bound, buffer, offset, stride, maxDrawCount,
                                           countBuffer, countBufferOffset,
                                           "VUID-VkDrawIndexedIndirectCommand-robustBufferAccess2-08798");

    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawMeshTasksNV(VkCommandBuffer commandBuffer, uint32_t taskCount, uint32_t firstTask,
                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawMeshTasksIndirectNV(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                        uint32_t drawCount, uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawMeshTasksIndirectCountNV(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                             VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                             uint32_t maxDrawCount, uint32_t stride,
                                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto indirect_buffer_state = Get<vvl::Buffer>(buffer);
    if (!indirect_buffer_state) {
        InternalError(commandBuffer, record_obj.location, "buffer must be a valid VkBuffer handle");
        return;
    }

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    valcmd::CountBuffer(*this, sub_state, record_obj.location, last_bound, buffer, offset, sizeof(VkDrawMeshTasksIndirectCommandNV),
                        vvl::Struct::VkDrawMeshTasksIndirectCommandNV, stride, countBuffer, countBufferOffset,
                        "VUID-vkCmdDrawMeshTasksIndirectCountNV-countBuffer-02717");

    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawMeshTasksEXT(VkCommandBuffer commandBuffer, uint32_t groupCountX, uint32_t groupCountY,
                                                 uint32_t groupCountZ, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawMeshTasksIndirectEXT(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                         uint32_t drawCount, uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    valcmd::DrawMeshIndirect(*this, sub_state, record_obj.location, last_bound, buffer, offset, stride, VK_NULL_HANDLE, 0,
                             drawCount);
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDrawMeshTasksIndirectCountEXT(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                              VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                              uint32_t maxDrawCount, uint32_t stride,
                                                              const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto indirect_buffer_state = Get<vvl::Buffer>(buffer);
    if (!indirect_buffer_state) {
        InternalError(commandBuffer, record_obj.location, "buffer must be a valid VkBuffer handle");
        return;
    }

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundGraphics();
    valcmd::DrawMeshIndirect(*this, sub_state, record_obj.location, last_bound, buffer, offset, stride, countBuffer,
                             countBufferOffset, maxDrawCount);

    valcmd::CountBuffer(*this, sub_state, record_obj.location, last_bound, buffer, offset,
                        sizeof(VkDrawMeshTasksIndirectCommandEXT), vvl::Struct::VkDrawMeshTasksIndirectCommandEXT, stride,
                        countBuffer, countBufferOffset, "VUID-vkCmdDrawMeshTasksIndirectCountEXT-countBuffer-02717");
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDispatch(VkCommandBuffer commandBuffer, uint32_t x, uint32_t y, uint32_t z,
                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundCompute();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDispatchIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                 const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundCompute();
    valcmd::DispatchIndirect(*this, record_obj.location, sub_state, last_bound, buffer, offset);
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDispatchIndirect2KHR(VkCommandBuffer commandBuffer, const VkDispatchIndirect2InfoKHR* pInfo,
                                                     const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundCompute();

    const auto buffer_states = GetBuffersByAddressRange(pInfo->addressRange, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
    if (buffer_states.empty()) {
        return;
    }

    const VkBuffer buffer = buffer_states[0]->VkHandle();
    const VkDeviceSize offset = pInfo->addressRange.address - buffer_states[0]->deviceAddress;
    valcmd::DispatchIndirect(*this, record_obj.location, sub_state, last_bound, buffer, offset);
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDispatchBase(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY,
                                             uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ,
                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundCompute();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdDispatchBaseKHR(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY,
                                                uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY,
                                                uint32_t groupCountZ, const RecordObject& record_obj) {
    PreCallRecordCmdDispatchBase(commandBuffer, baseGroupX, baseGroupY, baseGroupZ, groupCountX, groupCountY, groupCountZ,
                                 record_obj);
}

void Validator::PreCallRecordCmdBuildAccelerationStructuresKHR(
    VkCommandBuffer commandBuffer, uint32_t infoCount, const VkAccelerationStructureBuildGeometryInfoKHR* pInfos,
    const VkAccelerationStructureBuildRangeInfoKHR* const* ppBuildRangeInfos, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& cb_sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundRayTracing();
    valcmd::TLAS(*this, record_obj.location, cb_sub_state, last_bound, infoCount, pInfos, ppBuildRangeInfos);
    valcmd::BLAS(*this, record_obj.location, cb_sub_state, last_bound, infoCount, pInfos, ppBuildRangeInfos);
}

void Validator::PostCallRecordCmdBuildAccelerationStructuresKHR(
    VkCommandBuffer commandBuffer, uint32_t infoCount, const VkAccelerationStructureBuildGeometryInfoKHR* pInfos,
    const VkAccelerationStructureBuildRangeInfoKHR* const* ppBuildRangeInfos, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& cb_sub_state = SubState(*cb_state);
    UpdateAccelerationStructureGpuState(*this, cb_sub_state, record_obj.location, infoCount, pInfos);
}

void Validator::PreCallRecordCmdTraceRaysNV(VkCommandBuffer commandBuffer, VkBuffer raygenShaderBindingTableBuffer,
                                            VkDeviceSize raygenShaderBindingOffset, VkBuffer missShaderBindingTableBuffer,
                                            VkDeviceSize missShaderBindingOffset, VkDeviceSize missShaderBindingStride,
                                            VkBuffer hitShaderBindingTableBuffer, VkDeviceSize hitShaderBindingOffset,
                                            VkDeviceSize hitShaderBindingStride, VkBuffer callableShaderBindingTableBuffer,
                                            VkDeviceSize callableShaderBindingOffset, VkDeviceSize callableShaderBindingStride,
                                            uint32_t width, uint32_t height, uint32_t depth, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundRayTracing();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdTraceRaysKHR(VkCommandBuffer commandBuffer,
                                             const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable,
                                             const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable,
                                             const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable,
                                             const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable, uint32_t width,
                                             uint32_t height, uint32_t depth, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundRayTracing();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdTraceRaysIndirectKHR(VkCommandBuffer commandBuffer,
                                                     const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable,
                                                     const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable,
                                                     const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable,
                                                     const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable,
                                                     VkDeviceAddress indirectDeviceAddress, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundRayTracing();
    valcmd::TraceRaysIndirect(*this, record_obj.location, sub_state, last_bound, indirectDeviceAddress);
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdTraceRaysIndirect2KHR(VkCommandBuffer commandBuffer, VkDeviceAddress indirectDeviceAddress,
                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);
    const LastBound& last_bound = cb_state->GetLastBoundRayTracing();
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
}

void Validator::PreCallRecordCmdExecuteGeneratedCommandsEXT(VkCommandBuffer commandBuffer, VkBool32 isPreprocessed,
                                                            const VkGeneratedCommandsInfoEXT* pGeneratedCommandsInfo,
                                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    auto& sub_state = SubState(*cb_state);

    const VkPipelineBindPoint bind_point = ConvertStageToBindPoint(pGeneratedCommandsInfo->shaderStages);
    const vvl::BindPoint vvl_bind_point = ConvertToVvlBindPoint(bind_point);
    const LastBound& last_bound = cb_state->lastBound[vvl_bind_point];
    PreCallActionCommand(*this, sub_state, last_bound, record_obj.location);
};

void Validator::PreCallRecordCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage,
                                                  VkImageLayout dstImageLayout, uint32_t regionCount,
                                                  const VkBufferImageCopy* pRegions, const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);

    std::vector<VkBufferImageCopy2> regions_2(regionCount);
    for (const auto [i, region] : vvl::enumerate(pRegions, regionCount)) {
        regions_2[i].bufferOffset = region.bufferOffset;
        regions_2[i].bufferRowLength = region.bufferRowLength;
        regions_2[i].bufferImageHeight = region.bufferImageHeight;
        regions_2[i].imageSubresource = region.imageSubresource;
        regions_2[i].imageOffset = region.imageOffset;
        regions_2[i].imageExtent = region.imageExtent;
    }

    VkCopyBufferToImageInfo2 copy_buffer_to_image_info = vku::InitStructHelper();
    copy_buffer_to_image_info.srcBuffer = srcBuffer;
    copy_buffer_to_image_info.dstImage = dstImage;
    copy_buffer_to_image_info.dstImageLayout = dstImageLayout;
    copy_buffer_to_image_info.regionCount = regionCount;
    copy_buffer_to_image_info.pRegions = regions_2.data();

    valcmd::CopyBufferToImage(*this, record_obj.location, SubState(*cb_state), &copy_buffer_to_image_info);
}

void Validator::PreCallRecordCmdCopyBufferToImage2KHR(VkCommandBuffer commandBuffer,
                                                      const VkCopyBufferToImageInfo2KHR* pCopyBufferToImageInfo2KHR,
                                                      const RecordObject& record_obj) {
    PreCallRecordCmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo2KHR, record_obj);
}

void Validator::PreCallRecordCmdCopyBufferToImage2(VkCommandBuffer commandBuffer,
                                                   const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo,
                                                   const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);
    valcmd::CopyBufferToImage(*this, record_obj.location, SubState(*cb_state), pCopyBufferToImageInfo);
}

void Validator::PreCallRecordCmdCopyMemoryIndirectKHR(VkCommandBuffer commandBuffer,
                                                      const VkCopyMemoryIndirectInfoKHR* pCopyMemoryIndirectInfo,
                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);
    const valcmd::CopyMemoryIndirectCommon copy_info = {pCopyMemoryIndirectInfo->copyCount,
                                                        pCopyMemoryIndirectInfo->copyAddressRange};
    valcmd::CopyMemoryIndirect(*this, record_obj.location, SubState(*cb_state), copy_info);
}

void Validator::PreCallRecordCmdCopyMemoryToImageIndirectKHR(
    VkCommandBuffer commandBuffer, const VkCopyMemoryToImageIndirectInfoKHR* pCopyMemoryToImageIndirectInfo,
    const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);
    const valcmd::CopyMemoryIndirectCommon copy_info = {pCopyMemoryToImageIndirectInfo->copyCount,
                                                        pCopyMemoryToImageIndirectInfo->copyAddressRange};
    valcmd::CopyMemoryIndirect(*this, record_obj.location, SubState(*cb_state), copy_info);
}

void Validator::PreCallRecordCmdCopyMemoryToImageKHR(VkCommandBuffer commandBuffer,
                                                     const VkCopyDeviceMemoryImageInfoKHR* pCopyMemoryInfo,
                                                     const RecordObject& record_obj) {
    for (uint32_t i = 0; i < pCopyMemoryInfo->regionCount; ++i) {
        // TODO - https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11879
        const auto buffer_states =
            GetBuffersByAddressRange(pCopyMemoryInfo->pRegions[i].addressRange, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
        if (buffer_states.empty()) {
            continue;
        }

        VkBufferImageCopy2 region = vku::InitStructHelper();
        region.bufferOffset = pCopyMemoryInfo->pRegions[i].addressRange.address - buffer_states[0]->deviceAddress;
        region.bufferImageHeight = pCopyMemoryInfo->pRegions[i].addressImageHeight;
        region.imageSubresource = pCopyMemoryInfo->pRegions[i].imageSubresource;
        region.imageOffset = pCopyMemoryInfo->pRegions[i].imageOffset;
        region.imageExtent = pCopyMemoryInfo->pRegions[i].imageExtent;

        VkCopyBufferToImageInfo2 copy_buffer_to_image_info = vku::InitStructHelper();
        copy_buffer_to_image_info.srcBuffer = buffer_states[0]->VkHandle();
        copy_buffer_to_image_info.dstImage = pCopyMemoryInfo->image;
        copy_buffer_to_image_info.dstImageLayout = pCopyMemoryInfo->pRegions[i].imageLayout;
        copy_buffer_to_image_info.regionCount = 1u;
        copy_buffer_to_image_info.pRegions = &region;
        valcmd::CopyBufferToImage(*this, record_obj.location, SubState(*GetWrite<vvl::CommandBuffer>(commandBuffer)),
                                  &copy_buffer_to_image_info);
    }
}

bool Validator::PreCallValidateCmdPushDataEXT(VkCommandBuffer commandBuffer, const VkPushDataInfoEXT* pPushDataInfo,
                                              const ErrorObject& error_obj) const {
    bool skip = false;
    if (!gpuav_settings.IsShaderInstrumentationEnabled()) {
        return skip;
    }
    const uint32_t final_byte = (uint32_t)(pPushDataInfo->offset + pPushDataInfo->data.size);
    if (final_byte > push_data_offset_) {
        skip |= LogError("UNASSIGNED-GPU-Assisted-Validation", commandBuffer, error_obj.location,
                         "is trying to set push data at [%" PRIu32 ":%" PRIu32
                         ") however GPU-AV needs to reserve 8 bytes at [%" PRIu32 ":%" PRIu32
                         ") for internal use. Therefore only push data at byte [0:%" PRIu32 "] are available to the application.",
                         pPushDataInfo->offset, final_byte - 1, push_data_offset_, push_data_offset_ + 7, push_data_offset_ - 1);
    }
    return skip;
}

// Validates the buffer is allowed to be protected
bool Validator::ValidateProtectedBuffer(const vvl::CommandBuffer& cb_state, const vvl::Buffer& buffer_state,
                                        const Location& buffer_loc, const char* vuid, const char* more_message) const {
    bool skip = false;

    // if driver supports protectedNoFault the operation is valid, just has undefined values
    if ((!phys_dev_props_core11.protectedNoFault) && (cb_state.unprotected == true) && (buffer_state.unprotected == false)) {
        const LogObjectList objlist(cb_state.Handle(), buffer_state.Handle());
        skip |= LogError(vuid, objlist, buffer_loc, "(%s) is a protected buffer, but command buffer (%s) is unprotected.%s",
                         FormatHandle(buffer_state).c_str(), FormatHandle(cb_state).c_str(), more_message);
    }
    return skip;
}

// Validates the buffer is allowed to be unprotected
bool Validator::ValidateUnprotectedBuffer(const vvl::CommandBuffer& cb_state, const vvl::Buffer& buffer_state,
                                          const Location& buffer_loc, const char* vuid, const char* more_message) const {
    bool skip = false;

    // if driver supports protectedNoFault the operation is valid, just has undefined values
    if ((!phys_dev_props_core11.protectedNoFault) && (cb_state.unprotected == false) && (buffer_state.unprotected == true)) {
        const LogObjectList objlist(cb_state.Handle(), buffer_state.Handle());
        skip |= LogError(vuid, objlist, buffer_loc, "(%s) is an unprotected buffer, but command buffer (%s) is protected.%s",
                         FormatHandle(buffer_state).c_str(), FormatHandle(cb_state).c_str(), more_message);
    }
    return skip;
}

// Validates the image is allowed to be protected
bool Validator::ValidateProtectedImage(const vvl::CommandBuffer& cb_state, const vvl::Image& image_state, const Location& loc,
                                       const char* vuid, const char* more_message) const {
    bool skip = false;

    // if driver supports protectedNoFault the operation is valid, just has undefined values
    if ((!phys_dev_props_core11.protectedNoFault) && (cb_state.unprotected == true) && (image_state.unprotected == false)) {
        const LogObjectList objlist(cb_state.Handle(), image_state.Handle());
        skip |= LogError(vuid, objlist, loc, "(%s) is a protected image, but command buffer (%s) is unprotected.%s",
                         FormatHandle(image_state).c_str(), FormatHandle(cb_state).c_str(), more_message);
    }
    return skip;
}

// Validates the image is allowed to be unprotected
bool Validator::ValidateUnprotectedImage(const vvl::CommandBuffer& cb_state, const vvl::Image& image_state, const Location& loc,
                                         const char* vuid, const char* more_message) const {
    bool skip = false;

    // if driver supports protectedNoFault the operation is valid, just has undefined values
    if ((!phys_dev_props_core11.protectedNoFault) && (cb_state.unprotected == false) && (image_state.unprotected == true)) {
        const LogObjectList objlist(cb_state.Handle(), image_state.Handle());
        skip |= LogError(vuid, objlist, loc, "(%s) is an unprotected image, but command buffer (%s) is protected.%s",
                         FormatHandle(image_state).c_str(), FormatHandle(cb_state).c_str(), more_message);
    }
    return skip;
}

// Validates the buffer is allowed to be protected
bool Validator::ValidateProtectedTensor(const vvl::CommandBuffer& cb_state, const vvl::Tensor& tensor_state,
                                        const Location& tensor_loc, const char* more_message) const {
    /* don't use on an unprotected tensor */
    assert(tensor_state.unprotected == false);

    bool skip = false;

    // if driver supports protectedNoFault the operation is valid, just has undefined values
    if ((!phys_dev_props_core11.protectedNoFault) && (cb_state.unprotected == true)) {
        const LogObjectList objlist(cb_state.Handle(), tensor_state.Handle());
        skip |= LogError("VUID-vkCmdDispatchDataGraphARM-commandBuffer-09800", objlist, tensor_loc,
                         "(%s) is a protected tensor, but command buffer (%s) is unprotected.%s",
                         FormatHandle(tensor_state).c_str(), FormatHandle(cb_state).c_str(), more_message);
    }
    return skip;
}

// Validates the buffer is allowed to be unprotected
bool Validator::ValidateUnprotectedTensor(const vvl::CommandBuffer& cb_state, const vvl::Tensor& tensor_state,
                                          const Location& tensor_loc, const char* more_message) const {
    /* don't use on a protected tensor */
    assert(tensor_state.unprotected == true);

    bool skip = false;

    // if driver supports protectedNoFault the operation is valid, just has undefined values
    if ((!phys_dev_props_core11.protectedNoFault) && (cb_state.unprotected == false)) {
        const LogObjectList objlist(cb_state.Handle(), tensor_state.Handle());
        skip |= LogError("VUID-vkCmdDispatchDataGraphARM-commandBuffer-09801", objlist, tensor_loc,
                         "(%s) is an unprotected tensor, but command buffer (%s) is protected.%s",
                         FormatHandle(tensor_state).c_str(), FormatHandle(cb_state).c_str(), more_message);
    }
    return skip;
}

}  // namespace gpuav
