/* Copyright (c) 2015-2026 The Khronos Group Inc.
 * Copyright (c) 2015-2026 Valve Corporation
 * Copyright (c) 2015-2026 LunarG, Inc.
 * Copyright (C) 2015-2026 Google Inc.
 * Copyright (c) 2025 Arm Limited.
 * Copyright (C) 2026 Qualcomm Technologies, Inc.
 * Modifications Copyright (C) 2020,2025-2026 Advanced Micro Devices, Inc. All rights reserved.
 * Modifications Copyright (C) 2022 RasterGrid Kft.
 *
 * 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 <algorithm>
#include <vulkan/utility/vk_format_utils.h>
#include <vulkan/vk_enum_string_helper.h>
#include <vulkan/vulkan_core.h>
#include <sstream>
#include <string>
#include <vulkan/utility/vk_struct_helper.hpp>
#include "containers/custom_containers.h"
#include "containers/container_utils.h"
#include "error_message/error_strings.h"
#include "generated/vk_extension_helper.h"
#include "state_tracker/bind_point.h"
#include "state_tracker/data_graph_pipeline_session_state.h"
#include "state_tracker/descriptor_hashing.h"
#include "state_tracker/shader_stage_state.h"
#include "state_tracker/image_state.h"
#include "state_tracker/buffer_state.h"
#include "state_tracker/tensor_state.h"
#include "state_tracker/device_state.h"
#include "state_tracker/event_state.h"
#include "state_tracker/queue_state.h"
#include "state_tracker/cmd_buffer_state.h"
#include "state_tracker/pipeline_state.h"
#include "state_tracker/render_pass_state.h"
#include "state_tracker/ray_tracing_state.h"
#include "state_tracker/shader_object_state.h"
#include "state_tracker/device_generated_commands_state.h"
#include "state_tracker/wsi_state.h"
#include "state_tracker/descriptor_mode.h"
#include "chassis/chassis_modification_state.h"
#include "spirv-tools/optimizer.hpp"
#include "utils/assert_utils.h"
#include "utils/descriptor_utils.h"
#include "utils/lock_utils.h"
#include "utils/spirv_tools_utils.h"
#include "utils/math_utils.h"

// Used for debugging
#include "utils/keyboard.h"

#include "utils/image_utils.h"  // GetExternalFormat
#include "utils/sync_utils.h"
#include "chassis/chassis.h"

namespace vvl {

DeviceState::DeviceState(DispatchDevice* dev, InstanceState* instance)
    : BaseDevice(dev, instance, LayerObjectTypeStateTracker),
      instance_state(instance),
      special_supported(dev->stateless_device_data.special_supported) {
    physical_device_state = instance_state->Get<vvl::PhysicalDevice>(physical_device).get();
    physical_device_state->has_maintenance9 = dev->stateless_device_data.special_supported.has_maintenance9;
}

DeviceState::~DeviceState() { DestroyObjectMaps(); }

void DeviceState::AddProxy(DeviceProxy& proxy) { proxies.emplace(proxy.container_type, proxy); }

void DeviceState::RemoveProxy(LayerObjectTypeId id) {
    proxies.erase(id);
    // this is used by gpuav abort so it needs to clean up any substates as well
    RemoveSubState(id);
}

// Seperate function so GPU-AV (or other objects) can destroy its substate before it destroys itself (and then the leaked object
// cleanup tries to destroy the substate)
void DeviceState::RemoveSubState(LayerObjectTypeId id) {
    // Currently we have not good way to track all objects that have a substate, so this is a list from manual inspection
    ForEachShared<vvl::CommandBuffer>([id](std::shared_ptr<vvl::CommandBuffer> state) {
        state->RemoveSubState(id);
        state->RemoveOwnedSubState(id);
    });
    ForEachShared<vvl::Queue>([id](std::shared_ptr<vvl::Queue> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::Swapchain>([id](std::shared_ptr<vvl::Swapchain> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::ImageView>([id](std::shared_ptr<vvl::ImageView> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::Image>([id](std::shared_ptr<vvl::Image> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::Sampler>([id](std::shared_ptr<vvl::Sampler> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::DescriptorSet>([id](std::shared_ptr<vvl::DescriptorSet> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::BufferView>([id](std::shared_ptr<vvl::BufferView> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::Buffer>([id](std::shared_ptr<vvl::Buffer> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::AccelerationStructureNV>(
        [id](std::shared_ptr<vvl::AccelerationStructureNV> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::AccelerationStructureKHR>(
        [id](std::shared_ptr<vvl::AccelerationStructureKHR> state) { state->RemoveSubState(id); });
    ForEachShared<vvl::ShaderObject>([id](std::shared_ptr<vvl::ShaderObject> state) { state->RemoveSubState(id); });
}

VkDeviceAddress DeviceState::GetBufferDeviceAddressHelper(VkBuffer buffer, const DeviceExtensions* exts) const {
    // GPU-AV needs to pass in the modified extensions, since it may turn on BDA on its own
    if (!exts) {
        exts = &extensions;
    }
    VkBufferDeviceAddressInfo address_info = vku::InitStructHelper();
    address_info.buffer = buffer;

    if (api_version >= VK_API_VERSION_1_2) {
        return DispatchGetBufferDeviceAddress(device, &address_info);
    } else {
        if (IsExtEnabled(exts->vk_khr_buffer_device_address)) {
            return DispatchGetBufferDeviceAddressKHR(device, &address_info);
        } else {
            return 0;
        }
    }
}

// From the spec:
// If multiple VkBuffer objects are bound to overlapping ranges of VkDeviceMemory, implementations may return
// address ranges which overlap. In this case, it is ambiguous which VkBuffer is associated with any given
// device address. For purposes of valid usage, if multiple VkBuffer objects can be attributed to
// a device address, a VkBuffer is selected such that valid usage passes, if it exists.
// Regarding using raw pointers instead of shared: The reason is performance, because arrays of vvl::Buffer* are used, it is
// more efficient to store them using raw pointers. It is safe to do so (at time of writing) because those raw pointers come
// from shared ones created when the buffer is first recorded, and they are removed from buffer_address_map_ at BufferDestroy
// time
vvl::span<vvl::Buffer* const> DeviceState::GetBuffersByAddress(VkDeviceAddress address) const {
    ReadLockGuard guard(buffer_address_lock_);
    auto found_it = buffer_address_map_.find(address);
    if (found_it == buffer_address_map_.end()) {
        return vvl::make_span<vvl::Buffer* const>(nullptr, static_cast<size_t>(0));
    }
    return found_it->second;
}

small_vector<vvl::Buffer*, 2> DeviceState::GetBuffersByAddressRange(const VkDeviceAddressRangeKHR& address_range,
                                                                    VkBufferUsageFlags2 buffer_usage_flags) const {
    small_vector<vvl::Buffer*, 2> filtered_buffers;

    vvl::span<vvl::Buffer* const> buffers = GetBuffersByAddress(address_range.address);
    for (vvl::Buffer* const buffer : buffers) {
        // address_range.address already belongs to buffer by definition,
        // just check that end address of address_range is inferior to buffer's end address
        if (buffer->deviceAddress + buffer->GetSize() < address_range.address + address_range.size) {
            continue;
        }

        if ((buffer->usage & buffer_usage_flags) == buffer_usage_flags) {
            filtered_buffers.emplace_back(buffer);
        }
    }

    return filtered_buffers;
}

void DeviceState::TrackDeviceAddressRange(vvl::CommandBuffer& cb_state, const vvl::range<VkDeviceAddress> range,
                                          VkBufferUsageFlags2 usage) {
    if (disabled[command_buffer_state]) {
        return;
    }

    // small_vector is here to match the BufferAddressRange state object
    small_vector<vvl::Buffer*, 2> buffers;
    {
        ReadLockGuard guard(buffer_address_lock_);
        const VkDeviceAddress address = range.begin;
        auto found_it = buffer_address_map_.find(address);
        if (found_it != buffer_address_map_.end()) {
            for (vvl::Buffer* found_buffer : found_it->second) {
                if ((found_buffer->usage & usage) == usage && found_buffer->DeviceAddressRange().includes(range)) {
                    buffers.emplace_back(found_buffer);
                }
            }
        }
    }

    // This shouldn't be possible as we validate the address is in a valid buffer range already
    // But if there is already an error, we will still need to guard against this
    if (buffers.empty()) {
        return;
    }

    auto bufer_address_range_states = std::make_shared<vvl::BufferAddressRange>(buffers, range, usage);
    bufer_address_range_states->LinkChildNodes();
    cb_state.AddChild(bufer_address_range_states);
}

// NOTE:  Beware the lifespan of the rp_begin when holding  the return.  If the rp_begin isn't a "safe" copy, "IMAGELESS"
//        attachments won't persist past the API entry point exit.
static std::pair<uint32_t, const VkImageView*> GetFramebufferAttachments(const VkRenderPassBeginInfo& rp_begin,
                                                                         const Framebuffer& fb_state) {
    const VkImageView* attachments = fb_state.create_info.pAttachments;
    uint32_t count = fb_state.create_info.attachmentCount;
    if (fb_state.create_info.flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) {
        const auto* framebuffer_attachments = vku::FindStructInPNextChain<VkRenderPassAttachmentBeginInfo>(rp_begin.pNext);
        if (framebuffer_attachments) {
            attachments = framebuffer_attachments->pAttachments;
            count = framebuffer_attachments->attachmentCount;
        }
    }
    return std::make_pair(count, attachments);
}

template <typename ImageViewPointer, typename Get>
std::vector<ImageViewPointer> GetAttachmentViewsImpl(const VkRenderPassBeginInfo& rp_begin, const Framebuffer& fb_state,
                                                     const Get& get_fn) {
    std::vector<ImageViewPointer> views;

    const auto count_attachment = GetFramebufferAttachments(rp_begin, fb_state);
    const auto attachment_count = count_attachment.first;
    const auto* attachments = count_attachment.second;
    views.resize(attachment_count, nullptr);
    for (uint32_t i = 0; i < attachment_count; i++) {
        if (attachments[i] != VK_NULL_HANDLE) {
            views[i] = get_fn(attachments[i]);
        }
    }
    return views;
}

std::vector<std::shared_ptr<const ImageView>> DeviceState::GetAttachmentViews(const VkRenderPassBeginInfo& rp_begin,
                                                                              const Framebuffer& fb_state) const {
    auto get_fn = [this](VkImageView handle) { return this->Get<ImageView>(handle); };
    return GetAttachmentViewsImpl<std::shared_ptr<const ImageView>>(rp_begin, fb_state, get_fn);
}

std::shared_ptr<vvl::ShaderModule> DeviceState::GetShaderModuleStateFromIdentifier(const VkShaderModuleIdentifierEXT& ident) const {
    ReadLockGuard guard(shader_identifier_map_lock_);
    if (const auto itr = shader_identifier_map_.find(ident); itr != shader_identifier_map_.cend()) {
        return itr->second;
    }
    return {};
}

std::shared_ptr<vvl::ShaderModule> DeviceState::GetShaderModuleStateFromIdentifier(
    const VkPipelineShaderStageModuleIdentifierCreateInfoEXT& shader_stage_id) const {
    if (shader_stage_id.pIdentifier) {
        VkShaderModuleIdentifierEXT shader_id = vku::InitStructHelper();
        shader_id.identifierSize = shader_stage_id.identifierSize;
        const uint32_t copy_size = std::min(VK_MAX_SHADER_MODULE_IDENTIFIER_SIZE_EXT, shader_stage_id.identifierSize);
        std::copy(shader_stage_id.pIdentifier, shader_stage_id.pIdentifier + copy_size, shader_id.identifier);
        ReadLockGuard guard(shader_identifier_map_lock_);
        if (const auto itr = shader_identifier_map_.find(shader_id); itr != shader_identifier_map_.cend()) {
            return itr->second;
        }
    }
    return {};
}

DescriptorSetLayoutId DeviceState::GetCanonicalId(const VkDescriptorSetLayoutCreateInfo* p_create_info) {
    return descriptor_set_layout_canonical_ids_.LookUp(DescriptorSetLayoutDef(*this, p_create_info));
}

#ifdef VK_USE_PLATFORM_ANDROID_KHR
// Android-specific validation that uses types defined only with VK_USE_PLATFORM_ANDROID_KHR
// This could also move into a seperate core_validation_android.cpp file... ?

VkFormatFeatureFlags2 DeviceState::GetExternalFormatFeaturesANDROID(const void* pNext) const {
    VkFormatFeatureFlags2 format_features = 0;
    const uint64_t external_format = GetExternalFormat(pNext);
    if ((0 != external_format)) {
        ReadLockGuard guard(ahb_lock_);
        // VUID 01894 will catch if not found in map
        auto it = ahb_ext_formats_map.find(external_format);
        if (it != ahb_ext_formats_map.end()) {
            format_features = it->second;
        }
    }
    return format_features;
}

VkFormat DeviceState::GetExternalFormatResolveANDROID(uint64_t external_format) const {
    ReadLockGuard guard(ahb_lock_);
    auto it = ahb_ext_resolve_formats_map.find(external_format);
    if (it != ahb_ext_resolve_formats_map.end()) {
        return it->second;
    }
    return VK_FORMAT_UNDEFINED;
}

void DeviceState::PostCallRecordGetAndroidHardwareBufferPropertiesANDROID(VkDevice device, const struct AHardwareBuffer* buffer,
                                                                          VkAndroidHardwareBufferPropertiesANDROID* pProperties,
                                                                          const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    WriteLockGuard guard(ahb_lock_);
    uint64_t external_format = 0;
    auto ahb_format_props2 = vku::FindStructInPNextChain<VkAndroidHardwareBufferFormatProperties2ANDROID>(pProperties->pNext);
    if (ahb_format_props2) {
        external_format = ahb_format_props2->externalFormat;
        ahb_ext_formats_map.emplace(external_format, ahb_format_props2->formatFeatures);
    } else {
        auto ahb_format_props = vku::FindStructInPNextChain<VkAndroidHardwareBufferFormatPropertiesANDROID>(pProperties->pNext);
        if (ahb_format_props) {
            external_format = ahb_format_props->externalFormat;
            ahb_ext_formats_map.emplace(external_format, static_cast<VkFormatFeatureFlags2>(ahb_format_props->formatFeatures));
        }
    }

    // For external format resolve, we need to also track externalFormat with its color attachment property
    if (enabled_features.externalFormatResolve) {
        auto ahb_format_resolve_props =
            vku::FindStructInPNextChain<VkAndroidHardwareBufferFormatResolvePropertiesANDROID>(pProperties->pNext);
        if (ahb_format_resolve_props && external_format != 0) {
            // easy case, caller provided both structs for us
            ahb_ext_resolve_formats_map.emplace(external_format, ahb_format_resolve_props->colorAttachmentFormat);
        } else {
            // If caller didn't provide both struct, re-call for them
            VkAndroidHardwareBufferFormatResolvePropertiesANDROID new_ahb_format_resolve_props = vku::InitStructHelper();
            VkAndroidHardwareBufferFormatPropertiesANDROID new_ahb_format_props =
                vku::InitStructHelper(&new_ahb_format_resolve_props);
            VkAndroidHardwareBufferPropertiesANDROID new_ahb_props = vku::InitStructHelper(&new_ahb_format_props);
            DispatchGetAndroidHardwareBufferPropertiesANDROID(device, buffer, &new_ahb_props);
            ahb_ext_resolve_formats_map.emplace(new_ahb_format_props.externalFormat,
                                                new_ahb_format_resolve_props.colorAttachmentFormat);
        }
    }
}

#else

VkFormatFeatureFlags2 DeviceState::GetExternalFormatFeaturesANDROID(const void* pNext) const {
    (void)pNext;
    return 0;
}

VkFormat DeviceState::GetExternalFormatResolveANDROID(uint64_t external_format) const {
    (void)external_format;
    return VK_FORMAT_UNDEFINED;
}

#endif  // VK_USE_PLATFORM_ANDROID_KHR

VkFormatFeatureFlags2 InstanceState::GetImageFormatFeatures(VkPhysicalDevice physical_device, bool has_format_feature2,
                                                            bool has_drm_modifiers, VkDevice device, VkImage image, VkFormat format,
                                                            VkImageTiling tiling) {
    VkFormatFeatureFlags2 format_features = 0;

    // Add feature support according to Image Format Features (vkspec.html#resources-image-format-features)
    // if format is AHB external format then the features are already set
    if (has_format_feature2) {
        VkDrmFormatModifierPropertiesList2EXT fmt_drm_props = vku::InitStructHelper();
        auto fmt_props_3 = vku::InitStruct<VkFormatProperties3>(has_drm_modifiers ? &fmt_drm_props : nullptr);
        VkFormatProperties2 fmt_props_2 = vku::InitStructHelper(&fmt_props_3);

        DispatchGetPhysicalDeviceFormatProperties2Helper(api_version, physical_device, format, &fmt_props_2);

        fmt_props_3.linearTilingFeatures |= fmt_props_2.formatProperties.linearTilingFeatures;
        fmt_props_3.optimalTilingFeatures |= fmt_props_2.formatProperties.optimalTilingFeatures;
        fmt_props_3.bufferFeatures |= fmt_props_2.formatProperties.bufferFeatures;

        if (tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) {
            VkImageDrmFormatModifierPropertiesEXT drm_format_props = vku::InitStructHelper();

            // If the driver returns zero, apparently that means nothing is valid
            // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11739
            // https://gitlab.khronos.org/vulkan/vulkan/-/issues/4710
            if (fmt_drm_props.drmFormatModifierCount == 0) {
                return format_features;
            }

            // Find the image modifier
            DispatchGetImageDrmFormatModifierPropertiesEXT(device, image, &drm_format_props);

            std::vector<VkDrmFormatModifierProperties2EXT> drm_mod_props;
            drm_mod_props.resize(fmt_drm_props.drmFormatModifierCount);
            fmt_drm_props.pDrmFormatModifierProperties = &drm_mod_props[0];

            // Second query to have all the modifiers filled
            DispatchGetPhysicalDeviceFormatProperties2Helper(api_version, physical_device, format, &fmt_props_2);

            // Look for the image modifier in the list
            for (uint32_t i = 0; i < fmt_drm_props.drmFormatModifierCount; i++) {
                if (fmt_drm_props.pDrmFormatModifierProperties[i].drmFormatModifier == drm_format_props.drmFormatModifier) {
                    format_features = fmt_drm_props.pDrmFormatModifierProperties[i].drmFormatModifierTilingFeatures;
                    break;
                }
            }
        } else {
            format_features =
                (tiling == VK_IMAGE_TILING_LINEAR) ? fmt_props_3.linearTilingFeatures : fmt_props_3.optimalTilingFeatures;
        }
    } else if (tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) {
        VkImageDrmFormatModifierPropertiesEXT drm_format_properties = vku::InitStructHelper();
        DispatchGetImageDrmFormatModifierPropertiesEXT(device, image, &drm_format_properties);

        VkFormatProperties2 format_properties_2 = vku::InitStructHelper();
        VkDrmFormatModifierPropertiesListEXT drm_properties_list = vku::InitStructHelper();
        format_properties_2.pNext = (void*)&drm_properties_list;
        DispatchGetPhysicalDeviceFormatProperties2Helper(api_version, physical_device, format, &format_properties_2);
        std::vector<VkDrmFormatModifierPropertiesEXT> drm_properties;
        drm_properties.resize(drm_properties_list.drmFormatModifierCount);
        drm_properties_list.pDrmFormatModifierProperties = &drm_properties[0];
        DispatchGetPhysicalDeviceFormatProperties2Helper(api_version, physical_device, format, &format_properties_2);

        for (uint32_t i = 0; i < drm_properties_list.drmFormatModifierCount; i++) {
            if (drm_properties_list.pDrmFormatModifierProperties[i].drmFormatModifier == drm_format_properties.drmFormatModifier) {
                format_features = drm_properties_list.pDrmFormatModifierProperties[i].drmFormatModifierTilingFeatures;
                break;
            }
        }
    } else {
        VkFormatProperties format_properties;
        DispatchGetPhysicalDeviceFormatProperties(physical_device, format, &format_properties);
        format_features =
            (tiling == VK_IMAGE_TILING_LINEAR) ? format_properties.linearTilingFeatures : format_properties.optimalTilingFeatures;
    }
    return format_features;
}

std::shared_ptr<Image> DeviceState::CreateImageState(VkImage handle, const VkImageCreateInfo* create_info,
                                                     VkFormatFeatureFlags2 features) {
    return std::make_shared<Image>(*this, handle, create_info, features);
}

std::shared_ptr<Image> DeviceState::CreateImageState(VkImage handle, const VkImageCreateInfo* create_info, VkSwapchainKHR swapchain,
                                                     uint32_t swapchain_index, VkFormatFeatureFlags2 features) {
    return std::make_shared<Image>(*this, handle, create_info, swapchain, swapchain_index, features);
}

void DeviceState::PostCallRecordCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo,
                                            const VkAllocationCallbacks* pAllocator, VkImage* pImage,
                                            const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    VkFormatFeatureFlags2 format_features = 0;
    if (IsExtEnabled(extensions.vk_android_external_memory_android_hardware_buffer)) {
        format_features = GetExternalFormatFeaturesANDROID(pCreateInfo->pNext);
    }
    if (format_features == 0) {
        format_features = instance_state->GetImageFormatFeatures(physical_device, special_supported.vk_khr_format_feature_flags2,
                                                                 IsExtEnabled(extensions.vk_ext_image_drm_format_modifier), device,
                                                                 *pImage, pCreateInfo->format, pCreateInfo->tiling);
    }
    Add(CreateImageState(*pImage, pCreateInfo, format_features));
}

void DeviceState::PreCallRecordDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator,
                                            const RecordObject& record_obj) {
    if (global_settings.descriptor_hashing) {
        if (auto image_state = Get<Image>(image)) {
            WriteLockGuard guard(descriptor_hashing->map_lock);
            for (uint64_t key : image_state->descriptor_hashes) {
                descriptor_hashing->table.Erase(key);
                descriptor_hashing->debug_names.erase(key);
            }
        }
    }
    Destroy<Image>(image);
}

void DeviceState::PostCallRecordCmdClearColorImage(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout,
                                                   const VkClearColorValue* pColor, uint32_t rangeCount,
                                                   const VkImageSubresourceRange* pRanges, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto image_state = Get<Image>(image);
    ASSERT_AND_RETURN(image_state);
    cb_state->AddChild(image_state);

    cb_state->RecordClearColorImage(*image_state, imageLayout, pColor, rangeCount, pRanges, record_obj.location);
}

void DeviceState::PostCallRecordCmdClearDepthStencilImage(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout,
                                                          const VkClearDepthStencilValue* pDepthStencil, uint32_t rangeCount,
                                                          const VkImageSubresourceRange* pRanges, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto image_state = Get<Image>(image);
    ASSERT_AND_RETURN(image_state);
    cb_state->AddChild(image_state);

    cb_state->RecordClearDepthStencilImage(*image_state, imageLayout, pDepthStencil, rangeCount, pRanges, record_obj.location);
}

void DeviceState::PostCallRecordCmdClearAttachments(VkCommandBuffer commandBuffer, uint32_t attachmentCount,
                                                    const VkClearAttachment* pAttachments, uint32_t rectCount,
                                                    const VkClearRect* pRects, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordClearAttachments(attachmentCount, pAttachments, rectCount, pRects, record_obj.location);
}

void DeviceState::PostCallRecordCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                             VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                             const VkImageCopy* pRegions, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_image_state = Get<Image>(srcImage);
    auto dst_image_state = Get<Image>(dstImage);
    ASSERT_AND_RETURN(src_image_state && dst_image_state);
    cb_state->AddChild(src_image_state);
    cb_state->AddChild(dst_image_state);
    cb_state->RecordCopyImage(*src_image_state, *dst_image_state, srcImageLayout, dstImageLayout, regionCount, pRegions,
                              record_obj.location);
}

void DeviceState::PostCallRecordCmdCopyImage2KHR(VkCommandBuffer commandBuffer, const VkCopyImageInfo2KHR* pCopyImageInfo,
                                                 const RecordObject& record_obj) {
    PostCallRecordCmdCopyImage2(commandBuffer, pCopyImageInfo, record_obj);
}

void DeviceState::PostCallRecordCmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo,
                                              const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_image_state = Get<Image>(pCopyImageInfo->srcImage);
    auto dst_image_state = Get<Image>(pCopyImageInfo->dstImage);
    ASSERT_AND_RETURN(src_image_state && dst_image_state);
    cb_state->AddChild(src_image_state);
    cb_state->AddChild(dst_image_state);
    cb_state->RecordCopyImage2(*src_image_state, *dst_image_state, pCopyImageInfo->srcImageLayout, pCopyImageInfo->dstImageLayout,
                               pCopyImageInfo->regionCount, pCopyImageInfo->pRegions, record_obj.location);
}

void DeviceState::PostCallRecordCmdResolveImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                                VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                                const VkImageResolve* pRegions, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_image_state = Get<Image>(srcImage);
    auto dst_image_state = Get<Image>(dstImage);
    ASSERT_AND_RETURN(src_image_state && dst_image_state);
    cb_state->AddChild(src_image_state);
    cb_state->AddChild(dst_image_state);

    cb_state->RecordResolveImage(*src_image_state, *dst_image_state, regionCount, pRegions, record_obj.location);
}

void DeviceState::PostCallRecordCmdResolveImage2KHR(VkCommandBuffer commandBuffer, const VkResolveImageInfo2KHR* pResolveImageInfo,
                                                    const RecordObject& record_obj) {
    PostCallRecordCmdResolveImage2(commandBuffer, pResolveImageInfo, record_obj);
}

void DeviceState::PostCallRecordCmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo,
                                                 const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_image_state = Get<Image>(pResolveImageInfo->srcImage);
    auto dst_image_state = Get<Image>(pResolveImageInfo->dstImage);
    ASSERT_AND_RETURN(src_image_state && dst_image_state);
    cb_state->AddChild(src_image_state);
    cb_state->AddChild(dst_image_state);

    cb_state->RecordResolveImage2(*src_image_state, *dst_image_state, pResolveImageInfo->regionCount, pResolveImageInfo->pRegions,
                                  record_obj.location);
}

void DeviceState::PostCallRecordCmdBlitImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                             VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                             const VkImageBlit* pRegions, VkFilter filter, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_image_state = Get<Image>(srcImage);
    auto dst_image_state = Get<Image>(dstImage);
    ASSERT_AND_RETURN(src_image_state && dst_image_state);
    cb_state->AddChild(src_image_state);
    cb_state->AddChild(dst_image_state);

    cb_state->RecordBlitImage(*src_image_state, *dst_image_state, srcImageLayout, dstImageLayout, regionCount, pRegions,
                              record_obj.location);
}

void DeviceState::PostCallRecordCmdBlitImage2KHR(VkCommandBuffer commandBuffer, const VkBlitImageInfo2KHR* pBlitImageInfo,
                                                 const RecordObject& record_obj) {
    PostCallRecordCmdBlitImage2(commandBuffer, pBlitImageInfo, record_obj);
}

void DeviceState::PostCallRecordCmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo,
                                              const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_image_state = Get<Image>(pBlitImageInfo->srcImage);
    auto dst_image_state = Get<Image>(pBlitImageInfo->dstImage);
    ASSERT_AND_RETURN(src_image_state && dst_image_state);
    cb_state->AddChild(src_image_state);
    cb_state->AddChild(dst_image_state);

    cb_state->RecordBlitImage2(*src_image_state, *dst_image_state, pBlitImageInfo->srcImageLayout, pBlitImageInfo->dstImageLayout,
                               pBlitImageInfo->regionCount, pBlitImageInfo->pRegions, record_obj.location);
}

struct BufferAddressInfillUpdateOps {
    using Map = typename DeviceState::BufferAddressRangeMap;
    using Iterator = typename Map::iterator;
    using Value = typename Map::value_type;
    using Mapped = typename Map::mapped_type;
    using Range = typename Map::key_type;
    void infill(Map& map, const Iterator& pos, const Range& infill_range) const {
        map.insert(pos, Value(infill_range, insert_value));
    }
    void update(const Iterator& pos) const {
        auto& current_buffer_list = pos->second;
        assert(!current_buffer_list.empty());
        const auto buffer_found_it = std::find(current_buffer_list.begin(), current_buffer_list.end(), insert_value[0]);
        if (buffer_found_it == current_buffer_list.end()) {
            if (current_buffer_list.capacity() <= (current_buffer_list.size() + 1)) {
                current_buffer_list.reserve(current_buffer_list.capacity() * 2);
            }
            current_buffer_list.emplace_back(insert_value[0]);
        }
    }
    const Mapped& insert_value;
};

std::shared_ptr<Buffer> DeviceState::CreateBufferState(VkBuffer handle, const VkBufferCreateInfo* create_info) {
    return std::make_shared<Buffer>(*this, handle, create_info);
}

std::shared_ptr<vvl::Tensor> DeviceState::CreateTensorState(VkTensorARM handle, const VkTensorCreateInfoARM* create_info) {
    return std::make_shared<vvl::Tensor>(*this, handle, create_info);
}

void DeviceState::PostCallRecordCreateTensorARM(VkDevice device, const VkTensorCreateInfoARM* pCreateInfo,
                                                const VkAllocationCallbacks* pAllocator, VkTensorARM* pTensor,
                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    std::shared_ptr<vvl::Tensor> tensor_state = CreateTensorState(*pTensor, pCreateInfo);
    Add(std::move(tensor_state));
}

void DeviceState::PreCallRecordDestroyTensorARM(VkDevice device, VkTensorARM tensor, const VkAllocationCallbacks* pAllocator,
                                                const RecordObject& record_obj) {
    Destroy<Tensor>(tensor);
}

void DeviceState::PostCallRecordBindTensorMemoryARM(VkDevice device, uint32_t bindInfoCount,
                                                    const VkBindTensorMemoryInfoARM* pBindInfos, const RecordObject& record_obj) {
    if (VK_SUCCESS != record_obj.result) {
        return;
    }
    for (uint32_t i = 0; i < bindInfoCount; i++) {
        auto tensor_state = Get<vvl::Tensor>(pBindInfos[i].tensor);
        auto mem_info = Get<vvl::DeviceMemory>(pBindInfos[i].memory);
        ASSERT_AND_RETURN(tensor_state && mem_info);
        tensor_state->BindMemory(tensor_state.get(), mem_info, pBindInfos[i].memoryOffset, 0u,
                                 tensor_state->MemReqs()->memoryRequirements.size);
    }
}

void DeviceState::PostCallRecordCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo,
                                             const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer,
                                             const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    std::shared_ptr<Buffer> buffer_state = CreateBufferState(*pBuffer, pCreateInfo);

    const auto* opaque_capture_address = vku::FindStructInPNextChain<VkBufferOpaqueCaptureAddressCreateInfo>(pCreateInfo->pNext);
    if (opaque_capture_address && (opaque_capture_address->opaqueCaptureAddress != 0)) {
        WriteLockGuard guard(buffer_address_lock_);
        // address is used for GPU-AV and ray tracing buffer validation
        buffer_state->deviceAddress = opaque_capture_address->opaqueCaptureAddress;
        const vvl::range<VkDeviceAddress> address_range = buffer_state->DeviceAddressRange();

        BufferAddressInfillUpdateOps ops{{buffer_state.get()}};
        sparse_container::infill_update_range(buffer_address_map_, address_range, ops);
    }

    RecordCreateDescriptorBuffer(*buffer_state, *pCreateInfo);

    Add(std::move(buffer_state));
}

std::shared_ptr<BufferView> DeviceState::CreateBufferViewState(const std::shared_ptr<Buffer>& buffer, VkBufferView handle,
                                                               const VkBufferViewCreateInfo* create_info,
                                                               VkFormatFeatureFlags2 format_features) {
    return std::make_shared<BufferView>(buffer, handle, create_info, format_features);
}

void DeviceState::PostCallRecordCreateBufferView(VkDevice device, const VkBufferViewCreateInfo* pCreateInfo,
                                                 const VkAllocationCallbacks* pAllocator, VkBufferView* pView,
                                                 const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto buffer_state = Get<Buffer>(pCreateInfo->buffer);

    VkFormatFeatureFlags2 buffer_features;
    if (special_supported.vk_khr_format_feature_flags2) {
        VkFormatProperties3 fmt_props_3 = vku::InitStructHelper();
        VkFormatProperties2 fmt_props_2 = vku::InitStructHelper(&fmt_props_3);
        DispatchGetPhysicalDeviceFormatProperties2Helper(api_version, physical_device, pCreateInfo->format, &fmt_props_2);
        buffer_features = fmt_props_3.bufferFeatures | fmt_props_2.formatProperties.bufferFeatures;
    } else {
        VkFormatProperties format_properties;
        DispatchGetPhysicalDeviceFormatProperties(physical_device, pCreateInfo->format, &format_properties);
        buffer_features = format_properties.bufferFeatures;
    }

    Add(CreateBufferViewState(buffer_state, *pView, pCreateInfo, buffer_features));
}

std::shared_ptr<vvl::DataGraphPipelineSession> DeviceState::CreateDataGraphPipelineSessionState(
    VkDataGraphPipelineSessionARM handle, const VkDataGraphPipelineSessionCreateInfoARM* pCreateInfo) {
    return std::make_shared<vvl::DataGraphPipelineSession>(*this, handle, pCreateInfo);
}

void DeviceState::PostCallRecordCreateDataGraphPipelineSessionARM(VkDevice device,
                                                                  const VkDataGraphPipelineSessionCreateInfoARM* pCreateInfo,
                                                                  const VkAllocationCallbacks* pAllocator,
                                                                  VkDataGraphPipelineSessionARM* pSession,
                                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    std::shared_ptr<vvl::DataGraphPipelineSession> pipeline_session_state =
        CreateDataGraphPipelineSessionState(*pSession, pCreateInfo);
    Add(std::move(pipeline_session_state));
}

void DeviceState::PostCallRecordBindDataGraphPipelineSessionMemoryARM(VkDevice device, uint32_t bindInfoCount,
                                                                      const VkBindDataGraphPipelineSessionMemoryInfoARM* pBindInfos,
                                                                      const RecordObject& record_obj) {
    if (VK_SUCCESS != record_obj.result) {
        return;
    }
    for (uint32_t i = 0; i < bindInfoCount; i++) {
        auto& bind_info = pBindInfos[i];
        auto session_state = Get<vvl::DataGraphPipelineSession>(bind_info.session);
        if (session_state) {
            auto mem_info = std::shared_ptr<vvl::DeviceMemory>(Get<vvl::DeviceMemory>(bind_info.memory));
            vvl::MemoryBinding binding = {mem_info, bind_info.memoryOffset, 0};
            session_state->AddBoundMemory(bind_info.bindPoint, binding);
        }
    }
}

std::shared_ptr<ImageView> DeviceState::CreateImageViewState(const std::shared_ptr<Image>& image_state, VkImageView handle,
                                                             const VkImageViewCreateInfo* create_info,
                                                             VkFormatFeatureFlags2 format_features,
                                                             const VkFilterCubicImageViewImageFormatPropertiesEXT& cubic_props) {
    return std::make_shared<ImageView>(*this, image_state, handle, create_info, format_features, cubic_props);
}

void DeviceState::PostCallRecordCreateImageView(VkDevice device, const VkImageViewCreateInfo* pCreateInfo,
                                                const VkAllocationCallbacks* pAllocator, VkImageView* pView,
                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto image_state = Get<Image>(pCreateInfo->image);
    ASSERT_AND_RETURN(image_state);

    VkFormatFeatureFlags2 format_features = 0;
    if (image_state->HasAHBFormat() == true) {
        // The ImageView uses same Image's format feature since they share same AHB
        format_features = image_state->format_features;
    } else {
        format_features =
            instance_state->GetImageFormatFeatures(physical_device, special_supported.vk_khr_format_feature_flags2,
                                                   IsExtEnabled(extensions.vk_ext_image_drm_format_modifier), device,
                                                   image_state->VkHandle(), pCreateInfo->format, image_state->GetTiling());
    }

    // filter_cubic_props is used in CmdDraw validation. But it takes a lot of performance if it does in CmdDraw.
    VkFilterCubicImageViewImageFormatPropertiesEXT filter_cubic_props = vku::InitStructHelper();
    if (IsExtEnabled(extensions.vk_ext_filter_cubic)) {
        VkPhysicalDeviceImageViewImageFormatInfoEXT imageview_format_info = vku::InitStructHelper();
        imageview_format_info.imageViewType = pCreateInfo->viewType;
        VkPhysicalDeviceImageFormatInfo2 image_format_info = vku::InitStructHelper(&imageview_format_info);
        image_format_info.type = image_state->GetImageType();
        image_format_info.format = image_state->GetFormat();
        image_format_info.tiling = image_state->GetTiling();
        auto usage_create_info = vku::FindStructInPNextChain<VkImageViewUsageCreateInfo>(pCreateInfo->pNext);
        image_format_info.usage = usage_create_info ? usage_create_info->usage : (VkImageUsageFlags)image_state->usage;
        image_format_info.flags = (VkImageCreateFlags)image_state->create_flags;

        VkImageFormatProperties2 image_format_properties = vku::InitStructHelper(&filter_cubic_props);

        DispatchGetPhysicalDeviceImageFormatProperties2Helper(api_version, physical_device, &image_format_info,
                                                              &image_format_properties);
    }

    Add(CreateImageViewState(image_state, *pView, pCreateInfo, format_features, filter_cubic_props));
}

void DeviceState::PostCallRecordCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer,
                                              uint32_t regionCount, const VkBufferCopy* pRegions, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_buffer_state = Get<Buffer>(srcBuffer);
    auto dst_buffer_state = Get<Buffer>(dstBuffer);
    ASSERT_AND_RETURN(src_buffer_state && dst_buffer_state);
    cb_state->AddChild(src_buffer_state);
    cb_state->AddChild(dst_buffer_state);
    cb_state->RecordCopyBuffer(*src_buffer_state, *dst_buffer_state, regionCount, pRegions, record_obj.location);
}

void DeviceState::PostCallRecordCmdCopyTensorARM(VkCommandBuffer commandBuffer, const VkCopyTensorInfoARM* pCopyTensorInfo,
                                                 const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_tensor_state = Get<Tensor>(pCopyTensorInfo->srcTensor);
    auto dst_tensor_state = Get<Tensor>(pCopyTensorInfo->dstTensor);
    ASSERT_AND_RETURN(src_tensor_state && dst_tensor_state);
    cb_state->AddChild(src_tensor_state);
    cb_state->AddChild(dst_tensor_state);
}

std::shared_ptr<vvl::TensorView> DeviceState::CreateTensorViewState(const std::shared_ptr<vvl::Tensor>& tensor,
                                                                    VkTensorViewARM handle,
                                                                    const VkTensorViewCreateInfoARM* pCreateInfo) {
    return std::make_shared<vvl::TensorView>(tensor, handle, pCreateInfo);
}

void DeviceState::PostCallRecordCreateTensorViewARM(VkDevice device, const VkTensorViewCreateInfoARM* pCreateInfo,
                                                    const VkAllocationCallbacks* pAllocator, VkTensorViewARM* pView,
                                                    const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    auto tensor_state = Get<vvl::Tensor>(pCreateInfo->tensor);
    ASSERT_AND_RETURN(tensor_state);
    Add(CreateTensorViewState(tensor_state, *pView, pCreateInfo));
}

void DeviceState::PreCallRecordDestroyTensorViewARM(VkDevice device, VkTensorViewARM tensorView,
                                                    const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<TensorView>(tensorView);
}

void DeviceState::PostCallRecordCmdCopyBuffer2KHR(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2KHR* pCopyBufferInfo,
                                                  const RecordObject& record_obj) {
    PostCallRecordCmdCopyBuffer2(commandBuffer, pCopyBufferInfo, record_obj);
}

void DeviceState::PostCallRecordCmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo,
                                               const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_buffer_state = Get<Buffer>(pCopyBufferInfo->srcBuffer);
    auto dst_buffer_state = Get<Buffer>(pCopyBufferInfo->dstBuffer);
    ASSERT_AND_RETURN(src_buffer_state && dst_buffer_state);
    cb_state->AddChild(src_buffer_state);
    cb_state->AddChild(dst_buffer_state);
    cb_state->RecordCopyBuffer2(*src_buffer_state, *dst_buffer_state, pCopyBufferInfo->regionCount, pCopyBufferInfo->pRegions,
                                record_obj.location);
}

void DeviceState::PreCallRecordDestroyImageView(VkDevice device, VkImageView imageView, const VkAllocationCallbacks* pAllocator,
                                                const RecordObject& record_obj) {
    Destroy<ImageView>(imageView);
}

void DeviceState::PreCallRecordDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator,
                                             const RecordObject& record_obj) {
    if (auto buffer_state = Get<Buffer>(buffer)) {
        WriteLockGuard guard(buffer_address_lock_);

        RecordDestoryDescriptorBuffer(*buffer_state);

        if (buffer_state->deviceAddress != 0) {
            const vvl::range<VkDeviceAddress> address_range = buffer_state->DeviceAddressRange();

            buffer_address_map_.erase_range_or_touch(address_range, [buffer_state_raw = buffer_state.get()](auto& buffers) {
                assert(!buffers.empty());
                const auto buffer_found_it = std::find(buffers.begin(), buffers.end(), buffer_state_raw);
                assert(buffer_found_it != buffers.end());

                // If buffer list only has one element, remove range map entry.
                // Else, remove target buffer from buffer list.
                if (buffer_found_it != buffers.end()) {
                    if (buffers.size() == 1) {
                        return true;
                    } else {
                        assert(!buffers.empty());
                        const size_t i = std::distance(buffers.begin(), buffer_found_it);
                        std::swap(buffers[i], buffers[buffers.size() - 1]);
                        buffers.resize(buffers.size() - 1);
                        return false;
                    }
                }

                return false;
            });
        }

        // TODO - mix with the |buffer_address_map_| logic to handle multiple buffers on the same address
        if (global_settings.descriptor_hashing) {
            WriteLockGuard hash_guard(descriptor_hashing->map_lock);
            for (uint64_t key : buffer_state->descriptor_hashes) {
                descriptor_hashing->table.Erase(key);
                descriptor_hashing->debug_names.erase(key);
            }
        }
    }
    Destroy<Buffer>(buffer);
}

void DeviceState::PreCallRecordDestroyBufferView(VkDevice device, VkBufferView bufferView, const VkAllocationCallbacks* pAllocator,
                                                 const RecordObject& record_obj) {
    Destroy<BufferView>(bufferView);
}

static constexpr VkBufferUsageFlags2 kDescriptorBufferUsages =
    VK_BUFFER_USAGE_2_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_2_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT;

void DeviceState::RecordCreateDescriptorBuffer(const vvl::Buffer& buffer_state, const VkBufferCreateInfo& create_info) {
    if ((buffer_state.usage & kDescriptorBufferUsages) != 0) {
        const VkDeviceSize size = create_info.size;
        descriptor_buffer_address_space.all += size;

        if ((buffer_state.usage & VK_BUFFER_USAGE_2_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT) != 0) {
            descriptor_buffer_address_space.resource += size;
        }

        if ((buffer_state.usage & VK_BUFFER_USAGE_2_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT) != 0) {
            descriptor_buffer_address_space.sampler += size;
        }
    }
}

void DeviceState::RecordDestoryDescriptorBuffer(const vvl::Buffer& buffer_state) {
    if ((buffer_state.usage & kDescriptorBufferUsages) != 0) {
        const VkDeviceSize size = buffer_state.GetSize();
        descriptor_buffer_address_space.all -= size;

        if (buffer_state.usage & VK_BUFFER_USAGE_2_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT) {
            descriptor_buffer_address_space.resource -= size;
        }

        if (buffer_state.usage & VK_BUFFER_USAGE_2_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT) {
            descriptor_buffer_address_space.sampler -= size;
        }
    }
}

void DeviceState::PostCallRecordCmdFillBuffer(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset,
                                              VkDeviceSize size, uint32_t data, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto buffer_state = Get<Buffer>(dstBuffer);
    ASSERT_AND_RETURN(buffer_state);
    cb_state->AddChild(buffer_state);
    cb_state->RecordFillBuffer(*buffer_state, dstOffset, size, record_obj.location);
}

void DeviceState::PostCallRecordCmdFillMemoryKHR(VkCommandBuffer commandBuffer, const VkDeviceAddressRangeKHR* pDstRange,
                                                 VkAddressCommandFlagsKHR dstFlags, uint32_t data, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordFillMemory(*pDstRange, record_obj.location);

    TrackDeviceAddressRange(*cb_state, pDstRange->address, pDstRange->size, VK_BUFFER_USAGE_2_TRANSFER_DST_BIT);
}

void DeviceState::PostCallRecordCmdCopyImageToBuffer(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                                     VkBuffer dstBuffer, uint32_t regionCount, const VkBufferImageCopy* pRegions,
                                                     const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_image_state = Get<Image>(srcImage);
    auto dst_buffer_state = Get<Buffer>(dstBuffer);
    ASSERT_AND_RETURN(src_image_state && dst_buffer_state);
    cb_state->AddChild(src_image_state);
    cb_state->AddChild(dst_buffer_state);

    cb_state->RecordCopyImageToBuffer(*src_image_state, *dst_buffer_state, srcImageLayout, regionCount, pRegions,
                                      record_obj.location);
}

void DeviceState::PostCallRecordCmdCopyImageToBuffer2KHR(VkCommandBuffer commandBuffer,
                                                         const VkCopyImageToBufferInfo2KHR* pCopyImageToBufferInfo,
                                                         const RecordObject& record_obj) {
    PostCallRecordCmdCopyImageToBuffer2(commandBuffer, pCopyImageToBufferInfo, record_obj);
}

void DeviceState::PostCallRecordCmdCopyImageToBuffer2(VkCommandBuffer commandBuffer,
                                                      const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo,
                                                      const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_image_state = Get<Image>(pCopyImageToBufferInfo->srcImage);
    auto dst_buffer_state = Get<Buffer>(pCopyImageToBufferInfo->dstBuffer);
    ASSERT_AND_RETURN(src_image_state && dst_buffer_state);
    cb_state->AddChild(src_image_state);
    cb_state->AddChild(dst_buffer_state);

    cb_state->RecordCopyImageToBuffer2(*src_image_state, *dst_buffer_state, pCopyImageToBufferInfo->srcImageLayout,
                                       pCopyImageToBufferInfo->regionCount, pCopyImageToBufferInfo->pRegions, record_obj.location);
}

void DeviceState::PostCallRecordCmdCopyMemoryToImageKHR(VkCommandBuffer commandBuffer,
                                                        const VkCopyDeviceMemoryImageInfoKHR* pCopyMemoryInfo,
                                                        const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto dst_image_state = Get<Image>(pCopyMemoryInfo->image);
    ASSERT_AND_RETURN(dst_image_state);
    cb_state->AddChild(dst_image_state);

    cb_state->RecordCopyMemoryToImage(*dst_image_state, pCopyMemoryInfo->regionCount, pCopyMemoryInfo->pRegions,
                                      record_obj.location);
    for (uint32_t i = 0; i < pCopyMemoryInfo->regionCount; ++i) {
        TrackDeviceAddressRange(*cb_state, pCopyMemoryInfo->pRegions[i].addressRange.address,
                                pCopyMemoryInfo->pRegions[i].addressRange.size, VK_BUFFER_USAGE_2_TRANSFER_SRC_BIT);
    }
}

void DeviceState::PostCallRecordCmdCopyImageToMemoryKHR(VkCommandBuffer commandBuffer,
                                                        const VkCopyDeviceMemoryImageInfoKHR* pCopyMemoryInfo,
                                                        const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_image_state = Get<Image>(pCopyMemoryInfo->image);
    ASSERT_AND_RETURN(src_image_state);
    cb_state->AddChild(src_image_state);

    cb_state->RecordCopyImageToMemory(*src_image_state, pCopyMemoryInfo->regionCount, pCopyMemoryInfo->pRegions,
                                      record_obj.location);
    for (uint32_t i = 0; i < pCopyMemoryInfo->regionCount; ++i) {
        TrackDeviceAddressRange(*cb_state, pCopyMemoryInfo->pRegions[i].addressRange.address,
                                pCopyMemoryInfo->pRegions[i].addressRange.size, VK_BUFFER_USAGE_2_TRANSFER_DST_BIT);
    }
}

void DeviceState::PostCallRecordCmdCopyMemoryKHR(VkCommandBuffer commandBuffer, const VkCopyDeviceMemoryInfoKHR* pCopyMemoryInfo,
                                                 const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCopyMemory(pCopyMemoryInfo->regionCount, pCopyMemoryInfo->pRegions, record_obj.location);
    for (uint32_t i = 0; i < pCopyMemoryInfo->regionCount; ++i) {
        TrackDeviceAddressRange(*cb_state, pCopyMemoryInfo->pRegions[i].dstRange.address,
                                pCopyMemoryInfo->pRegions[i].dstRange.size, VK_BUFFER_USAGE_2_TRANSFER_SRC_BIT);
    }
}

void DeviceState::PostCallRecordCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage,
                                                     VkImageLayout dstImageLayout, uint32_t regionCount,
                                                     const VkBufferImageCopy* pRegions, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_buffer_state = Get<Buffer>(srcBuffer);
    auto dst_image_state = Get<Image>(dstImage);
    ASSERT_AND_RETURN(src_buffer_state && dst_image_state);
    cb_state->AddChild(src_buffer_state);
    cb_state->AddChild(dst_image_state);

    cb_state->RecordCopyBufferToImage(*src_buffer_state, *dst_image_state, dstImageLayout, regionCount, pRegions,
                                      record_obj.location);
}

void DeviceState::PostCallRecordCmdCopyBufferToImage2KHR(VkCommandBuffer commandBuffer,
                                                         const VkCopyBufferToImageInfo2KHR* pCopyBufferToImageInfo,
                                                         const RecordObject& record_obj) {
    PostCallRecordCmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo, record_obj);
}

void DeviceState::PostCallRecordCmdCopyBufferToImage2(VkCommandBuffer commandBuffer,
                                                      const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo,
                                                      const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_buffer_state = Get<Buffer>(pCopyBufferToImageInfo->srcBuffer);
    auto dst_image_state = Get<Image>(pCopyBufferToImageInfo->dstImage);
    ASSERT_AND_RETURN(src_buffer_state && dst_image_state);
    cb_state->AddChild(src_buffer_state);
    cb_state->AddChild(dst_image_state);

    cb_state->RecordCopyBufferToImage2(*src_buffer_state, *dst_image_state, pCopyBufferToImageInfo->dstImageLayout,
                                       pCopyBufferToImageInfo->regionCount, pCopyBufferToImageInfo->pRegions, record_obj.location);
}

// Gets union of all features defined by Potential Format Features
// except, does not handle the external format case for AHB as that only can be used for sampled images
VkFormatFeatureFlags2 DeviceState::GetPotentialFormatFeatures(VkFormat format) const {
    VkFormatFeatureFlags2 format_features = 0;

    if (format != VK_FORMAT_UNDEFINED) {
        if (special_supported.vk_khr_format_feature_flags2) {
            VkDrmFormatModifierPropertiesList2EXT fmt_drm_props = vku::InitStructHelper();
            auto fmt_props_3 = vku::InitStruct<VkFormatProperties3>(
                IsExtEnabled(extensions.vk_ext_image_drm_format_modifier) ? &fmt_drm_props : nullptr);
            VkFormatProperties2 fmt_props_2 = vku::InitStructHelper(&fmt_props_3);

            DispatchGetPhysicalDeviceFormatProperties2Helper(api_version, physical_device, format, &fmt_props_2);

            format_features |= fmt_props_2.formatProperties.linearTilingFeatures;
            format_features |= fmt_props_2.formatProperties.optimalTilingFeatures;

            format_features |= fmt_props_3.linearTilingFeatures;
            format_features |= fmt_props_3.optimalTilingFeatures;

            if (IsExtEnabled(extensions.vk_ext_image_drm_format_modifier)) {
                std::vector<VkDrmFormatModifierProperties2EXT> drm_properties;
                drm_properties.resize(fmt_drm_props.drmFormatModifierCount);
                fmt_drm_props.pDrmFormatModifierProperties = drm_properties.data();
                DispatchGetPhysicalDeviceFormatProperties2Helper(api_version, physical_device, format, &fmt_props_2);

                for (uint32_t i = 0; i < fmt_drm_props.drmFormatModifierCount; i++) {
                    format_features |= fmt_drm_props.pDrmFormatModifierProperties[i].drmFormatModifierTilingFeatures;
                }
            }
        } else {
            VkFormatProperties format_properties;
            DispatchGetPhysicalDeviceFormatProperties(physical_device, format, &format_properties);
            format_features |= format_properties.linearTilingFeatures;
            format_features |= format_properties.optimalTilingFeatures;

            if (IsExtEnabled(extensions.vk_ext_image_drm_format_modifier)) {
                VkDrmFormatModifierPropertiesListEXT fmt_drm_props = vku::InitStructHelper();
                VkFormatProperties2 fmt_props_2 = vku::InitStructHelper(&fmt_drm_props);

                DispatchGetPhysicalDeviceFormatProperties2Helper(api_version, physical_device, format, &fmt_props_2);

                std::vector<VkDrmFormatModifierPropertiesEXT> drm_properties;
                drm_properties.resize(fmt_drm_props.drmFormatModifierCount);
                fmt_drm_props.pDrmFormatModifierProperties = drm_properties.data();
                DispatchGetPhysicalDeviceFormatProperties2Helper(api_version, physical_device, format, &fmt_props_2);

                for (uint32_t i = 0; i < fmt_drm_props.drmFormatModifierCount; i++) {
                    format_features |= fmt_drm_props.pDrmFormatModifierProperties[i].drmFormatModifierTilingFeatures;
                }
            }
        }
    }

    return format_features;
}

std::shared_ptr<Queue> DeviceState::CreateQueue(VkQueue handle, uint32_t family_index, uint32_t queue_index,
                                                VkDeviceQueueCreateFlags flags,
                                                const VkQueueFamilyProperties& queueFamilyProperties) {
    return std::make_shared<Queue>(*this, handle, family_index, queue_index, flags, queueFamilyProperties);
}

vvl::unordered_set<VkFormat> DeviceState::GetOpticalFlowFormats(const uint32_t queue_index,
                                                                const VkPhysicalDeviceDataGraphProcessingEngineARM engine,
                                                                const VkDataGraphOpticalFlowImageUsageFlagsARM usage) {
    // make sure we have filled this in
    assert(!physical_device_state->data_graph.queue_family_properties.empty());

    VkDataGraphOpticalFlowImageFormatInfoARM format_info = vku::InitStructHelper();
    format_info.usage = usage;
    vvl::unordered_set<VkFormat> formats;  // set because we might have duplications between queue families
    for (const auto& prop : physical_device_state->data_graph.queue_family_properties[queue_index]) {
        if (prop.operation.operationType != VK_PHYSICAL_DEVICE_DATA_GRAPH_OPERATION_TYPE_OPTICAL_FLOW_ARM ||
            prop.engine.type != engine.type || prop.engine.isForeign != engine.isForeign) {
            continue;
        }

        uint32_t format_count = 0;
        [[maybe_unused]] VkResult result = DispatchGetPhysicalDeviceQueueFamilyDataGraphOpticalFlowImageFormatsARM(
            physical_device, queue_index, &prop, &format_info, &format_count, nullptr);
        assert(VK_SUCCESS == result);
        std::vector<VkDataGraphOpticalFlowImageFormatPropertiesARM> format_properties(format_count,
            vku::InitStruct<VkDataGraphOpticalFlowImageFormatPropertiesARM>());
        result = DispatchGetPhysicalDeviceQueueFamilyDataGraphOpticalFlowImageFormatsARM(
            physical_device, queue_index, &prop, &format_info, &format_count, format_properties.data());
        assert(VK_SUCCESS == result);

        for (auto& fp : format_properties) {
            formats.insert(fp.format);
        }
    }
    return formats;
}

void DeviceState::FinishDeviceSetup(const VkDeviceCreateInfo* pCreateInfo, const Location& loc) {
    const auto* device_group_ci = vku::FindStructInPNextChain<VkDeviceGroupDeviceCreateInfo>(pCreateInfo->pNext);
    if (device_group_ci) {
        physical_device_count = device_group_ci->physicalDeviceCount;
        if (physical_device_count == 0) {
            physical_device_count =
                1;  // see https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDeviceGroupDeviceCreateInfo.html
        }
        device_group_create_info = *device_group_ci;
    } else {
        device_group_create_info = vku::InitStructHelper();
        device_group_create_info.physicalDeviceCount = 1;  // see previous VkDeviceGroupDeviceCreateInfo link
        device_group_create_info.pPhysicalDevices = &physical_device;
        physical_device_count = 1;
    }

    uint32_t num_queue_families = 0;
    DispatchGetPhysicalDeviceQueueFamilyProperties(physical_device, &num_queue_families, nullptr);

    // Store queue family data
    if (pCreateInfo->pQueueCreateInfos != nullptr) {
        std::vector<VkQueueFamilyProperties> queue_family_properties_list(num_queue_families);
        DispatchGetPhysicalDeviceQueueFamilyProperties(physical_device, &num_queue_families, queue_family_properties_list.data());

        for (uint32_t i = 0; i < pCreateInfo->queueCreateInfoCount; ++i) {
            const VkDeviceQueueCreateInfo& queue_create_info = pCreateInfo->pQueueCreateInfos[i];
            queue_family_index_set.insert(queue_create_info.queueFamilyIndex);
            device_queue_info_list.emplace_back(
                DeviceQueueInfo{i, queue_create_info.queueFamilyIndex, queue_create_info.flags, queue_create_info.queueCount});
        }
        for (const auto& queue_info : device_queue_info_list) {
            for (uint32_t i = 0; i < queue_info.queue_count; i++) {
                VkQueue queue = VK_NULL_HANDLE;
                // vkGetDeviceQueue2() was added in vulkan 1.1, and there was never a KHR version of it.
                if (api_version >= VK_API_VERSION_1_1 && queue_info.flags != 0) {
                    VkDeviceQueueInfo2 get_info = vku::InitStructHelper();
                    get_info.flags = queue_info.flags;
                    get_info.queueFamilyIndex = queue_info.queue_family_index;
                    get_info.queueIndex = i;
                    DispatchGetDeviceQueue2(device, &get_info, &queue);
                } else {
                    DispatchGetDeviceQueue(device, queue_info.queue_family_index, i, &queue);
                }
                assert(queue != VK_NULL_HANDLE);
                Add(CreateQueue(queue, queue_info.queue_family_index, i, queue_info.flags,
                                queue_family_properties_list[queue_info.queue_family_index]));
            }
        }
    }

    // Query queue family extension properties
    if (IsExtEnabled(extensions.vk_khr_get_physical_device_properties2)) {
        uint32_t queue_family_count = (uint32_t)physical_device_state->queue_family_properties.size();
        auto& ext_props = queue_family_ext_props;
        ext_props.resize(queue_family_count);

        std::vector<VkQueueFamilyProperties2> props(queue_family_count, vku::InitStruct<VkQueueFamilyProperties2>());

        if (IsExtEnabled(extensions.vk_khr_video_queue)) {
            for (uint32_t i = 0; i < queue_family_count; ++i) {
                ext_props[i].query_result_status_props = vku::InitStructHelper();
                ext_props[i].video_props = vku::InitStructHelper(&ext_props[i].query_result_status_props);
                props[i].pNext = &ext_props[i].video_props;
            }
        }

        DispatchGetPhysicalDeviceQueueFamilyProperties2Helper(api_version, physical_device, &queue_family_count, props.data());
    }

    if (IsExtEnabled(extensions.vk_khr_performance_query)) {
        uint32_t queue_family_count = (uint32_t)physical_device_state->queue_family_properties.size();
        for (uint32_t i = 0; i < queue_family_count; ++i) {
            uint32_t counterCount;
            DispatchEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR(physical_device, i, &counterCount, nullptr,
                                                                                  nullptr);

            std::unique_ptr<QueueFamilyPerfCounters> queue_family_counters(new QueueFamilyPerfCounters());
            queue_family_counters->counters.resize(counterCount);

            DispatchEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR(physical_device, i, &counterCount,
                                                                                  queue_family_counters->counters.data(), nullptr);

            physical_device_state->perf_counters[i] = std::move(queue_family_counters);
        }
    }

    // internal pipeline cache control
    const auto* cache_control = vku::FindStructInPNextChain<VkDevicePipelineBinaryInternalCacheControlKHR>(pCreateInfo->pNext);
    disable_internal_pipeline_cache = cache_control && cache_control->disableInternalCache;

    if (IsExtEnabled(extensions.vk_nv_cooperative_matrix)) {
        uint32_t num_cooperative_matrix_properties_nv = 0;
        DispatchGetPhysicalDeviceCooperativeMatrixPropertiesNV(physical_device, &num_cooperative_matrix_properties_nv, NULL);
        cooperative_matrix_properties_nv.resize(num_cooperative_matrix_properties_nv,
                                                vku::InitStruct<VkCooperativeMatrixPropertiesNV>());

        DispatchGetPhysicalDeviceCooperativeMatrixPropertiesNV(physical_device, &num_cooperative_matrix_properties_nv,
                                                               cooperative_matrix_properties_nv.data());
    }

    if (IsExtEnabled(extensions.vk_khr_cooperative_matrix)) {
        uint32_t num_cooperative_matrix_properties_khr = 0;
        DispatchGetPhysicalDeviceCooperativeMatrixPropertiesKHR(physical_device, &num_cooperative_matrix_properties_khr, NULL);
        cooperative_matrix_properties_khr.resize(num_cooperative_matrix_properties_khr,
                                                 vku::InitStruct<VkCooperativeMatrixPropertiesKHR>());

        DispatchGetPhysicalDeviceCooperativeMatrixPropertiesKHR(physical_device, &num_cooperative_matrix_properties_khr,
                                                                cooperative_matrix_properties_khr.data());
    }

    if (IsExtEnabled(extensions.vk_nv_cooperative_matrix2)) {
        uint32_t num_cooperative_matrix_flexible_dimensions_properties = 0;
        DispatchGetPhysicalDeviceCooperativeMatrixFlexibleDimensionsPropertiesNV(
            physical_device, &num_cooperative_matrix_flexible_dimensions_properties, NULL);
        cooperative_matrix_flexible_dimensions_properties.resize(
            num_cooperative_matrix_flexible_dimensions_properties,
            vku::InitStruct<VkCooperativeMatrixFlexibleDimensionsPropertiesNV>());

        DispatchGetPhysicalDeviceCooperativeMatrixFlexibleDimensionsPropertiesNV(
            physical_device, &num_cooperative_matrix_flexible_dimensions_properties,
            cooperative_matrix_flexible_dimensions_properties.data());
    }

    if (IsExtEnabled(extensions.vk_nv_cooperative_vector)) {
        uint32_t num_cooperative_vector_properties_nv = 0;
        DispatchGetPhysicalDeviceCooperativeVectorPropertiesNV(physical_device, &num_cooperative_vector_properties_nv, NULL);
        cooperative_vector_properties_nv.resize(num_cooperative_vector_properties_nv,
                                                vku::InitStruct<VkCooperativeVectorPropertiesNV>());

        DispatchGetPhysicalDeviceCooperativeVectorPropertiesNV(physical_device, &num_cooperative_vector_properties_nv,
                                                               cooperative_vector_properties_nv.data());
    }

    if (IsExtEnabled(extensions.vk_arm_data_graph)) {
        for (uint32_t i = 0; i < num_queue_families; i++) {
            std::vector<VkQueueFamilyDataGraphPropertiesARM> family_properties;
            uint32_t n_properties{0};
            DispatchGetPhysicalDeviceQueueFamilyDataGraphPropertiesARM(physical_device, i, &n_properties, nullptr);
            family_properties.resize(n_properties, vku::InitStruct<VkQueueFamilyDataGraphPropertiesARM>());
            DispatchGetPhysicalDeviceQueueFamilyDataGraphPropertiesARM(physical_device, i, &n_properties, family_properties.data());
            physical_device_state->data_graph.queue_family_properties.try_emplace(i, std::move(family_properties));
        }
    }

    if (IsExtEnabled(extensions.vk_arm_data_graph_optical_flow)) {
        // VK_ARM_data_graph_optical_flow depends on VK_ARM_data_graph
        assert(IsExtEnabled(extensions.vk_arm_data_graph));

        // make sure we have filled this in
        assert(!physical_device_state->data_graph.queue_family_properties.empty());

        for (uint32_t i = 0; i < num_queue_families; i++) {
            for (const auto& prop : physical_device_state->data_graph.queue_family_properties[i]) {
                if (prop.operation.operationType != VK_PHYSICAL_DEVICE_DATA_GRAPH_OPERATION_TYPE_OPTICAL_FLOW_ARM) {
                    continue;
                }
                VkQueueFamilyDataGraphOpticalFlowPropertiesARM op_flow_properties = vku::InitStructHelper();
                VkBaseOutStructure* base_properties = reinterpret_cast<VkBaseOutStructure*>(&op_flow_properties);
                DispatchGetPhysicalDeviceQueueFamilyDataGraphEngineOperationPropertiesARM(physical_device, i, &prop,
                                                                                          base_properties);

                vvl::PhysicalDevice::DataGraph::QueueAndEngine queue_and_engine{i, prop.engine};
                physical_device_state->data_graph.optical_flow_properties.try_emplace(queue_and_engine, op_flow_properties);

                vvl::PhysicalDevice::DataGraph::OpticalFlowFormats& of_formats =
                    physical_device_state->data_graph.optical_flow_formats;
                of_formats.input.try_emplace(
                    queue_and_engine, GetOpticalFlowFormats(i, prop.engine, VK_DATA_GRAPH_OPTICAL_FLOW_IMAGE_USAGE_INPUT_BIT_ARM));
                of_formats.output.try_emplace(
                    queue_and_engine, GetOpticalFlowFormats(i, prop.engine, VK_DATA_GRAPH_OPTICAL_FLOW_IMAGE_USAGE_OUTPUT_BIT_ARM));
                of_formats.cost.try_emplace(
                    queue_and_engine, GetOpticalFlowFormats(i, prop.engine, VK_DATA_GRAPH_OPTICAL_FLOW_IMAGE_USAGE_COST_BIT_ARM));
            }
        }
    }

    if (IsExtEnabled(extensions.vk_arm_data_graph_instruction_set_tosa)) {
        // VK_ARM_data_graph_instruction_set_tosa depends on VK_ARM_data_graph
        assert(IsExtEnabled(extensions.vk_arm_data_graph));

        // make sure we have filled this in
        assert(!physical_device_state->data_graph.queue_family_properties.empty());

        for (uint32_t i = 0; i < num_queue_families; i++) {
            VkQueueFamilyDataGraphTOSAPropertiesARM tosa_properties = vku::InitStructHelper();
            for (const auto& prop : physical_device_state->data_graph.queue_family_properties[i]) {
                if (prop.operation.operationType !=
                    VK_PHYSICAL_DEVICE_DATA_GRAPH_OPERATION_TYPE_SPIRV_EXTENDED_INSTRUCTION_SET_ARM) {
                    continue;
                }
                auto* base_properties = reinterpret_cast<VkBaseOutStructure*>(&tosa_properties);
                DispatchGetPhysicalDeviceQueueFamilyDataGraphEngineOperationPropertiesARM(physical_device, i, &prop,
                                                                                          base_properties);
                physical_device_state->data_graph.tosa_properties.try_emplace({i, prop.engine}, tosa_properties);
            }
        }
    }

    if (IsExtEnabled(extensions.vk_ext_descriptor_heap) || IsExtEnabled(extensions.vk_ext_descriptor_buffer)) {
        cached_descriptor_size.Init(*this);
        descriptor_hashing = std::make_unique<DescriptorHashing>(global_settings.descriptor_hashing_total_descriptors);
        if (IsExtEnabled(extensions.vk_ext_descriptor_heap)) {
            descriptor_hashing->heap_all_descriptor_sizes = cached_descriptor_size.GetAllSizes(true);
            for (VkDeviceSize size : descriptor_hashing->heap_all_descriptor_sizes) {
                if (size > descriptor_hashing->heap_max_descriptor_size) {
                    descriptor_hashing->heap_max_descriptor_size = size;
                }
            }
        }
        if (IsExtEnabled(extensions.vk_ext_descriptor_buffer)) {
            descriptor_hashing->buffer_all_descriptor_sizes = cached_descriptor_size.GetAllSizes(false);
            for (VkDeviceSize size : descriptor_hashing->buffer_all_descriptor_sizes) {
                if (size > descriptor_hashing->buffer_max_descriptor_size) {
                    descriptor_hashing->buffer_max_descriptor_size = size;
                }
            }
        }

        // |force_on_robustness| marks the "modified" feature so need to check for both
        if (enabled_features.nullDescriptor || gpuav_settings.force_on_robustness) {
            descriptor_hashing->null_descriptor_allowed = true;
        }
    } else if (global_settings.descriptor_hashing) {
        // In theory this shouldn't need to be turned off, but there are cases where we use
        // |descriptor_hashing->map_lock| and it can be null
        global_settings.descriptor_hashing = false;
        LogWarning("WARNING-Setting-Limit-Adjusted", device, loc,
                   "|descriptor_hashing| was enabled, but neither VK_EXT_descriptor_heap nor VK_EXT_descriptor_buffer was enabled. "
                   "[Disabling descriptor_hashing]");
    }

#if defined(VK_USE_PLATFORM_ANDROID_KHR)
    android_external_format_resolve_null_color_attachment_prop =
        phys_dev_ext_props.android_format_resolve_props.nullColorAttachmentWithExternalFormatResolve;
#endif
#if defined(VVL_TRACY_GPU)
    std::vector<VkTimeDomainKHR> time_domains;
    uint32_t time_domain_count = 0;
    VkResult result = DispatchGetPhysicalDeviceCalibrateableTimeDomainsEXT(physical_device, &time_domain_count, nullptr);
    assert(result == VK_SUCCESS);
    time_domains.resize(time_domain_count);
    result = DispatchGetPhysicalDeviceCalibrateableTimeDomainsEXT(physical_device, &time_domain_count, time_domains.data());
    assert(result == VK_SUCCESS);

    bool found_tracy_required_time_domain = false;
    for (VkTimeDomainEXT time_domain : time_domains) {
#if defined(VK_USE_PLATFORM_WIN32_KHR)
        if (time_domain == VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_EXT) {
            found_tracy_required_time_domain = true;
            break;
        }
#else
        if (time_domain == VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_EXT) {
            found_tracy_required_time_domain = true;
            break;
        }
#endif
    }
    (void)found_tracy_required_time_domain;
    assert(found_tracy_required_time_domain);

#endif
}

void DeviceState::DestroyObjectMaps() {
    command_pool_map_.clear();
    assert(command_buffer_map_.empty());
    pipeline_map_.clear();
    pipeline_cache_map_.clear();
    pipeline_layout_map_.clear();
    shader_object_map_.clear();
    render_pass_map_.clear();
    shader_module_map_.clear();
    frame_buffer_map_.clear();

    // This will also delete all sets in the pool & remove them from setMap
    descriptor_pool_map_.clear();
    // All sets should be removed
    assert(descriptor_set_map_.empty());
    desc_template_map_.clear();
    descriptor_set_layout_map_.clear();
    // Because swapchains are associated with Surfaces, which are at instance level,
    // they need to be explicitly destroyed here to avoid continued references to
    // the device we're destroying.
    for (auto& entry : swapchain_map_.snapshot()) {
        entry.second->Destroy();
    }
    swapchain_map_.clear();
    image_view_map_.clear();
    image_map_.clear();
    buffer_view_map_.clear();
    buffer_map_.clear();
    sampler_map_.clear();
    sampler_ycbcr_conversion_map_.clear();
    acceleration_structure_nv_map_.clear();
    acceleration_structure_khr_map_.clear();
    mem_obj_map_.clear();

    // Queues persist until device is destroyed
    for (auto& entry : queue_map_.snapshot()) {
        entry.second->Destroy();
    }
    queue_map_.clear();
    fence_map_.clear();
    semaphore_map_.clear();
    event_map_.clear();
    indirect_execution_set_ext_map_.clear();
    indirect_commands_layout_ext_map_.clear();
    query_pool_map_.clear();
    video_session_map_.clear();
    video_session_parameters_map_.clear();
}

void DeviceState::PreCallRecordDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator,
                                             const RecordObject& record_obj) {
    if (!device) {
        return;
    }

    DestroyObjectMaps();

    // Warning: If ever adding new destroy logic here,
    // consider that the base `DeviceState` object is destroyed
    // *before* the layer objects referencing it.
    // Ok as of writing, but this may cause issues in the future.
    // See `DestroyDevice` in chassis_manual.cpp for accurate
    // device destroy order.
}

static void UpdateCmdBufLabelStack(const CommandBuffer& cb_state, Queue& queue_state) {
    if (queue_state.found_unbalanced_cmdbuf_label) {
        return;
    }
    for (const auto& command : cb_state.GetLabelCommands()) {
        if (command.begin) {
            queue_state.cmdbuf_label_stack.push_back(command.label_name);
        } else {
            if (queue_state.cmdbuf_label_stack.empty()) {
                queue_state.found_unbalanced_cmdbuf_label = true;
                return;
            }
            queue_state.last_closed_cmdbuf_label = queue_state.cmdbuf_label_stack.back();
            queue_state.cmdbuf_label_stack.pop_back();
        }
    }
}

// This is a common location where we can detect queue submit about to occur.
// This is designed to capture "snapshots" of what VVL looks like, but at arbitrary time.
void DeviceState::CheckDebugCapture() const {
#if defined(DEBUG_CAPTURE_KEYBOARD)
    // Incase we want to support Android for this, a future option might not be a keyboard
    bool captured = false;
    // This will detect if the "F1" key is pressed
    captured |= IsDebugKeyPressed(instance_state->xlib_display, instance_state->xcb_connection);

    if (captured) {
        for (auto& item : proxies) {
            item.second.DebugCapture();
        }
    }
#endif
}

void DeviceState::PreCallRecordQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence,
                                           const RecordObject& record_obj) {
    CheckDebugCapture();
    auto queue_state = Get<Queue>(queue);
    std::vector<QueueSubmission> submissions;
    submissions.reserve(submitCount);
    if (submitCount == 0) {
        QueueSubmission submission(record_obj.location);
        submission.AddFence(Get<Fence>(fence));
        submissions.emplace_back(std::move(submission));
    }
    // Now process each individual submit
    for (uint32_t submit_i = 0; submit_i < submitCount; submit_i++) {
        Location submit_loc = record_obj.location.dot(Struct::VkSubmitInfo, Field::pSubmits, submit_i);
        QueueSubmission submission(submit_loc);
        const VkSubmitInfo* submit = &pSubmits[submit_i];
        auto* timeline_info = vku::FindStructInPNextChain<VkTimelineSemaphoreSubmitInfo>(submit->pNext);
        for (uint32_t i = 0; i < submit->waitSemaphoreCount; ++i) {
            auto wait_semaphore = Get<Semaphore>(submit->pWaitSemaphores[i]);
            uint64_t value{0};
            if (wait_semaphore->type == VK_SEMAPHORE_TYPE_TIMELINE && timeline_info && timeline_info->pWaitSemaphoreValues &&
                i < timeline_info->waitSemaphoreValueCount) {
                value = timeline_info->pWaitSemaphoreValues[i];
            }
            submission.AddWaitSemaphore(std::move(wait_semaphore), value);
        }
        for (uint32_t i = 0; i < submit->signalSemaphoreCount; ++i) {
            auto signal_semaphore = Get<Semaphore>(submit->pSignalSemaphores[i]);
            uint64_t value{0};
            if (signal_semaphore->type == VK_SEMAPHORE_TYPE_TIMELINE && timeline_info && timeline_info->pSignalSemaphoreValues &&
                i < timeline_info->signalSemaphoreValueCount) {
                value = timeline_info->pSignalSemaphoreValues[i];
            }
            submission.AddSignalSemaphore(std::move(signal_semaphore), value);
        }

        const auto perf_submit = vku::FindStructInPNextChain<VkPerformanceQuerySubmitInfoKHR>(submit->pNext);
        submission.perf_submit_pass = perf_submit ? perf_submit->counterPassIndex : 0;

        for (const VkCommandBuffer& cb : make_span(submit->pCommandBuffers, submit->commandBufferCount)) {
            if (auto cb_state = GetWrite<CommandBuffer>(cb)) {
                submission.AddCommandBuffer(cb_state, queue_state->cmdbuf_label_stack);
                UpdateCmdBufLabelStack(*cb_state, *queue_state);
            }
        }
        if (submit_i == (submitCount - 1) && fence != VK_NULL_HANDLE) {
            submission.AddFence(Get<Fence>(fence));
        }
        submissions.emplace_back(std::move(submission));
    }

    queue_state->PreSubmit(std::move(submissions));
}

void DeviceState::PostCallRecordQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence,
                                            const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto queue_state = Get<Queue>(queue);
    queue_state->PostSubmit();
    queue_state->is_used_for_regular_submits = true;
}

void DeviceState::PreCallRecordQueueSubmit2KHR(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2KHR* pSubmits, VkFence fence,
                                               const RecordObject& record_obj) {
    PreCallRecordQueueSubmit2(queue, submitCount, pSubmits, fence, record_obj);
}

void DeviceState::PreCallRecordQueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence,
                                            const RecordObject& record_obj) {
    CheckDebugCapture();
    auto queue_state = Get<Queue>(queue);
    std::vector<QueueSubmission> submissions;
    submissions.reserve(submitCount);
    if (submitCount == 0) {
        QueueSubmission submission(record_obj.location);
        submission.AddFence(Get<Fence>(fence));
        submissions.emplace_back(std::move(submission));
    }

    for (uint32_t submit_i = 0; submit_i < submitCount; submit_i++) {
        Location submit_loc = record_obj.location.dot(Struct::VkSubmitInfo2, Field::pSubmits, submit_i);
        QueueSubmission submission(submit_loc);
        const VkSubmitInfo2KHR& submit = pSubmits[submit_i];
        for (const VkSemaphoreSubmitInfo& wait_sem_info : make_span(submit.pWaitSemaphoreInfos, submit.waitSemaphoreInfoCount)) {
            auto wait_semaphore = Get<Semaphore>(wait_sem_info.semaphore);
            ASSERT_AND_CONTINUE(wait_semaphore);
            const uint64_t value = (wait_semaphore->type == VK_SEMAPHORE_TYPE_BINARY) ? 0 : wait_sem_info.value;
            submission.AddWaitSemaphore(std::move(wait_semaphore), value);
        }
        for (const VkSemaphoreSubmitInfo& sig_sem_info : make_span(submit.pSignalSemaphoreInfos, submit.signalSemaphoreInfoCount)) {
            submission.AddSignalSemaphore(Get<Semaphore>(sig_sem_info.semaphore), sig_sem_info.value);
        }
        const auto perf_submit = vku::FindStructInPNextChain<VkPerformanceQuerySubmitInfoKHR>(submit.pNext);
        submission.perf_submit_pass = perf_submit ? perf_submit->counterPassIndex : 0;

        for (const VkCommandBufferSubmitInfo& cb_info : make_span(submit.pCommandBufferInfos, submit.commandBufferInfoCount)) {
            if (auto cb_state = GetWrite<CommandBuffer>(cb_info.commandBuffer)) {
                submission.AddCommandBuffer(cb_state, queue_state->cmdbuf_label_stack);
                UpdateCmdBufLabelStack(*cb_state, *queue_state);
            }
        }
        if (submit_i == (submitCount - 1)) {
            submission.AddFence(Get<Fence>(fence));
        }
        submissions.emplace_back(std::move(submission));
    }
    queue_state->PreSubmit(std::move(submissions));
}

void DeviceState::PostCallRecordQueueSubmit2KHR(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2KHR* pSubmits,
                                                VkFence fence, const RecordObject& record_obj) {
    PostCallRecordQueueSubmit2(queue, submitCount, pSubmits, fence, record_obj);
}

void DeviceState::PostCallRecordQueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence,
                                             const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto queue_state = Get<Queue>(queue);
    queue_state->PostSubmit();
    queue_state->is_used_for_regular_submits = true;
}

void DeviceState::PostCallRecordAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo,
                                               const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory,
                                               const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    const auto& memory_type = phys_dev_mem_props.memoryTypes[pAllocateInfo->memoryTypeIndex];
    const auto& memory_heap = phys_dev_mem_props.memoryHeaps[memory_type.heapIndex];
    auto fake_address = fake_memory.Alloc(pAllocateInfo->allocationSize);

    VulkanTypedHandle dedicated_binding = NullVulkanTypedHandle;
    if (const auto dedicated = vku::FindStructInPNextChain<VkMemoryDedicatedAllocateInfo>(pAllocateInfo->pNext)) {
        if (dedicated->buffer) {
            dedicated_binding = {dedicated->buffer, kVulkanObjectTypeBuffer};
        } else if (dedicated->image) {
            dedicated_binding = {dedicated->image, kVulkanObjectTypeImage};
        } else if (const auto dedicated_tensor =
                       vku::FindStructInPNextChain<VkMemoryDedicatedAllocateInfoTensorARM>(pAllocateInfo->pNext)) {
            dedicated_binding = {dedicated_tensor->tensor, kVulkanObjectTypeTensorARM};
        }
    }
    Add(CreateDeviceMemoryState(*pMemory, pAllocateInfo, fake_address, memory_type, memory_heap, dedicated_binding,
                                physical_device_count));
    return;
}

void DeviceState::PreCallRecordFreeMemory(VkDevice device, VkDeviceMemory mem, const VkAllocationCallbacks* pAllocator,
                                          const RecordObject& record_obj) {
    if (auto mem_info = Get<DeviceMemory>(mem)) {
        fake_memory.Free(mem_info->fake_base_address);
    }
#ifdef VK_USE_PLATFORM_WIN32_KHR
    {
        WriteLockGuard guard(win32_handle_map_lock_);
        for (auto it = win32_handle_map_.begin(); it != win32_handle_map_.end();) {
            if (it->second.device_memory == mem) {
                it = win32_handle_map_.erase(it);
                break;
            } else {
                ++it;
            }
        }
    }
#endif
    Destroy<DeviceMemory>(mem);
}

void DeviceState::PostCallRecordSetDeviceMemoryPriorityEXT(VkDevice device, VkDeviceMemory memory, float priority,
                                                           const RecordObject& record_obj) {
    auto mem_info = Get<vvl::DeviceMemory>(memory);
    if (mem_info) {
        mem_info->dynamic_priority.emplace(priority);
    }
}

void DeviceState::PreCallRecordQueueBindSparse(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo,
                                               VkFence fence, const RecordObject& record_obj) {
    auto queue_state = Get<Queue>(queue);

    std::vector<QueueSubmission> submissions;
    submissions.reserve(bindInfoCount);
    for (uint32_t bind_idx = 0; bind_idx < bindInfoCount; ++bind_idx) {
        const VkBindSparseInfo& bind_info = pBindInfo[bind_idx];
        // Track objects tied to memory
        for (uint32_t j = 0; j < bind_info.bufferBindCount; j++) {
            for (uint32_t k = 0; k < bind_info.pBufferBinds[j].bindCount; k++) {
                auto sparse_binding = bind_info.pBufferBinds[j].pBinds[k];
                auto memory_state = Get<DeviceMemory>(sparse_binding.memory);
                if (auto buffer_state = Get<Buffer>(bind_info.pBufferBinds[j].buffer)) {
                    buffer_state->BindMemory(buffer_state.get(), memory_state, sparse_binding.memoryOffset,
                                             sparse_binding.resourceOffset, sparse_binding.size);
                }
            }
        }
        for (uint32_t j = 0; j < bind_info.imageOpaqueBindCount; j++) {
            for (uint32_t k = 0; k < bind_info.pImageOpaqueBinds[j].bindCount; k++) {
                auto sparse_binding = bind_info.pImageOpaqueBinds[j].pBinds[k];
                auto memory_state = Get<DeviceMemory>(sparse_binding.memory);
                if (auto image_state = Get<Image>(bind_info.pImageOpaqueBinds[j].image)) {
                    image_state->BindMemory(image_state.get(), memory_state, sparse_binding.memoryOffset,
                                            sparse_binding.resourceOffset, sparse_binding.size);
                }
            }
        }
        for (uint32_t j = 0; j < bind_info.imageBindCount; j++) {
            for (uint32_t k = 0; k < bind_info.pImageBinds[j].bindCount; k++) {
                auto sparse_binding = bind_info.pImageBinds[j].pBinds[k];
                // TODO: This size is broken for non-opaque bindings, need to update to comprehend full sparse binding data
                VkDeviceSize size = sparse_binding.extent.depth * sparse_binding.extent.height * sparse_binding.extent.width * 4;
                VkDeviceSize offset = sparse_binding.offset.z * sparse_binding.offset.y * sparse_binding.offset.x * 4;
                auto memory_state = Get<DeviceMemory>(sparse_binding.memory);
                if (auto image_state = Get<Image>(bind_info.pImageBinds[j].image)) {
                    image_state->BindMemory(image_state.get(), memory_state, sparse_binding.memoryOffset, offset, size);
                }
            }
        }
        auto* timeline_info = vku::FindStructInPNextChain<VkTimelineSemaphoreSubmitInfo>(bind_info.pNext);
        Location submit_loc = record_obj.location.dot(Struct::VkBindSparseInfo, Field::pBindInfo, bind_idx);
        QueueSubmission submission(submit_loc);
        for (uint32_t i = 0; i < bind_info.waitSemaphoreCount; ++i) {
            auto wait_semaphore = Get<Semaphore>(bind_info.pWaitSemaphores[i]);
            uint64_t value{0};
            if (wait_semaphore->type == VK_SEMAPHORE_TYPE_TIMELINE && timeline_info && timeline_info->pWaitSemaphoreValues &&
                i < timeline_info->waitSemaphoreValueCount) {
                value = timeline_info->pWaitSemaphoreValues[i];
            }
            submission.AddWaitSemaphore(std::move(wait_semaphore), value);
        }
        for (uint32_t i = 0; i < bind_info.signalSemaphoreCount; ++i) {
            auto signal_semaphore = Get<Semaphore>(bind_info.pSignalSemaphores[i]);
            uint64_t value{0};
            if (signal_semaphore->type == VK_SEMAPHORE_TYPE_TIMELINE && timeline_info && timeline_info->pSignalSemaphoreValues &&
                i < timeline_info->signalSemaphoreValueCount) {
                value = timeline_info->pSignalSemaphoreValues[i];
            }
            submission.AddSignalSemaphore(std::move(signal_semaphore), value);
        }
        if (bind_idx == (bindInfoCount - 1)) {
            submission.AddFence(Get<Fence>(fence));
        }
        submissions.emplace_back(std::move(submission));
    }

    queue_state->PreSubmit(std::move(submissions));
}

void DeviceState::PostCallRecordQueueBindSparse(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo,
                                                VkFence fence, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto queue_state = Get<Queue>(queue);
    queue_state->PostSubmit();
    queue_state->is_used_for_regular_submits = true;
}

void DeviceState::PostCallRecordCreateSemaphore(VkDevice device, const VkSemaphoreCreateInfo* pCreateInfo,
                                                const VkAllocationCallbacks* pAllocator, VkSemaphore* pSemaphore,
                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<Semaphore>(*this, *pSemaphore, pCreateInfo));
}

void DeviceState::RecordImportSemaphoreState(VkSemaphore semaphore, VkExternalSemaphoreHandleTypeFlagBits handle_type,
                                             VkSemaphoreImportFlags flags) {
    auto semaphore_state = Get<Semaphore>(semaphore);
    if (semaphore_state) {
        semaphore_state->Import(handle_type, flags);
    }
}

void DeviceState::PreCallRecordSignalSemaphore(VkDevice device, const VkSemaphoreSignalInfo* pSignalInfo,
                                               const RecordObject& record_obj) {
    auto semaphore_state = Get<Semaphore>(pSignalInfo->semaphore);
    if (semaphore_state) {
        auto value = pSignalInfo->value;  // const workaround
        semaphore_state->EnqueueSignal(SubmissionReference{}, value);
    }
}

void DeviceState::PreCallRecordSignalSemaphoreKHR(VkDevice device, const VkSemaphoreSignalInfo* pSignalInfo,
                                                  const RecordObject& record_obj) {
    PreCallRecordSignalSemaphore(device, pSignalInfo, record_obj);
}

void DeviceState::RecordMappedMemory(VkDeviceMemory mem, VkDeviceSize offset, VkDeviceSize size, void** ppData) {
    if (auto mem_info = Get<DeviceMemory>(mem)) {
        mem_info->mapped_range.offset = offset;
        mem_info->mapped_range.size = size;
        mem_info->p_driver_data = *ppData;
    }
}

void DeviceState::PostCallRecordWaitForFences(VkDevice device, uint32_t fenceCount, const VkFence* pFences, VkBool32 waitAll,
                                              uint64_t timeout, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    // When we know that all fences are complete we can clean/remove their CBs
    if ((VK_TRUE == waitAll) || (1 == fenceCount)) {
        for (uint32_t i = 0; i < fenceCount; i++) {
            if (auto fence_state = Get<Fence>(pFences[i])) {
                fence_state->NotifyAndWait(record_obj.location.dot(Field::pFences, i));
            }
        }
    }
    // NOTE : Alternate case not handled here is when some fences have completed. In
    //  this case for app to guarantee which fences completed it will have to call
    //  vkGetFenceStatus() at which point we'll clean/remove their CBs if complete.
}

void DeviceState::PreCallRecordWaitSemaphores(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout,
                                              const RecordObject& record_obj) {
    for (uint32_t i = 0; i < pWaitInfo->semaphoreCount; i++) {
        if (auto semaphore_state = Get<Semaphore>(pWaitInfo->pSemaphores[i])) {
            auto value = pWaitInfo->pValues[i];  // const workaround
            semaphore_state->EnqueueWait(SubmissionReference{}, value);
        }
    }
}

void DeviceState::PreCallRecordWaitSemaphoresKHR(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout,
                                                 const RecordObject& record_obj) {
    PreCallRecordWaitSemaphores(device, pWaitInfo, timeout, record_obj);
}

void DeviceState::PostCallRecordWaitSemaphores(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout,
                                               const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    // Same logic as vkWaitForFences(). If some semaphores are not signaled, we will get their status when
    // the application calls vkGetSemaphoreCounterValue() on each of them.
    if ((pWaitInfo->flags & VK_SEMAPHORE_WAIT_ANY_BIT) == 0 || pWaitInfo->semaphoreCount == 1) {
        const Location wait_info_loc = record_obj.location.dot(Field::pWaitInfo);
        for (uint32_t i = 0; i < pWaitInfo->semaphoreCount; i++) {
            if (auto semaphore_state = Get<Semaphore>(pWaitInfo->pSemaphores[i])) {
                Location wait_value_loc = wait_info_loc.dot(Field::pValues, i);
                semaphore_state->RetireWait(nullptr, pWaitInfo->pValues[i], wait_value_loc);
            }
        }
    }
}

void DeviceState::PostCallRecordWaitSemaphoresKHR(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout,
                                                  const RecordObject& record_obj) {
    PostCallRecordWaitSemaphores(device, pWaitInfo, timeout, record_obj);
}

void DeviceState::PostCallRecordGetSemaphoreCounterValue(VkDevice device, VkSemaphore semaphore, uint64_t* pValue,
                                                         const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (auto semaphore_state = Get<Semaphore>(semaphore)) {
        semaphore_state->RetireWait(nullptr, *pValue, record_obj.location);
    }
}

void DeviceState::PostCallRecordGetSemaphoreCounterValueKHR(VkDevice device, VkSemaphore semaphore, uint64_t* pValue,
                                                            const RecordObject& record_obj) {
    PostCallRecordGetSemaphoreCounterValue(device, semaphore, pValue, record_obj);
}

void DeviceState::PostCallRecordGetFenceStatus(VkDevice device, VkFence fence, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (auto fence_state = Get<Fence>(fence)) {
        fence_state->NotifyAndWait(record_obj.location);
    }
}

void DeviceState::RecordGetDeviceQueueState(uint32_t queue_family_index, uint32_t queue_index, VkDeviceQueueCreateFlags flags,
                                            VkQueue queue) {
    if (Get<Queue>(queue) == nullptr) {
        uint32_t num_queue_families = 0;
        DispatchGetPhysicalDeviceQueueFamilyProperties(physical_device, &num_queue_families, nullptr);
        std::vector<VkQueueFamilyProperties> queue_family_properties_list(num_queue_families);
        DispatchGetPhysicalDeviceQueueFamilyProperties(physical_device, &num_queue_families, queue_family_properties_list.data());

        Add(CreateQueue(queue, queue_family_index, queue_index, flags, queue_family_properties_list[queue_family_index]));
    }
}

void DeviceState::PostCallRecordGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue,
                                               const RecordObject& record_obj) {
    RecordGetDeviceQueueState(queueFamilyIndex, queueIndex, {}, *pQueue);
}

void DeviceState::PostCallRecordGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQueue,
                                                const RecordObject& record_obj) {
    RecordGetDeviceQueueState(pQueueInfo->queueFamilyIndex, pQueueInfo->queueIndex, pQueueInfo->flags, *pQueue);
}

void DeviceState::PostCallRecordQueueWaitIdle(VkQueue queue, const RecordObject& record_obj) {
    // We assume this is only ever non-success if it is VK_ERROR_DEVICE_LOST, in that case we don't want to update state
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (auto queue_state = Get<Queue>(queue)) {
        queue_state->NotifyAndWait(record_obj.location);

        // Reset semaphore's in-use-by-swapchain state.
        // Only for pre-swapchain-maintenance1 code. New code should realy on presentation fence.
        if (!IsExtEnabled(extensions.vk_khr_swapchain_maintenance1) && !IsExtEnabled(extensions.vk_ext_swapchain_maintenance1)) {
            if (queue_state->is_used_for_presentation) {
                for (const auto& entry : semaphore_map_.snapshot()) {
                    const std::shared_ptr<vvl::Semaphore>& semaphore_state = entry.second;
                    semaphore_state->ClearSwapchainWaitInfo();
                }
            }
        }
    }
}

void DeviceState::PostCallRecordDeviceWaitIdle(VkDevice device, const RecordObject& record_obj) {
    // We assume this is only ever non-success if it is VK_ERROR_DEVICE_LOST, in that case we don't want to update state
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    // Sort the queues by id to notify in deterministic order (queue creation order).
    // This is not needed for correctness, but gives deterministic behavior to certain
    // types of bugs in the queue thread.
    std::vector<std::shared_ptr<Queue>> queues;
    queues.reserve(queue_map_.size());
    for (const auto& entry : queue_map_.snapshot()) {
        queues.push_back(entry.second);
    }
    std::sort(queues.begin(), queues.end(), [](const auto& q1, const auto& q2) { return q1->GetId() < q2->GetId(); });

    // Notify all queues before waiting.
    // NotifyAndWait is not safe here. It deadlocks when a wait depends on the not yet issued notify.
    for (auto& queue : queues) {
        queue->Notify();
    }
    // All possible forward progress is initiated. Now it's safe to wait.
    for (auto& queue : queues) {
        queue->Wait(record_obj.location);
    }
    // Reset semaphore's in-use-by-swapchain state.
    // Only for pre-swapchain-maintenance1 code. New code should rely on the presentation fence.
    if (!IsExtEnabled(extensions.vk_khr_swapchain_maintenance1) && !IsExtEnabled(extensions.vk_ext_swapchain_maintenance1)) {
        for (const auto& entry : semaphore_map_.snapshot()) {
            const std::shared_ptr<vvl::Semaphore>& semaphore_state = entry.second;
            semaphore_state->ClearSwapchainWaitInfo();
        }
    }
}

void DeviceState::PreCallRecordDestroyFence(VkDevice device, VkFence fence, const VkAllocationCallbacks* pAllocator,
                                            const RecordObject& record_obj) {
    Destroy<Fence>(fence);
}

void DeviceState::PreCallRecordDestroySemaphore(VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks* pAllocator,
                                                const RecordObject& record_obj) {
    Destroy<Semaphore>(semaphore);
}

void DeviceState::PreCallRecordDestroyEvent(VkDevice device, VkEvent event, const VkAllocationCallbacks* pAllocator,
                                            const RecordObject& record_obj) {
    Destroy<Event>(event);
}

void DeviceState::PreCallRecordDestroyQueryPool(VkDevice device, VkQueryPool queryPool, const VkAllocationCallbacks* pAllocator,
                                                const RecordObject& record_obj) {
    Destroy<QueryPool>(queryPool);
}

void DeviceState::UpdateBindBufferMemoryState(const VkBindBufferMemoryInfo& bind_info) {
    auto buffer_state = Get<Buffer>(bind_info.buffer);
    if (!buffer_state) {
        return;
    }

    // Track objects tied to memory
    if (auto memory_state = Get<DeviceMemory>(bind_info.memory)) {
        buffer_state->BindMemory(buffer_state.get(), memory_state, bind_info.memoryOffset, 0u, buffer_state->requirements.size);
    }
}

void DeviceState::PostCallRecordBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset,
                                                 const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    VkBindBufferMemoryInfo bind_info = vku::InitStructHelper();
    bind_info.buffer = buffer;
    bind_info.memory = memory;
    bind_info.memoryOffset = memoryOffset;
    UpdateBindBufferMemoryState(bind_info);
}

void DeviceState::PostCallRecordBindBufferMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos,
                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        // if bindInfoCount is 1, we know for sure if that single buffer was bound or not
        if (bindInfoCount > 1) {
            for (uint32_t i = 0; i < bindInfoCount; i++) {
                // If user passed in VkBindMemoryStatus, we can update which buffers are valid or not
                if (auto* bind_memory_status = vku::FindStructInPNextChain<VkBindMemoryStatus>(pBindInfos[i].pNext)) {
                    if (bind_memory_status->pResult && *bind_memory_status->pResult == VK_SUCCESS) {
                        UpdateBindBufferMemoryState(pBindInfos[i]);
                    }
                } else if (auto buffer_state = Get<Buffer>(pBindInfos[i].buffer)) {
                    buffer_state->indeterminate_state = true;
                }
            }
        }
    } else {
        for (uint32_t i = 0; i < bindInfoCount; i++) {
            UpdateBindBufferMemoryState(pBindInfos[i]);
        }
    }
}

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

void DeviceState::PreCallRecordDestroyShaderModule(VkDevice device, VkShaderModule shaderModule,
                                                   const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<ShaderModule>(shaderModule);
}

void DeviceState::PreCallRecordDestroyShaderEXT(VkDevice device, VkShaderEXT shader, const VkAllocationCallbacks* pAllocator,
                                                const RecordObject& record_obj) {
    // Don't do state lookup if not needed
    if (enabled_features.descriptorHeap) {
        if (const auto& shader_state = Get<ShaderObject>(shader)) {
            if (shader_state->descriptor_heap_embedded_samplers_count > 0) {
                descriptor_heap_global_embedded_sampler_count_ -= shader_state->descriptor_heap_embedded_samplers_count;
            }
        }
    }

    Destroy<ShaderObject>(shader);
}

void DeviceState::PreCallRecordDestroyPipeline(VkDevice device, VkPipeline pipeline, const VkAllocationCallbacks* pAllocator,
                                               const RecordObject& record_obj) {
    // Don't do state lookup if not needed
    if (enabled_features.descriptorHeap) {
        if (const auto& pipeline_state = Get<Pipeline>(pipeline)) {
            if (pipeline_state->descriptor_heap_embedded_samplers_count > 0) {
                descriptor_heap_global_embedded_sampler_count_ -= pipeline_state->descriptor_heap_embedded_samplers_count;
            }
        }
    }

    Destroy<Pipeline>(pipeline);
}

void DeviceState::PostCallRecordCmdBindShadersEXT(VkCommandBuffer commandBuffer, uint32_t stageCount,
                                                  const VkShaderStageFlagBits* pStages, const VkShaderEXT* pShaders,
                                                  const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    for (uint32_t i = 0; i < stageCount; ++i) {
        std::shared_ptr<ShaderObject> shader_object_state;
        if (pShaders && pShaders[i] != VK_NULL_HANDLE) {
            shader_object_state = Get<ShaderObject>(pShaders[i]);
        }

        cb_state->RecordBindShaderObject(pStages[i], shader_object_state);
        if (shader_object_state && !disabled[command_buffer_state]) {
            cb_state->AddChild(shader_object_state);
        }
    }
}

void DeviceState::PreCallRecordDestroyPipelineLayout(VkDevice device, VkPipelineLayout pipelineLayout,
                                                     const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<PipelineLayout>(pipelineLayout);
}

void DeviceState::PreCallRecordDestroySampler(VkDevice device, VkSampler sampler, const VkAllocationCallbacks* pAllocator,
                                              const RecordObject& record_obj) {
    if (!sampler) {
        return;
    }
    // Any bound cmd buffers are now invalid
    if (auto sampler_state = Get<Sampler>(sampler)) {
        if (sampler_state->create_info.borderColor == VK_BORDER_COLOR_INT_CUSTOM_EXT ||
            sampler_state->create_info.borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT) {
            custom_border_color_sampler_count--;
        }
    }
    Destroy<Sampler>(sampler);
}

void DeviceState::PreCallRecordDestroyDescriptorSetLayout(VkDevice device, VkDescriptorSetLayout descriptorSetLayout,
                                                          const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<DescriptorSetLayout>(descriptorSetLayout);
}

void DeviceState::PreCallRecordDestroyDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool,
                                                     const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<DescriptorPool>(descriptorPool);
}

void DeviceState::PreCallRecordFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount,
                                                  const VkCommandBuffer* pCommandBuffers, const RecordObject& record_obj) {
    if (auto pool = Get<CommandPool>(commandPool)) {
        pool->Free(commandBufferCount, pCommandBuffers);
    }
}

std::shared_ptr<CommandPool> DeviceState::CreateCommandPoolState(VkCommandPool handle, const VkCommandPoolCreateInfo* create_info) {
    auto queue_flags = physical_device_state->queue_family_properties[create_info->queueFamilyIndex].queueFlags;
    return std::make_shared<CommandPool>(*this, handle, create_info, queue_flags);
}

void DeviceState::PostCallRecordCreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo,
                                                  const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool,
                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(CreateCommandPoolState(*pCommandPool, pCreateInfo));
}

void DeviceState::PostCallRecordCreateQueryPool(VkDevice device, const VkQueryPoolCreateInfo* pCreateInfo,
                                                const VkAllocationCallbacks* pAllocator, VkQueryPool* pQueryPool,
                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    uint32_t index_count = 0;
    uint32_t perf_queue_family_index = 0;
    uint32_t n_perf_pass = 0;
    bool has_cb = false, has_rb = false;
    if (pCreateInfo->queryType == VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR) {
        const auto* perf = vku::FindStructInPNextChain<VkQueryPoolPerformanceCreateInfoKHR>(pCreateInfo->pNext);
        perf_queue_family_index = perf->queueFamilyIndex;
        index_count = perf->counterIndexCount;

        const QueueFamilyPerfCounters& counters = *physical_device_state->perf_counters[perf_queue_family_index];
        for (uint32_t i = 0; i < perf->counterIndexCount; i++) {
            const auto& counter = counters.counters[perf->pCounterIndices[i]];
            switch (counter.scope) {
                case VK_PERFORMANCE_COUNTER_SCOPE_COMMAND_BUFFER_KHR:
                    has_cb = true;
                    break;
                case VK_PERFORMANCE_COUNTER_SCOPE_RENDER_PASS_KHR:
                    has_rb = true;
                    break;
                default:
                    break;
            }
        }

        DispatchGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR(physical_device_state->VkHandle(), perf, &n_perf_pass);
    }

    VkVideoEncodeFeedbackFlagsKHR video_encode_feedback_flags = 0;
    if (pCreateInfo->queryType == VK_QUERY_TYPE_VIDEO_ENCODE_FEEDBACK_KHR) {
        const auto* feedback_info = vku::FindStructInPNextChain<VkQueryPoolVideoEncodeFeedbackCreateInfoKHR>(pCreateInfo->pNext);
        if (feedback_info) {
            video_encode_feedback_flags = feedback_info->encodeFeedbackFlags;
        }
    }

    Add(std::make_shared<QueryPool>(
        *pQueryPool, pCreateInfo, index_count, perf_queue_family_index, n_perf_pass, has_cb, has_rb,
        video_profile_cache_.Get(physical_device, vku::FindStructInPNextChain<VkVideoProfileInfoKHR>(pCreateInfo->pNext)),
        video_encode_feedback_flags));
}

void DeviceState::PreCallRecordDestroyCommandPool(VkDevice device, VkCommandPool commandPool,
                                                  const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<CommandPool>(commandPool);
}

void DeviceState::PostCallRecordResetCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags,
                                                 const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    // Reset all of the CBs allocated from this pool
    if (auto pool = Get<CommandPool>(commandPool)) {
        pool->Reset(record_obj.location);
    }
}

void DeviceState::PostCallRecordResetFences(VkDevice device, uint32_t fenceCount, const VkFence* pFences,
                                            const RecordObject& record_obj) {
    // Discussion what a failed reset with multiple fences would mean
    // https://gitlab.khronos.org/vulkan/vulkan/-/issues/4253
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    for (uint32_t i = 0; i < fenceCount; ++i) {
        if (auto fence_state = Get<Fence>(pFences[i])) {
            fence_state->Reset();
        }
    }
}

void DeviceState::PreCallRecordDestroyFramebuffer(VkDevice device, VkFramebuffer framebuffer,
                                                  const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<Framebuffer>(framebuffer);
}

void DeviceState::PreCallRecordDestroyRenderPass(VkDevice device, VkRenderPass renderPass, const VkAllocationCallbacks* pAllocator,
                                                 const RecordObject& record_obj) {
    Destroy<RenderPass>(renderPass);
}

void DeviceState::PostCallRecordCreateFence(VkDevice device, const VkFenceCreateInfo* pCreateInfo,
                                            const VkAllocationCallbacks* pAllocator, VkFence* pFence,
                                            const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<Fence>(*this, *pFence, pCreateInfo));
}

std::shared_ptr<PipelineCache> DeviceState::CreatePipelineCacheState(VkPipelineCache handle,
                                                                     const VkPipelineCacheCreateInfo* create_info) const {
    return std::make_shared<PipelineCache>(handle, create_info);
}

void DeviceState::PostCallRecordCreatePipelineCache(VkDevice device, const VkPipelineCacheCreateInfo* pCreateInfo,
                                                    const VkAllocationCallbacks* pAllocator, VkPipelineCache* pPipelineCache,
                                                    const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(CreatePipelineCacheState(*pPipelineCache, pCreateInfo));
}

void DeviceState::PreCallRecordDestroyPipelineCache(VkDevice device, VkPipelineCache pipelineCache,
                                                    const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<PipelineCache>(pipelineCache);
}

std::shared_ptr<Pipeline> DeviceState::CreateGraphicsPipelineState(
    const VkGraphicsPipelineCreateInfo* create_info, std::shared_ptr<const PipelineCache> pipeline_cache,
    std::shared_ptr<const RenderPass>&& render_pass, std::shared_ptr<const PipelineLayout>&& layout,
    spirv::StatelessData stateless_data[kCommonMaxGraphicsShaderStages]) const {
    return std::make_shared<Pipeline>(*this, create_info, std::move(pipeline_cache), std::move(render_pass), std::move(layout),
                                      stateless_data);
}

// PreCallValidate used here to have a single global spot to build the vvl::Pipeline object so we can use it right away
bool DeviceState::PreCallValidateCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count,
                                                         const VkGraphicsPipelineCreateInfo* pCreateInfos,
                                                         const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                         const ErrorObject& error_obj, PipelineStates& pipeline_states,
                                                         chassis::CreateGraphicsPipelines& chassis_state) const {
    bool skip = false;
    // Set up the state that CoreChecks, gpu_validation and later StateTracker Record will use.
    pipeline_states.reserve(count);
    auto pipeline_cache = Get<PipelineCache>(pipelineCache);
    for (uint32_t i = 0; i < count; i++) {
        const auto& create_info = pCreateInfos[i];
        auto layout_state = Get<PipelineLayout>(create_info.layout);
        std::shared_ptr<const RenderPass> render_pass;

        if (pCreateInfos[i].renderPass != VK_NULL_HANDLE) {
            render_pass = Get<RenderPass>(create_info.renderPass);
        } else if (enabled_features.dynamicRendering) {
            // We need VkPipelineRenderingCreateInfo for two reasons
            //  - The viewMask (pre-raster/fragment shader)
            //  - The formats (fragment output) (also depends on rasterization being enabled)
            // We need generate our own, correct VkPipelineRenderingCreateInfo so we can make a safe struct in vvl::RenderPass
            //
            // We also need to be careful, if things like rasterization is diabled, we **need** to ignore a possible
            // VkPipelineRenderingCreateInfo that contains bad pointers (Thanks GPL!)

            VkPipelineCreateFlags2 flags = create_info.flags;
            if (auto flags2 = vku::FindStructInPNextChain<VkPipelineCreateFlags2CreateInfo>(create_info.pNext)) {
                flags = flags2->flags;
            }
            const bool is_library = (flags & VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR) != 0;

            // These are simply true for the non-GPL case (or when we are creating the final GPL pipeline)
            bool has_pre_raster_state = true;
            bool has_fragment_shader_state = true;
            bool has_fragment_output_state = true;
            if (is_library) {
                // Same as OwnsLibState() but when there is no vvl::Pipeline state
                if (auto lib_type = vku::FindStructInPNextChain<VkGraphicsPipelineLibraryCreateInfoEXT>(create_info.pNext)) {
                    has_pre_raster_state = (lib_type->flags & VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT) != 0;
                    has_fragment_shader_state = (lib_type->flags & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT) != 0;
                    has_fragment_output_state =
                        (lib_type->flags & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT) != 0;
                }
            }

            const bool uses_view_mask = has_pre_raster_state | has_fragment_shader_state;
            const bool rasterization_enabled =
                has_fragment_output_state && Pipeline::EnablesRasterizationStates(*this, create_info);

            // Start with empty struct, fill in what is not ignored
            VkPipelineRenderingCreateInfo rendering_ci = vku::InitStructHelper();
            rendering_ci.viewMask = 0;
            rendering_ci.colorAttachmentCount = 0;
            rendering_ci.pColorAttachmentFormats = nullptr;
            rendering_ci.depthAttachmentFormat = VK_FORMAT_UNDEFINED;
            rendering_ci.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;

            if (auto pnext_ci = vku::FindStructInPNextChain<VkPipelineRenderingCreateInfo>(create_info.pNext)) {
                if (uses_view_mask) {
                    rendering_ci.viewMask = pnext_ci->viewMask;
                }
                if (rasterization_enabled) {
                    rendering_ci.colorAttachmentCount = pnext_ci->colorAttachmentCount;
                    rendering_ci.pColorAttachmentFormats = pnext_ci->pColorAttachmentFormats;
                    rendering_ci.depthAttachmentFormat = pnext_ci->depthAttachmentFormat;
                    rendering_ci.stencilAttachmentFormat = pnext_ci->stencilAttachmentFormat;
                }
            }

            auto lib_info = vku::FindStructInPNextChain<VkPipelineLibraryCreateInfoKHR>(create_info.pNext);
            // When GPL (our favorite extension) is used, this will be the final executable pipeline and here we need to fetch all
            // the library state
            if (lib_info && lib_info->libraryCount != 0) {
                // Even if the final executable in GPL has a VkPipelineRenderingCreateInfo, we ignore (override) it
                for (VkPipeline lib : vvl::make_span(lib_info->pLibraries, lib_info->libraryCount)) {
                    auto lib_state = Get<vvl::Pipeline>(lib);
                    ASSERT_AND_CONTINUE(lib_state);
                    if (!lib_state->rendering_create_info) {
                        // chance there might not be VkPipelineRenderingCreateInfo when we except, means either a Vertex Input or an
                        // error will be caught elsewhere
                        continue;
                    }
                    if (lib_state->OwnsLibState(lib_state->fragment_output_state)) {
                        rendering_ci.colorAttachmentCount = lib_state->rendering_create_info->colorAttachmentCount;
                        rendering_ci.pColorAttachmentFormats = lib_state->rendering_create_info->pColorAttachmentFormats;
                        rendering_ci.depthAttachmentFormat = lib_state->rendering_create_info->depthAttachmentFormat;
                        rendering_ci.stencilAttachmentFormat = lib_state->rendering_create_info->stencilAttachmentFormat;
                    } else if (lib_state->OwnsLibState(lib_state->pre_raster_state)) {
                        // Could look for Fragment shader, but their viewMask must match and pre-raster is pre-raster is required
                        rendering_ci.viewMask = lib_state->rendering_create_info->viewMask;
                    }
                }
            }

            render_pass = std::make_shared<RenderPass>(rendering_ci);
        }
        pipeline_states.push_back(CreateGraphicsPipelineState(&create_info, pipeline_cache, std::move(render_pass),
                                                              std::move(layout_state), chassis_state.stateless_data));
    }
    return skip;
}

void DeviceState::PreCallRecordCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count,
                                                       const VkGraphicsPipelineCreateInfo* pCreateInfos,
                                                       const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                       const RecordObject& record_obj, PipelineStates& pipeline_states,
                                                       chassis::CreateGraphicsPipelines& chassis_state) {
    // Can't call in PreCallValidate as that is a const function
    //
    // Normally this would be called during
    //    Add(std::move(pipeline_states[i]));
    // but we do the notify first to create subset
    for (uint32_t i = 0; i < count; i++) {
        NotifyCreatedPipeline(*pipeline_states[i]);
    }
}

// GPL was the worst thing ever added... we have to make MORE hacks around the fact we create a Pipeline state object in
// PreCallValidate. The goal of this is go ahead of create the PipelineSubState such that GPU-AV can use it in PreCallRecord...I'm
// just as mad as you are reading this that we have this.
void DeviceState::NotifyCreatedPipeline(vvl::Pipeline& pipeline_state) {
    // Normally called on Add()
    for (auto& item : proxies) {
        item.second.Created(pipeline_state);
    }
}

void DeviceState::PostCallRecordCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count,
                                                        const VkGraphicsPipelineCreateInfo* pCreateInfos,
                                                        const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                        const RecordObject& record_obj, PipelineStates& pipeline_states,
                                                        chassis::CreateGraphicsPipelines& chassis_state) {
    for (uint32_t i = 0; i < count; i++) {
        const VkPipeline pipeline_handle = pPipelines[i];
        if (pipeline_handle == VK_NULL_HANDLE) {
            continue;  // vkspec.html#pipelines-multiple
        }

        if (pCreateInfos[i].pStages) {
            if (pipeline_states[i]->descriptor_heap_embedded_samplers_count > 0) {
                descriptor_heap_global_embedded_sampler_count_ += pipeline_states[i]->descriptor_heap_embedded_samplers_count;
            }
        }

        pipeline_states[i]->SetHandle(pipeline_handle);
        Add(std::move(pipeline_states[i]));
    }
    pipeline_states.clear();
}

std::shared_ptr<Pipeline> DeviceState::CreateComputePipelineState(const VkComputePipelineCreateInfo* create_info,
                                                                  std::shared_ptr<const PipelineCache> pipeline_cache,
                                                                  std::shared_ptr<const PipelineLayout>&& layout,
                                                                  spirv::StatelessData* stateless_data) const {
    return std::make_shared<Pipeline>(*this, create_info, std::move(pipeline_cache), std::move(layout), stateless_data);
}

// PreCallValidate used here to have a single global spot to build the vvl::Pipeline object so we can use it right away
bool DeviceState::PreCallValidateCreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count,
                                                        const VkComputePipelineCreateInfo* pCreateInfos,
                                                        const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                        const ErrorObject& error_obj, PipelineStates& pipeline_states,
                                                        chassis::CreateComputePipelines& chassis_state) const {
    pipeline_states.reserve(count);
    auto pipeline_cache = Get<PipelineCache>(pipelineCache);
    for (uint32_t i = 0; i < count; i++) {
        // Create and initialize internal tracking data structure
        pipeline_states.push_back(CreateComputePipelineState(
            &pCreateInfos[i], pipeline_cache, Get<PipelineLayout>(pCreateInfos[i].layout), &chassis_state.stateless_data));
    }
    return false;
}

void DeviceState::PreCallRecordCreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count,
                                                      const VkComputePipelineCreateInfo* pCreateInfos,
                                                      const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                      const RecordObject& record_obj, PipelineStates& pipeline_states,
                                                      chassis::CreateComputePipelines& chassis_state) {
    for (uint32_t i = 0; i < count; i++) {
        NotifyCreatedPipeline(*pipeline_states[i]);
    }
}

void DeviceState::PostCallRecordCreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count,
                                                       const VkComputePipelineCreateInfo* pCreateInfos,
                                                       const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                       const RecordObject& record_obj, PipelineStates& pipeline_states,
                                                       chassis::CreateComputePipelines& chassis_state) {
    for (uint32_t i = 0; i < count; i++) {
        const VkPipeline pipeline_handle = pPipelines[i];
        if (pipeline_handle == VK_NULL_HANDLE) {
            continue;  // vkspec.html#pipelines-multiple
        }

        if (pipeline_states[i]->descriptor_heap_embedded_samplers_count > 0) {
            descriptor_heap_global_embedded_sampler_count_ += pipeline_states[i]->descriptor_heap_embedded_samplers_count;
        }

        pipeline_states[i]->SetHandle(pipeline_handle);
        Add(std::move(pipeline_states[i]));
    }
    pipeline_states.clear();
}

// TODO - Add tests and pass down StatelessData
std::shared_ptr<Pipeline> DeviceState::CreateRayTracingPipelineStateNV(const VkRayTracingPipelineCreateInfoNV* create_info,
                                                                       std::shared_ptr<const PipelineCache> pipeline_cache,
                                                                       std::shared_ptr<const PipelineLayout>&& layout) const {
    return std::make_shared<Pipeline>(*this, create_info, std::move(pipeline_cache), std::move(layout));
}

// PreCallValidate used here to have a single global spot to build the vvl::Pipeline object so we can use it right away
bool DeviceState::PreCallValidateCreateRayTracingPipelinesNV(VkDevice device, VkPipelineCache pipelineCache, uint32_t count,
                                                             const VkRayTracingPipelineCreateInfoNV* pCreateInfos,
                                                             const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                             const ErrorObject& error_obj, PipelineStates& pipeline_states) const {
    pipeline_states.reserve(count);
    auto pipeline_cache = Get<PipelineCache>(pipelineCache);
    for (uint32_t i = 0; i < count; i++) {
        // Create and initialize internal tracking data structure
        pipeline_states.push_back(
            CreateRayTracingPipelineStateNV(&pCreateInfos[i], pipeline_cache, Get<PipelineLayout>(pCreateInfos[i].layout)));
    }
    return false;
}

void DeviceState::PostCallRecordCreateRayTracingPipelinesNV(VkDevice device, VkPipelineCache pipelineCache, uint32_t count,
                                                            const VkRayTracingPipelineCreateInfoNV* pCreateInfos,
                                                            const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                            const RecordObject& record_obj, PipelineStates& pipeline_states) {
    for (uint32_t i = 0; i < count; i++) {
        const VkPipeline pipeline_handle = pPipelines[i];
        if (pipeline_handle == VK_NULL_HANDLE) {
            continue;  // vkspec.html#pipelines-multiple
        }

        if (pipeline_states[i]->descriptor_heap_embedded_samplers_count > 0) {
            descriptor_heap_global_embedded_sampler_count_ += pipeline_states[i]->descriptor_heap_embedded_samplers_count;
        }

        pipeline_states[i]->SetHandle(pipeline_handle);
        Add(std::move(pipeline_states[i]));
    }
    pipeline_states.clear();
}

// TODO - Add tests and pass down StatelessData
std::shared_ptr<Pipeline> DeviceState::CreateRayTracingPipelineStateKHR(const VkRayTracingPipelineCreateInfoKHR* create_info,
                                                                        std::shared_ptr<const PipelineCache> pipeline_cache,
                                                                        std::shared_ptr<const PipelineLayout>&& layout,
                                                                        std::vector<spirv::StatelessData>& stateless_data) const {
    return std::make_shared<Pipeline>(*this, create_info, std::move(pipeline_cache), std::move(layout), &stateless_data);
}

// PreCallValidate used here to have a single global spot to build the vvl::Pipeline object so we can use it right away
bool DeviceState::PreCallValidateCreateRayTracingPipelinesKHR(VkDevice device, VkDeferredOperationKHR deferredOperation,
                                                              VkPipelineCache pipelineCache, uint32_t count,
                                                              const VkRayTracingPipelineCreateInfoKHR* pCreateInfos,
                                                              const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                              const ErrorObject& error_obj, PipelineStates& pipeline_states,
                                                              chassis::CreateRayTracingPipelinesKHR& chassis_state) const {
    pipeline_states.reserve(count);
    auto pipeline_cache = Get<PipelineCache>(pipelineCache);
    for (uint32_t i = 0; i < count; i++) {
        // Create and initialize internal tracking data structure
        pipeline_states.push_back(CreateRayTracingPipelineStateKHR(
            &pCreateInfos[i], pipeline_cache, Get<PipelineLayout>(pCreateInfos[i].layout), chassis_state.stateless_data));
    }
    return false;
}

void DeviceState::PreCallRecordCreateRayTracingPipelinesKHR(VkDevice device, VkDeferredOperationKHR deferredOperation,
                                                            VkPipelineCache pipelineCache, uint32_t count,
                                                            const VkRayTracingPipelineCreateInfoKHR* pCreateInfos,
                                                            const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                            const RecordObject& record_obj, PipelineStates& pipeline_states,
                                                            chassis::CreateRayTracingPipelinesKHR& chassis_state) {
    for (uint32_t i = 0; i < count; i++) {
        NotifyCreatedPipeline(*pipeline_states[i]);
    }
}

void DeviceState::PostCallRecordCreateRayTracingPipelinesKHR(VkDevice device, VkDeferredOperationKHR deferredOperation,
                                                             VkPipelineCache pipelineCache, uint32_t count,
                                                             const VkRayTracingPipelineCreateInfoKHR* pCreateInfos,
                                                             const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                             const RecordObject& record_obj, PipelineStates& pipeline_states,
                                                             std::shared_ptr<chassis::CreateRayTracingPipelinesKHR> chassis_state) {
    const bool is_operation_deferred = (deferredOperation != VK_NULL_HANDLE && record_obj.result == VK_OPERATION_DEFERRED_KHR);
    if (!is_operation_deferred) {
        for (uint32_t i = 0; i < count; i++) {
            const VkPipeline pipeline_handle = pPipelines[i];
            if (pipeline_handle == VK_NULL_HANDLE) {
                continue;  // vkspec.html#pipelines-multiple
            }

            if (pipeline_states[i]->descriptor_heap_embedded_samplers_count > 0) {
                descriptor_heap_global_embedded_sampler_count_ += pipeline_states[i]->descriptor_heap_embedded_samplers_count;
            }

            pipeline_states[i]->SetHandle(pipeline_handle);
            Add(std::move(pipeline_states[i]));
        }
    } else {
        // Deferred creation: pipelines will be considered created once the deferredOperation object
        // signals it, via usage of vkDeferredOperationJoinKHR and then vkGetDeferredOperationResultKHR
        // Hence pipeline state tracking needs to be deferred to the corresponding call to
        // vkGetDeferredOperationResultKHR => Store the deferred logic to do that in
        // `deferred_operation_post_check`.

        if (dispatch_device_->wrap_handles) {
            deferredOperation = dispatch_device_->Unwrap(deferredOperation);
        }
        std::vector<std::function<void(std::pair<uint32_t, VkPipeline*>)>> cleanup_fn;
        auto find_res = dispatch_device_->deferred_operation_post_check.pop(deferredOperation);
        if (find_res->first) {
            cleanup_fn = std::move(find_res->second);
        }
        // Mutable lambda because we want to move the shared pointer contained in the copied vector
        cleanup_fn.emplace_back([this, chassis_state, pipeline_states](std::pair<uint32_t, VkPipeline*> pipelines) mutable {
            // Just need to capture chassis state to maintain pipeline creations parameters alive, see
            // https://vkdoc.net/chapters/deferred-host-operations#deferred-host-operations-requesting
            (void)chassis_state;
            for (size_t i = 0; i < pipeline_states.size(); ++i) {
                if (pipeline_states[i]->descriptor_heap_embedded_samplers_count > 0) {
                    descriptor_heap_global_embedded_sampler_count_ += pipeline_states[i]->descriptor_heap_embedded_samplers_count;
                }

                pipeline_states[i]->SetHandle(pipelines.second[i]);
                this->Add(std::move(pipeline_states[i]));
            }
        });
        dispatch_device_->deferred_operation_post_check.insert(deferredOperation, cleanup_fn);
    }
}

std::shared_ptr<vvl::Pipeline> DeviceState::CreateDataGraphPipelineState(const VkDataGraphPipelineCreateInfoARM* pCreateInfo,
                                                                         std::shared_ptr<const vvl::PipelineCache> pipeline_cache,
                                                                         std::shared_ptr<const vvl::PipelineLayout>&& layout,
                                                                         spirv::StatelessData* stateless_data) const {
    return std::make_shared<vvl::Pipeline>(*this, pCreateInfo, std::move(pipeline_cache), std::move(layout), stateless_data);
}

bool DeviceState::PreCallValidateCreateDataGraphPipelinesARM(VkDevice device, VkDeferredOperationKHR deferredOperation,
                                                             VkPipelineCache pipelineCache, uint32_t count,
                                                             const VkDataGraphPipelineCreateInfoARM* pCreateInfos,
                                                             const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                             const ErrorObject& error_obj, PipelineStates& pipeline_states,
                                                             chassis::CreateDataGraphPipelinesARM& chassis_state) const {
    pipeline_states.reserve(count);
    auto pipeline_cache = Get<vvl::PipelineCache>(pipelineCache);
    for (uint32_t i = 0; i < count; i++) {
        // Create and initialize internal tracking data structure
        pipeline_states.push_back(CreateDataGraphPipelineState(
            &pCreateInfos[i], pipeline_cache, Get<vvl::PipelineLayout>(pCreateInfos[i].layout), &chassis_state.stateless_data));
    }
    return false;
}

void DeviceState::PostCallRecordCreateDataGraphPipelinesARM(VkDevice device, VkDeferredOperationKHR deferredOperation,
                                                            VkPipelineCache pipelineCache, uint32_t count,
                                                            const VkDataGraphPipelineCreateInfoARM* pCreateInfos,
                                                            const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                            const RecordObject& record_obj, PipelineStates& pipeline_states,
                                                            chassis::CreateDataGraphPipelinesARM& chassis_state) {
    // This API may create pipelines regardless of the return value
    for (uint32_t i = 0; i < count; i++) {
        if (pPipelines[i] != VK_NULL_HANDLE) {
            pipeline_states[i]->SetHandle(pPipelines[i]);
            Add(std::move(pipeline_states[i]));
        }
    }
    pipeline_states.clear();
}

void DeviceState::PostCallRecordCmdDispatchDataGraphARM(VkCommandBuffer commandBuffer, VkDataGraphPipelineSessionARM session,
                                                        const VkDataGraphPipelineDispatchInfoARM* pInfo,
                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<vvl::CommandBuffer>(commandBuffer);
    std::shared_ptr<vvl::DataGraphPipelineSession> pipeline_session = Get<vvl::DataGraphPipelineSession>(session);
    cb_state->AddChild(pipeline_session);
}

void DeviceState::PostCallRecordCreateSampler(VkDevice device, const VkSamplerCreateInfo* pCreateInfo,
                                              const VkAllocationCallbacks* pAllocator, VkSampler* pSampler,
                                              const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    Add(std::make_shared<Sampler>(*pSampler, pCreateInfo));
    if (pCreateInfo->borderColor == VK_BORDER_COLOR_INT_CUSTOM_EXT ||
        pCreateInfo->borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT) {
        custom_border_color_sampler_count++;
    }
}

void DeviceState::PostCallRecordCreateDescriptorSetLayout(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
                                                          const VkAllocationCallbacks* pAllocator,
                                                          VkDescriptorSetLayout* pSetLayout, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<DescriptorSetLayout>(*this, pCreateInfo, *pSetLayout));
}

void DeviceState::PostCallRecordCreatePipelineLayout(VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo,
                                                     const VkAllocationCallbacks* pAllocator, VkPipelineLayout* pPipelineLayout,
                                                     const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<PipelineLayout>(*this, *pPipelineLayout, pCreateInfo));
}

std::shared_ptr<DescriptorPool> DeviceState::CreateDescriptorPoolState(VkDescriptorPool handle,
                                                                       const VkDescriptorPoolCreateInfo* create_info) {
    return std::make_shared<DescriptorPool>(*this, handle, create_info);
}

std::shared_ptr<DescriptorSet> DeviceState::CreateDescriptorSet(VkDescriptorSet handle, DescriptorPool* pool,
                                                                const std::shared_ptr<DescriptorSetLayout const>& layout,
                                                                uint32_t variable_count) {
    return std::make_shared<DescriptorSet>(handle, pool, layout, variable_count, this);
}
std::shared_ptr<vvl::DescriptorSet> DeviceState::CreatePushDescriptorSet(
    const std::shared_ptr<vvl::DescriptorSetLayout const>& layout) {
    auto ds = CreateDescriptorSet(VK_NULL_HANDLE, nullptr, layout, 0);
    NotifyCreated(*ds);
    return ds;
}

void DeviceState::PostCallRecordCreateDescriptorPool(VkDevice device, const VkDescriptorPoolCreateInfo* pCreateInfo,
                                                     const VkAllocationCallbacks* pAllocator, VkDescriptorPool* pDescriptorPool,
                                                     const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(CreateDescriptorPoolState(*pDescriptorPool, pCreateInfo));
}

void DeviceState::PostCallRecordResetDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool,
                                                    VkDescriptorPoolResetFlags flags, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (auto ds_pool_state = Get<DescriptorPool>(descriptorPool)) {
        ds_pool_state->Reset();
    }
}

// We do some state tracking prior so other things can use it at PreCallValidate time as well
bool DeviceState::PreCallValidateAllocateDescriptorSets(VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo,
                                                        VkDescriptorSet* pDescriptorSets, const ErrorObject& error_obj,
                                                        AllocateDescriptorSetsData& ads_state) const {
    const auto* count_allocate_info =
        vku::FindStructInPNextChain<VkDescriptorSetVariableDescriptorCountAllocateInfo>(pAllocateInfo->pNext);

    ads_state.layout_nodes.resize(pAllocateInfo->descriptorSetCount);
    for (uint32_t i = 0; i < pAllocateInfo->descriptorSetCount; i++) {
        if (auto layout = Get<DescriptorSetLayout>(pAllocateInfo->pSetLayouts[i])) {
            ads_state.layout_nodes[i] = layout;
            // Count total descriptors required per type
            for (uint32_t j = 0; j < layout->GetBindingCount(); ++j) {
                const auto& binding_layout = layout->GetDescriptorSetLayoutBindingPtrFromIndex(j);
                uint32_t type_index = static_cast<uint32_t>(binding_layout->descriptorType);
                uint32_t descriptor_count = binding_layout->descriptorCount;
                if (count_allocate_info && i < count_allocate_info->descriptorSetCount) {
                    // Only binding will have this flag
                    if (layout->GetDescriptorBindingFlagsFromIndex(j) & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) {
                        descriptor_count = count_allocate_info->pDescriptorCounts[i];
                    }
                }

                // In VK_KHR_maintenance6 we can finally check for YCbCr samplers that will take multiple descriptor slots.
                // First need to check if has a YCbCr format
                if (binding_layout->pImmutableSamplers != nullptr &&
                    binding_layout->descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER &&
                    IsExtEnabled(extensions.vk_khr_maintenance6)) {
                    if (const auto sampler = Get<Sampler>(*binding_layout->pImmutableSamplers)) {
                        if (sampler->sampler_conversion != VK_NULL_HANDLE) {
                            descriptor_count *= phys_dev_props_core14.maxCombinedImageSamplerDescriptorCount;
                        }
                    }
                }

                ads_state.required_descriptors_by_type[type_index] += descriptor_count;
            }
        }
        // Any unknown layouts will be flagged as errors during ValidateAllocateDescriptorSets() call
    }

    return false;
}

// This is calculated once in DeviceState::PreCallValidateAllocateDescriptorSets, but if found an error, provide a way to show how
// we calculated this
std::string DeviceState::PrintDescriptorAllocation(const VkDescriptorSetAllocateInfo& allocate_info,
                                                   const vvl::DescriptorPool& pool_state, VkDescriptorType type) const {
    std::ostringstream ss;
    ss << "Where " << string_VkDescriptorType(type) << " is found in the pool:\n";
    for (const auto [pool_size_i, pool_size] :
         vvl::enumerate(pool_state.create_info.pPoolSizes, pool_state.create_info.poolSizeCount)) {
        if (pool_size.type == type) {
            ss << "  pPoolSizes[" << pool_size_i << "].descriptorCount = " << pool_size.descriptorCount << '\n';
        }
    }

    const auto* count_allocate_info =
        vku::FindStructInPNextChain<VkDescriptorSetVariableDescriptorCountAllocateInfo>(allocate_info.pNext);
    ss << "Where the allocation are being requested:\n";
    for (const auto [set_layout_i, set_layout] : vvl::enumerate(allocate_info.pSetLayouts, allocate_info.descriptorSetCount)) {
        if (auto ds_layout_state = Get<vvl::DescriptorSetLayout>(set_layout)) {
            for (uint32_t i = 0; i < ds_layout_state->GetBindingCount(); ++i) {
                const auto& binding_layout = ds_layout_state->GetDescriptorSetLayoutBindingPtrFromIndex(i);
                if (binding_layout->descriptorType != type) {
                    continue;
                }
                bool normal_message = true;
                if (count_allocate_info && i < count_allocate_info->descriptorSetCount &&
                    (ds_layout_state->GetDescriptorBindingFlagsFromIndex(i) &
                     VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT)) {
                    ss << "  pSetLayouts[" << set_layout_i << "]::pBindings[" << i
                       << "].descriptorCount = " << count_allocate_info->pDescriptorCounts[i]
                       << " (adjusted for VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT)\n";
                    normal_message = false;
                } else if (binding_layout->pImmutableSamplers != nullptr && type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER &&
                           IsExtEnabled(extensions.vk_khr_maintenance6)) {
                    if (const auto sampler = Get<Sampler>(*binding_layout->pImmutableSamplers)) {
                        if (sampler->sampler_conversion != VK_NULL_HANDLE) {
                            const uint32_t ycbcr_count =
                                binding_layout->descriptorCount * phys_dev_props_core14.maxCombinedImageSamplerDescriptorCount;
                            ss << "  pSetLayouts[" << set_layout_i << "]::pBindings[" << i << "].descriptorCount = " << ycbcr_count
                               << " (adjusted by multiplying maxCombinedImageSamplerDescriptorCount which is "
                               << phys_dev_props_core14.maxCombinedImageSamplerDescriptorCount << ")\n";
                            normal_message = false;
                        }
                    }
                }

                if (normal_message) {
                    ss << "  pSetLayouts[" << set_layout_i << "]::pBindings[" << i
                       << "].descriptorCount = " << binding_layout->descriptorCount << '\n';
                }
            }
        }
    }

    return ss.str();
}

// Allocation state was good and call down chain was made so update state based on allocating descriptor sets
void DeviceState::PostCallRecordAllocateDescriptorSets(VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo,
                                                       VkDescriptorSet* pDescriptorSets, const RecordObject& record_obj,
                                                       AllocateDescriptorSetsData& ads_state) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    // All the updates are contained in a single vvl function
    if (auto ds_pool_state = Get<DescriptorPool>(pAllocateInfo->descriptorPool)) {
        ds_pool_state->Allocate(pAllocateInfo, pDescriptorSets, ads_state);
    }
}

void DeviceState::PreCallRecordFreeDescriptorSets(VkDevice device, VkDescriptorPool descriptorPool, uint32_t count,
                                                  const VkDescriptorSet* pDescriptorSets, const RecordObject& record_obj) {
    if (auto ds_pool_state = Get<DescriptorPool>(descriptorPool)) {
        ds_pool_state->Free(count, pDescriptorSets);
    }
}

void DeviceState::PerformUpdateDescriptorSets(uint32_t write_count, const VkWriteDescriptorSet* p_wds, uint32_t copy_count,
                                              const VkCopyDescriptorSet* p_cds) {
    // Write updates first
    for (uint32_t i = 0; i < write_count; ++i) {
        const VkDescriptorSet dst_set = p_wds[i].dstSet;
        if (auto set_node = Get<DescriptorSet>(dst_set)) {
            set_node->PerformWriteUpdate(p_wds[i]);
        }
    }
    // Now copy updates
    for (uint32_t i = 0; i < copy_count; ++i) {
        const VkDescriptorSet dst_set = p_cds[i].dstSet;
        const VkDescriptorSet src_set = p_cds[i].srcSet;
        auto src_node = Get<DescriptorSet>(src_set);
        auto dst_node = Get<DescriptorSet>(dst_set);
        if (src_node && dst_node) {
            dst_node->PerformCopyUpdate(p_cds[i], *src_node);
        }
    }
}

void DeviceState::PreCallRecordUpdateDescriptorSets(VkDevice device, uint32_t descriptorWriteCount,
                                                    const VkWriteDescriptorSet* pDescriptorWrites, uint32_t descriptorCopyCount,
                                                    const VkCopyDescriptorSet* pDescriptorCopies, const RecordObject& record_obj) {
    PerformUpdateDescriptorSets(descriptorWriteCount, pDescriptorWrites, descriptorCopyCount, pDescriptorCopies);
}

void DeviceState::PostCallRecordAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo,
                                                       VkCommandBuffer* pCommandBuffers, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (auto pool = Get<CommandPool>(pAllocateInfo->commandPool)) {
        pool->Allocate(pAllocateInfo, pCommandBuffers);
    }
}

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

void DeviceState::PostCallRecordEndCommandBuffer(VkCommandBuffer commandBuffer, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->End(record_obj.result);
}

void DeviceState::PostCallRecordResetCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags,
                                                   const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->Reset(record_obj.location);
}

void DeviceState::PostCallRecordCmdBindPipeline(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint,
                                                VkPipeline pipeline, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    auto pipeline_state = Get<Pipeline>(pipeline);
    ASSERT_AND_RETURN(pipeline_state);
    cb_state->RecordBindPipeline(pipelineBindPoint, *pipeline_state);

    if (!disabled[command_buffer_state]) {
        cb_state->AddChild(pipeline_state);
    }
}

void DeviceState::PostCallRecordCmdSetViewport(VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount,
                                               const VkViewport* pViewports, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetViewport(firstViewport, viewportCount, pViewports);
}

void DeviceState::PostCallRecordCmdSetExclusiveScissorNV(VkCommandBuffer commandBuffer, uint32_t firstExclusiveScissor,
                                                         uint32_t exclusiveScissorCount, const VkRect2D* pExclusiveScissors,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_NV);
    // TODO: We don't have VUIDs for validating that all exclusive scissors have been set.
    // cb_state->exclusiveScissorMask |= ((1u << exclusiveScissorCount) - 1u) << firstExclusiveScissor;

    cb_state->dynamic_state_value.exclusive_scissor_first = firstExclusiveScissor;
    cb_state->dynamic_state_value.exclusive_scissor_count = exclusiveScissorCount;
    cb_state->dynamic_state_value.exclusive_scissors.resize(firstExclusiveScissor + exclusiveScissorCount);
    for (size_t i = 0; i < exclusiveScissorCount; ++i) {
        cb_state->dynamic_state_value.exclusive_scissors[firstExclusiveScissor + i] = pExclusiveScissors[i];
    }
}

void DeviceState::PostCallRecordCmdSetExclusiveScissorEnableNV(VkCommandBuffer commandBuffer, uint32_t firstExclusiveScissor,
                                                               uint32_t exclusiveScissorCount,
                                                               const VkBool32* pExclusiveScissorEnables,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_ENABLE_NV);

    cb_state->dynamic_state_value.exclusive_scissor_enable_first = firstExclusiveScissor;
    cb_state->dynamic_state_value.exclusive_scissor_enable_count = exclusiveScissorCount;
    cb_state->dynamic_state_value.exclusive_scissor_enables.resize(firstExclusiveScissor + exclusiveScissorCount);
    for (size_t i = 0; i < exclusiveScissorCount; ++i) {
        cb_state->dynamic_state_value.exclusive_scissor_enables[firstExclusiveScissor + i] = pExclusiveScissorEnables[i];
    }
}

void DeviceState::PostCallRecordCmdBindShadingRateImageNV(VkCommandBuffer commandBuffer, VkImageView imageView,
                                                          VkImageLayout imageLayout, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);

    if (imageView != VK_NULL_HANDLE) {
        auto view_state = Get<ImageView>(imageView);
        cb_state->AddChild(view_state);
    }
}

void DeviceState::PostCallRecordCmdSetViewportShadingRatePaletteNV(VkCommandBuffer commandBuffer, uint32_t firstViewport,
                                                                   uint32_t viewportCount,
                                                                   const VkShadingRatePaletteNV* pShadingRatePalettes,
                                                                   const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_VIEWPORT_SHADING_RATE_PALETTE_NV);
    // TODO: We don't have VUIDs for validating that all shading rate palettes have been set.
    // cb_state->shadingRatePaletteMask |= ((1u << viewportCount) - 1u) << firstViewport;
    cb_state->dynamic_state_value.shading_rate_palette_count = viewportCount;
}

std::shared_ptr<AccelerationStructureNV> DeviceState::CreateAccelerationStructureState(
    VkAccelerationStructureNV handle, const VkAccelerationStructureCreateInfoNV* create_info) {
    return std::make_shared<AccelerationStructureNV>(device, handle, create_info);
}

void DeviceState::PostCallRecordCreateAccelerationStructureNV(VkDevice device,
                                                              const VkAccelerationStructureCreateInfoNV* pCreateInfo,
                                                              const VkAllocationCallbacks* pAllocator,
                                                              VkAccelerationStructureNV* pAccelerationStructure,
                                                              const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(CreateAccelerationStructureState(*pAccelerationStructure, pCreateInfo));
}

void DeviceState::PostCallRecordCreateAccelerationStructureKHR(VkDevice device,
                                                               const VkAccelerationStructureCreateInfoKHR* pCreateInfo,
                                                               const VkAllocationCallbacks* pAllocator,
                                                               VkAccelerationStructureKHR* pAccelerationStructure,
                                                               const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<AccelerationStructureKHR>(*this, pCreateInfo, *pAccelerationStructure));
}

void DeviceState::PostCallRecordCreateAccelerationStructure2KHR(VkDevice device,
                                                                const VkAccelerationStructureCreateInfo2KHR* pCreateInfo,
                                                                const VkAllocationCallbacks* pAllocator,
                                                                VkAccelerationStructureKHR* pAccelerationStructure,
                                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<AccelerationStructureKHR>(*this, pCreateInfo, *pAccelerationStructure));
}

// helper method for device side acceleration structure builds
void DeviceState::RecordDeviceAccelerationStructureBuildInfo(CommandBuffer& cb_state,
                                                             const VkAccelerationStructureBuildGeometryInfoKHR& info) {
    auto dst_as_state = Get<AccelerationStructureKHR>(info.dstAccelerationStructure);
    if (disabled[command_buffer_state]) {
        return;
    }
    if (dst_as_state) {
        cb_state.AddChild(dst_as_state);
    }
    auto src_as_state = Get<AccelerationStructureKHR>(info.srcAccelerationStructure);
    if (src_as_state) {
        cb_state.AddChild(src_as_state);
    }

    // Issue https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/6461
    // showed that it is incorrect to try to add buffers obtained through a call to GetBuffersByAddress as children to a command
    // buffer
}

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

    cb_state->RecordCommand(record_obj.location);
    for (const auto [i, info] : enumerate(pInfos, infoCount)) {
        RecordDeviceAccelerationStructureBuildInfo(*cb_state, info);
    }
}

void DeviceState::PostCallRecordCmdBuildAccelerationStructuresIndirectKHR(VkCommandBuffer commandBuffer, uint32_t infoCount,
                                                                          const VkAccelerationStructureBuildGeometryInfoKHR* pInfos,
                                                                          const VkDeviceAddress* pIndirectDeviceAddresses,
                                                                          const uint32_t* pIndirectStrides,
                                                                          const uint32_t* const* ppMaxPrimitiveCounts,
                                                                          const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);

    cb_state->RecordCommand(record_obj.location);
    for (uint32_t i = 0; i < infoCount; i++) {
        RecordDeviceAccelerationStructureBuildInfo(*cb_state, pInfos[i]);

        // Issue https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/6461
        // showed that it is incorrect to try to add buffers obtained through a call to GetBuffersByAddress as children to a command
        // buffer
    }
}

void DeviceState::PostCallRecordGetAccelerationStructureMemoryRequirementsNV(
    VkDevice device, const VkAccelerationStructureMemoryRequirementsInfoNV* pInfo, VkMemoryRequirements2* pMemoryRequirements,
    const RecordObject& record_obj) {
    if (auto as_state = Get<AccelerationStructureNV>(pInfo->accelerationStructure)) {
        if (pInfo->type == VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV) {
            as_state->memory_requirements_checked = true;
        } else if (pInfo->type == VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV) {
            as_state->build_scratch_memory_requirements_checked = true;
        } else if (pInfo->type == VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV) {
            as_state->update_scratch_memory_requirements_checked = true;
        }
    }
}

void DeviceState::PostCallRecordBindAccelerationStructureMemoryNV(VkDevice device, uint32_t bindInfoCount,
                                                                  const VkBindAccelerationStructureMemoryInfoNV* pBindInfos,
                                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    for (uint32_t i = 0; i < bindInfoCount; i++) {
        const VkBindAccelerationStructureMemoryInfoNV& info = pBindInfos[i];

        if (auto as_state = Get<AccelerationStructureNV>(info.accelerationStructure)) {
            // Track objects tied to memory
            if (auto memory_state = Get<DeviceMemory>(info.memory)) {
                as_state->BindMemory(as_state.get(), memory_state, info.memoryOffset, 0u, as_state->memory_requirements.size);
            }

            // GPU validation of top level acceleration structure building needs acceleration structure handles.
            // XXX TODO: Query device address for KHR extension
            if (enabled[gpu_validation]) {
                DispatchGetAccelerationStructureHandleNV(device, info.accelerationStructure, 8, &as_state->opaque_handle);
            }
        }
    }
}

void DeviceState::PostCallRecordCmdBuildAccelerationStructureNV(VkCommandBuffer commandBuffer,
                                                                const VkAccelerationStructureInfoNV* pInfo, VkBuffer instanceData,
                                                                VkDeviceSize instanceOffset, VkBool32 update,
                                                                VkAccelerationStructureNV dst, VkAccelerationStructureNV src,
                                                                VkBuffer scratch, VkDeviceSize scratchOffset,
                                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    if (!cb_state) {
        return;
    }
    cb_state->RecordCommand(record_obj.location);

    auto dst_as_state = Get<AccelerationStructureNV>(dst);
    if (dst_as_state) {
        dst_as_state->Build(pInfo);
        if (!disabled[command_buffer_state]) {
            cb_state->AddChild(dst_as_state);
        }
    }
    if (!disabled[command_buffer_state]) {
        if (auto src_as_state = Get<AccelerationStructureNV>(src)) {
            cb_state->AddChild(src_as_state);
        }
        if (auto instance_buffer = Get<Buffer>(instanceData)) {
            cb_state->AddChild(instance_buffer);
        }
        if (auto scratch_buffer = Get<Buffer>(scratch)) {
            cb_state->AddChild(scratch_buffer);
        }

        for (uint32_t i = 0; i < pInfo->geometryCount; i++) {
            const auto& geom = pInfo->pGeometries[i];

            if (auto vertex_buffer = Get<Buffer>(geom.geometry.triangles.vertexData)) {
                cb_state->AddChild(vertex_buffer);
            }
            if (auto index_buffer = Get<Buffer>(geom.geometry.triangles.indexData)) {
                cb_state->AddChild(index_buffer);
            }
            if (auto transform_buffer = Get<Buffer>(geom.geometry.triangles.transformData)) {
                cb_state->AddChild(transform_buffer);
            }
            if (auto aabb_buffer = Get<Buffer>(geom.geometry.aabbs.aabbData)) {
                cb_state->AddChild(aabb_buffer);
            }
        }
    }
}

void DeviceState::PostCallRecordCmdCopyAccelerationStructureNV(VkCommandBuffer commandBuffer, VkAccelerationStructureNV dst,
                                                               VkAccelerationStructureNV src,
                                                               VkCopyAccelerationStructureModeNV mode,
                                                               const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto src_as_state = Get<AccelerationStructureNV>(src);
    auto dst_as_state = Get<AccelerationStructureNV>(dst);
    ASSERT_AND_RETURN(src_as_state && dst_as_state);
    cb_state->AddChild(src_as_state);
    cb_state->AddChild(dst_as_state);

    cb_state->RecordCommand(record_obj.location);
    dst_as_state->built = true;
    dst_as_state->build_info = src_as_state->build_info;
}

void DeviceState::PreCallRecordDestroyAccelerationStructureKHR(VkDevice device, VkAccelerationStructureKHR accelerationStructure,
                                                               const VkAllocationCallbacks* pAllocator,
                                                               const RecordObject& record_obj) {
    if (auto as_state = Get<vvl::AccelerationStructureKHR>(accelerationStructure)) {
        if (as_state->GetAccelerationStructureAddress() != 0) {
            WriteLockGuard lock(as_with_addresses.array_mutex);
            auto as_found_it = std::find(as_with_addresses.array.begin(), as_with_addresses.array.end(), as_state.get());
            while (as_found_it != as_with_addresses.array.end()) {
                const size_t i = std::distance(as_with_addresses.array.begin(), as_found_it);
                std::swap(as_with_addresses.array[i], as_with_addresses.array[as_with_addresses.array.size() - 1]);
                as_with_addresses.array.resize(as_with_addresses.array.size() - 1);

                as_found_it = std::find(as_with_addresses.array.begin() + i, as_with_addresses.array.end(), as_state.get());
            }
            as_state->SetAccelerationStructureAddress(0);
        }
    }
    // Remove any serialized_as_map_ entries whose source AS is being destroyed.
    // Without this, the map grows unboundedly because entries are never removed.
    // Once the source AS is gone its build info can no longer be propagated anyway.
    {
        WriteLockGuard guard(serialized_as_map_lock_);
        for (auto it = serialized_as_map_.begin(); it != serialized_as_map_.end();) {
            if (it->second == accelerationStructure) {
                it = serialized_as_map_.erase(it);
            } else {
                ++it;
            }
        }
    }
    Destroy<AccelerationStructureKHR>(accelerationStructure);
}

void DeviceState::PreCallRecordDestroyAccelerationStructureNV(VkDevice device, VkAccelerationStructureNV accelerationStructure,
                                                              const VkAllocationCallbacks* pAllocator,
                                                              const RecordObject& record_obj) {
    Destroy<AccelerationStructureNV>(accelerationStructure);
}

void DeviceState::PostCallRecordCmdSetViewportWScalingNV(VkCommandBuffer commandBuffer, uint32_t firstViewport,
                                                         uint32_t viewportCount, const VkViewportWScalingNV* pViewportWScalings,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_VIEWPORT_W_SCALING_NV);
    cb_state->dynamic_state_value.viewport_w_scaling_first = firstViewport;
    cb_state->dynamic_state_value.viewport_w_scaling_count = viewportCount;
    cb_state->dynamic_state_value.viewport_w_scalings.resize(viewportCount);
    for (size_t i = 0; i < viewportCount; ++i) {
        cb_state->dynamic_state_value.viewport_w_scalings[i] = pViewportWScalings[i];
    }
}

void DeviceState::PostCallRecordCmdSetLineWidth(VkCommandBuffer commandBuffer, float lineWidth, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_LINE_WIDTH);
}

void DeviceState::PostCallRecordCmdSetLineStipple(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor,
                                                  uint16_t lineStipplePattern, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_LINE_STIPPLE);
}

void DeviceState::PostCallRecordCmdSetLineStippleEXT(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor,
                                                     uint16_t lineStipplePattern, const RecordObject& record_obj) {
    PostCallRecordCmdSetLineStipple(commandBuffer, lineStippleFactor, lineStipplePattern, record_obj);
}

void DeviceState::PostCallRecordCmdSetLineStippleKHR(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor,
                                                     uint16_t lineStipplePattern, const RecordObject& record_obj) {
    PostCallRecordCmdSetLineStipple(commandBuffer, lineStippleFactor, lineStipplePattern, record_obj);
}

void DeviceState::PostCallRecordCmdSetDepthBias(VkCommandBuffer commandBuffer, float depthBiasConstantFactor, float depthBiasClamp,
                                                float depthBiasSlopeFactor, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_BIAS);
}

void DeviceState::PostCallRecordCmdSetDepthBias2EXT(VkCommandBuffer commandBuffer, const VkDepthBiasInfoEXT* pDepthBiasInfo,
                                                    const RecordObject& record_obj) {
    PostCallRecordCmdSetDepthBias(commandBuffer, pDepthBiasInfo->depthBiasConstantFactor, pDepthBiasInfo->depthBiasClamp,
                                  pDepthBiasInfo->depthBiasSlopeFactor, record_obj);
}

void DeviceState::PostCallRecordCmdSetScissor(VkCommandBuffer commandBuffer, uint32_t firstScissor, uint32_t scissorCount,
                                              const VkRect2D* pScissors, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetScissor(firstScissor, scissorCount);
}

void DeviceState::PostCallRecordCmdSetBlendConstants(VkCommandBuffer commandBuffer, const float blendConstants[4],
                                                     const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_BLEND_CONSTANTS);
}

void DeviceState::PostCallRecordCmdSetDepthBounds(VkCommandBuffer commandBuffer, float minDepthBounds, float maxDepthBounds,
                                                  const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_BOUNDS);
}

void DeviceState::PostCallRecordCmdSetStencilCompareMask(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask,
                                                         uint32_t compareMask, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_STENCIL_COMPARE_MASK);
}

void DeviceState::PostCallRecordCmdSetStencilWriteMask(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask,
                                                       uint32_t writeMask, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_STENCIL_WRITE_MASK);
    if (faceMask == VK_STENCIL_FACE_FRONT_BIT || faceMask == VK_STENCIL_FACE_FRONT_AND_BACK) {
        cb_state->dynamic_state_value.write_mask_front = writeMask;
    }
    if (faceMask == VK_STENCIL_FACE_BACK_BIT || faceMask == VK_STENCIL_FACE_FRONT_AND_BACK) {
        cb_state->dynamic_state_value.write_mask_back = writeMask;
    }
}

void DeviceState::PostCallRecordCmdSetStencilReference(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask,
                                                       uint32_t reference, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_STENCIL_REFERENCE);
}

// Update the bound state for the bind point, including the effects of incompatible pipeline layouts
void DeviceState::PostCallRecordCmdBindDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint,
                                                      VkPipelineLayout layout, uint32_t firstSet, uint32_t setCount,
                                                      const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount,
                                                      const uint32_t* pDynamicOffsets, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto pipeline_layout = Get<PipelineLayout>(layout);
    if (!cb_state || !pipeline_layout) {
        return;
    }
    cb_state->RecordCommand(record_obj.location);

    // legacy descriptor binding invalidates any previous call to vkCmdBindDescriptorBuffersEXT
    cb_state->descriptor_buffer.binding_info.clear();

    std::shared_ptr<DescriptorSet> no_push_desc;

    cb_state->UpdateLastBoundDescriptorSets(pipelineBindPoint, pipeline_layout, firstSet, setCount, pDescriptorSets, no_push_desc,
                                            dynamicOffsetCount, pDynamicOffsets, record_obj.location);

    cb_state->SetDescriptorMode(DescriptorModeClassic, record_obj.location.function);
}

void DeviceState::PostCallRecordCmdBindDescriptorSets2(VkCommandBuffer commandBuffer,
                                                       const VkBindDescriptorSetsInfo* pBindDescriptorSetsInfo,
                                                       const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto pipeline_layout = Get<PipelineLayout>(pBindDescriptorSetsInfo->layout);
    ASSERT_AND_RETURN(cb_state && pipeline_layout);

    cb_state->RecordCommand(record_obj.location);

    // legacy descriptor binding invalidates any previous call to vkCmdBindDescriptorBuffersEXT
    cb_state->descriptor_buffer.binding_info.clear();

    std::shared_ptr<DescriptorSet> no_push_desc;

    if (IsStageInPipelineBindPoint(pBindDescriptorSetsInfo->stageFlags, VK_PIPELINE_BIND_POINT_GRAPHICS)) {
        cb_state->UpdateLastBoundDescriptorSets(
            VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, pBindDescriptorSetsInfo->firstSet,
            pBindDescriptorSetsInfo->descriptorSetCount, pBindDescriptorSetsInfo->pDescriptorSets, no_push_desc,
            pBindDescriptorSetsInfo->dynamicOffsetCount, pBindDescriptorSetsInfo->pDynamicOffsets, record_obj.location);
    }
    if (IsStageInPipelineBindPoint(pBindDescriptorSetsInfo->stageFlags, VK_PIPELINE_BIND_POINT_COMPUTE)) {
        cb_state->UpdateLastBoundDescriptorSets(
            VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, pBindDescriptorSetsInfo->firstSet,
            pBindDescriptorSetsInfo->descriptorSetCount, pBindDescriptorSetsInfo->pDescriptorSets, no_push_desc,
            pBindDescriptorSetsInfo->dynamicOffsetCount, pBindDescriptorSetsInfo->pDynamicOffsets, record_obj.location);
    }
    if (IsStageInPipelineBindPoint(pBindDescriptorSetsInfo->stageFlags, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR)) {
        cb_state->UpdateLastBoundDescriptorSets(
            VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline_layout, pBindDescriptorSetsInfo->firstSet,
            pBindDescriptorSetsInfo->descriptorSetCount, pBindDescriptorSetsInfo->pDescriptorSets, no_push_desc,
            pBindDescriptorSetsInfo->dynamicOffsetCount, pBindDescriptorSetsInfo->pDynamicOffsets, record_obj.location);
    }

    cb_state->SetDescriptorMode(DescriptorModeClassic, record_obj.location.function);
}

void DeviceState::PostCallRecordCmdBindDescriptorSets2KHR(VkCommandBuffer commandBuffer,
                                                          const VkBindDescriptorSetsInfoKHR* pBindDescriptorSetsInfo,
                                                          const RecordObject& record_obj) {
    PostCallRecordCmdBindDescriptorSets2(commandBuffer, pBindDescriptorSetsInfo, record_obj);
}

void DeviceState::PostCallRecordCmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint,
                                                     VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount,
                                                     const VkWriteDescriptorSet* pDescriptorWrites,
                                                     const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto pipeline_layout = Get<PipelineLayout>(layout);
    ASSERT_AND_RETURN(pipeline_layout);
    cb_state->PushDescriptorSetState(pipelineBindPoint, pipeline_layout, set, descriptorWriteCount, pDescriptorWrites,
                                     record_obj.location);

    if (pipeline_layout->has_descriptor_buffer) {
        cb_state->SetDescriptorMode(DescriptorModeBuffer, record_obj.location.function);
    } else {
        cb_state->SetDescriptorMode(DescriptorModeClassic, record_obj.location.function);
    }
}

void DeviceState::PostCallRecordCmdPushDescriptorSetKHR(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint,
                                                        VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount,
                                                        const VkWriteDescriptorSet* pDescriptorWrites,
                                                        const RecordObject& record_obj) {
    PostCallRecordCmdPushDescriptorSet(commandBuffer, pipelineBindPoint, layout, set, descriptorWriteCount, pDescriptorWrites,
                                       record_obj);
}

void DeviceState::PostCallRecordCmdPushDescriptorSet2(VkCommandBuffer commandBuffer,
                                                      const VkPushDescriptorSetInfo* pPushDescriptorSetInfo,
                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto pipeline_layout = Get<PipelineLayout>(pPushDescriptorSetInfo->layout);
    ASSERT_AND_RETURN(pipeline_layout);
    if (IsStageInPipelineBindPoint(pPushDescriptorSetInfo->stageFlags, VK_PIPELINE_BIND_POINT_GRAPHICS)) {
        cb_state->PushDescriptorSetState(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, pPushDescriptorSetInfo->set,
                                         pPushDescriptorSetInfo->descriptorWriteCount, pPushDescriptorSetInfo->pDescriptorWrites,
                                         record_obj.location);
    }
    if (IsStageInPipelineBindPoint(pPushDescriptorSetInfo->stageFlags, VK_PIPELINE_BIND_POINT_COMPUTE)) {
        cb_state->PushDescriptorSetState(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, pPushDescriptorSetInfo->set,
                                         pPushDescriptorSetInfo->descriptorWriteCount, pPushDescriptorSetInfo->pDescriptorWrites,
                                         record_obj.location);
    }
    if (IsStageInPipelineBindPoint(pPushDescriptorSetInfo->stageFlags, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR)) {
        cb_state->PushDescriptorSetState(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline_layout, pPushDescriptorSetInfo->set,
                                         pPushDescriptorSetInfo->descriptorWriteCount, pPushDescriptorSetInfo->pDescriptorWrites,
                                         record_obj.location);
    }

    if (pipeline_layout->has_descriptor_buffer) {
        cb_state->SetDescriptorMode(DescriptorModeBuffer, record_obj.location.function);
    } else {
        cb_state->SetDescriptorMode(DescriptorModeClassic, record_obj.location.function);
    }
}

void DeviceState::PostCallRecordCmdPushDescriptorSet2KHR(VkCommandBuffer commandBuffer,
                                                         const VkPushDescriptorSetInfoKHR* pPushDescriptorSetInfo,
                                                         const RecordObject& record_obj) {
    PostCallRecordCmdPushDescriptorSet2(commandBuffer, pPushDescriptorSetInfo, record_obj);
}

void DeviceState::PostCallRecordCmdBindDescriptorBuffersEXT(VkCommandBuffer commandBuffer, uint32_t bufferCount,
                                                            const VkDescriptorBufferBindingInfoEXT* pBindingInfos,
                                                            const RecordObject& record_obj) {
    auto cb_state = Get<CommandBuffer>(commandBuffer);
    cb_state->RecordBindDescriptorBuffer(*this, bufferCount, pBindingInfos, record_obj.location);

    // We tried to once call SetDescriptorMode(DescriptorModeBuffer) here
    // but after much discussion
    // (https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/7504#note_546442)
    // it seems this call alone is not the correct spot to invalidate anything
}

void DeviceState::PostCallRecordCmdBindDescriptorBufferEmbeddedSamplersEXT(VkCommandBuffer commandBuffer,
                                                                           VkPipelineBindPoint pipelineBindPoint,
                                                                           VkPipelineLayout layout, uint32_t set,
                                                                           const RecordObject& record_obj) {
    auto cb_state = Get<CommandBuffer>(commandBuffer);
    auto pipeline_layout = Get<PipelineLayout>(layout);
    ASSERT_AND_RETURN(pipeline_layout);

    LastBound& last_bound = cb_state->lastBound[ConvertToVvlBindPoint(pipelineBindPoint)];
    cb_state->UpdateLastBoundDescriptorBuffersEmbedded(last_bound, pipeline_layout, set);
    cb_state->SetDescriptorMode(DescriptorModeBuffer, record_obj.location.function);
}

void DeviceState::PostCallRecordCmdBindDescriptorBufferEmbeddedSamplers2EXT(
    VkCommandBuffer commandBuffer, const VkBindDescriptorBufferEmbeddedSamplersInfoEXT* pBindDescriptorBufferEmbeddedSamplersInfo,
    const RecordObject& record_obj) {
    auto cb_state = Get<CommandBuffer>(commandBuffer);

    auto pipeline_layout = Get<PipelineLayout>(pBindDescriptorBufferEmbeddedSamplersInfo->layout);
    ASSERT_AND_RETURN(pipeline_layout);

    if (IsStageInPipelineBindPoint(pBindDescriptorBufferEmbeddedSamplersInfo->stageFlags, VK_PIPELINE_BIND_POINT_GRAPHICS)) {
        LastBound& last_bound = cb_state->lastBound[BindPointGraphics];
        cb_state->UpdateLastBoundDescriptorBuffersEmbedded(last_bound, pipeline_layout,
                                                           pBindDescriptorBufferEmbeddedSamplersInfo->set);
    }
    if (IsStageInPipelineBindPoint(pBindDescriptorBufferEmbeddedSamplersInfo->stageFlags, VK_PIPELINE_BIND_POINT_COMPUTE)) {
        LastBound& last_bound = cb_state->lastBound[BindPointCompute];
        cb_state->UpdateLastBoundDescriptorBuffersEmbedded(last_bound, pipeline_layout,
                                                           pBindDescriptorBufferEmbeddedSamplersInfo->set);
    }
    if (IsStageInPipelineBindPoint(pBindDescriptorBufferEmbeddedSamplersInfo->stageFlags, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR)) {
        LastBound& last_bound = cb_state->lastBound[BindPointRayTracing];
        cb_state->UpdateLastBoundDescriptorBuffersEmbedded(last_bound, pipeline_layout,
                                                           pBindDescriptorBufferEmbeddedSamplersInfo->set);
    }

    cb_state->SetDescriptorMode(DescriptorModeBuffer, record_obj.location.function);
}

void DeviceState::PostCallRecordCmdSetDescriptorBufferOffsetsEXT(VkCommandBuffer commandBuffer,
                                                                 VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout,
                                                                 uint32_t firstSet, uint32_t setCount,
                                                                 const uint32_t* pBufferIndices, const VkDeviceSize* pOffsets,
                                                                 const RecordObject& record_obj) {
    auto cb_state = Get<CommandBuffer>(commandBuffer);
    auto pipeline_layout = Get<PipelineLayout>(layout);
    ASSERT_AND_RETURN(pipeline_layout);

    LastBound& last_bound = cb_state->lastBound[ConvertToVvlBindPoint(pipelineBindPoint)];
    cb_state->UpdateLastBoundDescriptorBuffers(last_bound, pipeline_layout, firstSet, setCount, pBufferIndices, pOffsets);

    cb_state->SetDescriptorMode(DescriptorModeBuffer, record_obj.location.function);
}

void DeviceState::PostCallRecordCmdSetDescriptorBufferOffsets2EXT(
    VkCommandBuffer commandBuffer, const VkSetDescriptorBufferOffsetsInfoEXT* pSetDescriptorBufferOffsetsInfo,
    const RecordObject& record_obj) {
    auto cb_state = Get<CommandBuffer>(commandBuffer);
    auto pipeline_layout = Get<PipelineLayout>(pSetDescriptorBufferOffsetsInfo->layout);
    ASSERT_AND_RETURN(pipeline_layout);

    if (IsStageInPipelineBindPoint(pSetDescriptorBufferOffsetsInfo->stageFlags, VK_PIPELINE_BIND_POINT_GRAPHICS)) {
        LastBound& last_bound = cb_state->lastBound[BindPointGraphics];
        cb_state->UpdateLastBoundDescriptorBuffers(
            last_bound, pipeline_layout, pSetDescriptorBufferOffsetsInfo->firstSet, pSetDescriptorBufferOffsetsInfo->setCount,
            pSetDescriptorBufferOffsetsInfo->pBufferIndices, pSetDescriptorBufferOffsetsInfo->pOffsets);
    }
    if (IsStageInPipelineBindPoint(pSetDescriptorBufferOffsetsInfo->stageFlags, VK_PIPELINE_BIND_POINT_COMPUTE)) {
        LastBound& last_bound = cb_state->lastBound[BindPointCompute];
        cb_state->UpdateLastBoundDescriptorBuffers(
            last_bound, pipeline_layout, pSetDescriptorBufferOffsetsInfo->firstSet, pSetDescriptorBufferOffsetsInfo->setCount,
            pSetDescriptorBufferOffsetsInfo->pBufferIndices, pSetDescriptorBufferOffsetsInfo->pOffsets);
    }
    if (IsStageInPipelineBindPoint(pSetDescriptorBufferOffsetsInfo->stageFlags, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR)) {
        LastBound& last_bound = cb_state->lastBound[BindPointRayTracing];
        cb_state->UpdateLastBoundDescriptorBuffers(
            last_bound, pipeline_layout, pSetDescriptorBufferOffsetsInfo->firstSet, pSetDescriptorBufferOffsetsInfo->setCount,
            pSetDescriptorBufferOffsetsInfo->pBufferIndices, pSetDescriptorBufferOffsetsInfo->pOffsets);
    }

    cb_state->SetDescriptorMode(DescriptorModeBuffer, record_obj.location.function);
}

void DeviceState::PostCallRecordCmdPushConstants(VkCommandBuffer commandBuffer, VkPipelineLayout layout,
                                                 VkShaderStageFlags stageFlags, uint32_t offset, uint32_t size, const void* pValues,
                                                 const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto pipeline_layout_state = Get<PipelineLayout>(layout);
    ASSERT_AND_RETURN(cb_state && pipeline_layout_state);

    cb_state->RecordCommand(record_obj.location);
    cb_state->RecordPushConstants(*pipeline_layout_state, stageFlags, offset, size, pValues);

    const DescriptorMode descriptor_mode =
        pipeline_layout_state->has_descriptor_buffer ? DescriptorModeBuffer : DescriptorModeClassic;
    cb_state->InvalidateDescriptorMode(DescriptorModeHeap, descriptor_mode, record_obj.location.function);
}

void DeviceState::PostCallRecordCmdPushConstants2(VkCommandBuffer commandBuffer, const VkPushConstantsInfo* pPushConstantsInfo,
                                                  const RecordObject& record_obj) {
    PostCallRecordCmdPushConstants(commandBuffer, pPushConstantsInfo->layout, pPushConstantsInfo->stageFlags,
                                   pPushConstantsInfo->offset, pPushConstantsInfo->size, pPushConstantsInfo->pValues, record_obj);
}

void DeviceState::PostCallRecordCmdPushConstants2KHR(VkCommandBuffer commandBuffer,
                                                     const VkPushConstantsInfoKHR* pPushConstantsInfo,
                                                     const RecordObject& record_obj) {
    PostCallRecordCmdPushConstants2(commandBuffer, pPushConstantsInfo, record_obj);
}

static VkDeviceSize GetBufferRegionSize(const std::shared_ptr<const Buffer>& buffer_state, VkDeviceSize offset, VkDeviceSize size) {
    if (buffer_state) {
        const auto buffer_size = buffer_state->GetSize();
        if (offset < buffer_size) {
            if (size == VK_WHOLE_SIZE) {
                return buffer_size - offset;
            } else if ((offset + size) <= buffer_size) {
                return size;
            }
        }
    }
    return 0;
}

void DeviceState::PostCallRecordCmdBindIndexBuffer(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                   VkIndexType indexType, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBindIndexbuffer(record_obj.location);

    if (buffer == VK_NULL_HANDLE) {
        if (enabled_features.maintenance6) {
            cb_state->index_buffer_binding.bound = true;
        }
    } else {
        auto buffer_state = Get<Buffer>(buffer);
        // Being able to set the size was added in VK_KHR_maintenance5 via vkCmdBindIndexBuffer2KHR
        // Using this function is the same as passing in VK_WHOLE_SIZE
        VkDeviceSize buffer_size = GetBufferRegionSize(buffer_state, offset, VK_WHOLE_SIZE);
        cb_state->index_buffer_binding = IndexBufferBinding(buffer, buffer_size, offset, indexType);

        // Add binding for this index buffer to this commandbuffer
        if (!disabled[command_buffer_state]) {
            cb_state->AddChild(buffer_state);
        }
    }
}

void DeviceState::PostCallRecordCmdBindIndexBuffer2(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                    VkDeviceSize size, VkIndexType indexType, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBindIndexbuffer(record_obj.location);

    if (buffer == VK_NULL_HANDLE) {
        if (enabled_features.maintenance6) {
            cb_state->index_buffer_binding.bound = true;
        }
    } else {
        auto buffer_state = Get<Buffer>(buffer);
        VkDeviceSize buffer_size = GetBufferRegionSize(buffer_state, offset, size);
        cb_state->index_buffer_binding = IndexBufferBinding(buffer, buffer_size, offset, indexType);

        // Add binding for this index buffer to this commandbuffer
        if (!disabled[command_buffer_state]) {
            cb_state->AddChild(buffer_state);
        }
    }
}

void DeviceState::PostCallRecordCmdBindIndexBuffer2KHR(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                       VkDeviceSize size, VkIndexType indexType, const RecordObject& record_obj) {
    PostCallRecordCmdBindIndexBuffer2(commandBuffer, buffer, offset, size, indexType, record_obj);
}

void DeviceState::PostCallRecordCmdBindIndexBuffer3KHR(VkCommandBuffer commandBuffer, const VkBindIndexBuffer3InfoKHR* pInfo,
                                                       const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBindIndexbuffer(record_obj.location);
    cb_state->index_buffer_binding = IndexBufferBinding(pInfo->addressRange.address, pInfo->addressRange.size, pInfo->indexType);
    TrackDeviceAddressRange(*cb_state, pInfo->addressRange.address, pInfo->addressRange.size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdBindVertexBuffers(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount,
                                                     const VkBuffer* pBuffers, const VkDeviceSize* pOffsets,
                                                     const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    cb_state->bind_vertex_buffer_3_used = false;

    for (uint32_t i = 0; i < bindingCount; ++i) {
        auto buffer_state = Get<Buffer>(pBuffers[i]);
        // the stride is set from the pipeline or dynamic state
        const VkDeviceSize effective_size = GetBufferRegionSize(buffer_state, pOffsets[i], VK_WHOLE_SIZE);
        cb_state->current_vertex_buffer_binding_info[i + firstBinding].Set(pBuffers[i], effective_size, pOffsets[i], nullptr);

        // Add binding for this vertex buffer to this commandbuffer
        if (pBuffers[i] && !disabled[command_buffer_state]) {
            cb_state->AddChild(buffer_state);
        }
    }
}

void DeviceState::PostCallRecordCmdUpdateBuffer(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset,
                                                VkDeviceSize dataSize, const void* pData, const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto buffer_state = Get<Buffer>(dstBuffer);
    ASSERT_AND_RETURN(buffer_state);
    cb_state->AddChild(buffer_state);
    cb_state->RecordUpdateBuffer(*buffer_state, dstOffset, dataSize, record_obj.location);
}

void DeviceState::PostCallRecordCmdUpdateMemoryKHR(VkCommandBuffer commandBuffer, const VkDeviceAddressRangeKHR* pDstRange,
                                                   VkAddressCommandFlagsKHR dstFlags, VkDeviceSize dataSize, const void* pData,
                                                   const RecordObject& record_obj) {
    if (disabled[command_buffer_state]) {
        return;
    }

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordUpdateMemory(*pDstRange, record_obj.location);

    TrackDeviceAddressRange(*cb_state, pDstRange->address, pDstRange->size, VK_BUFFER_USAGE_2_TRANSFER_DST_BIT);
}

void DeviceState::PostCallRecordCmdSetEvent(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask,
                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetEvent(event, stageMask, record_obj.location);
}

void DeviceState::PostCallRecordCmdSetEvent2KHR(VkCommandBuffer commandBuffer, VkEvent event,
                                                const VkDependencyInfoKHR* pDependencyInfo, const RecordObject& record_obj) {
    PostCallRecordCmdSetEvent2(commandBuffer, event, pDependencyInfo, record_obj);
}

void DeviceState::PostCallRecordCmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo,
                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetEvent2(event, *pDependencyInfo, record_obj.location);
    cb_state->RecordBarrierObjects(*pDependencyInfo, record_obj.location.dot(vvl::Field::pDependencyInfo));
}

void DeviceState::PostCallRecordCmdResetEvent(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask,
                                              const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordResetEvent(event, stageMask, record_obj.location);
}

void DeviceState::PostCallRecordCmdResetEvent2KHR(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2KHR stageMask,
                                                  const RecordObject& record_obj) {
    PostCallRecordCmdResetEvent2(commandBuffer, event, stageMask, record_obj);
}

void DeviceState::PostCallRecordCmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask,
                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordResetEvent(event, stageMask, record_obj.location);
}

void DeviceState::PostCallRecordCmdWaitEvents(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents,
                                              VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
                                              uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers,
                                              uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers,
                                              uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers,
                                              const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    cb_state->RecordWaitEvents(vvl::make_span(pEvents, eventCount), srcStageMask, dstStageMask, record_obj.location);
    cb_state->RecordBarrierObjects(bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers,
                                   srcStageMask, dstStageMask, record_obj.location);
}

void DeviceState::PostCallRecordCmdWaitEvents2KHR(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents,
                                                  const VkDependencyInfoKHR* pDependencyInfos, const RecordObject& record_obj) {
    PostCallRecordCmdWaitEvents2(commandBuffer, eventCount, pEvents, pDependencyInfos, record_obj);
}

void DeviceState::PostCallRecordCmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents,
                                               const VkDependencyInfo* pDependencyInfos, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    for (uint32_t i = 0; i < eventCount; i++) {
        const auto& dep_info = pDependencyInfos[i];
        const Location& dep_info_loc = record_obj.location.dot(vvl::Field::pDependencyInfos, i);
        cb_state->RecordWaitEvent2(pEvents[i], pDependencyInfos[i], dep_info_loc);
        cb_state->RecordBarrierObjects(dep_info, dep_info_loc);
    }
}

void DeviceState::PostCallRecordCmdPipelineBarrier(
    VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
    VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers,
    uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
    const VkImageMemoryBarrier* pImageMemoryBarriers, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    cb_state->RecordBarrierObjects(bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers,
                                   srcStageMask, dstStageMask, record_obj.location);
}

void DeviceState::PostCallRecordCmdPipelineBarrier2KHR(VkCommandBuffer commandBuffer, const VkDependencyInfoKHR* pDependencyInfo,
                                                       const RecordObject& record_obj) {
    PostCallRecordCmdPipelineBarrier2(commandBuffer, pDependencyInfo, record_obj);
}

void DeviceState::PostCallRecordCmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo,
                                                    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    cb_state->RecordBarrierObjects(*pDependencyInfo, record_obj.location.dot(vvl::Field::pDependencyInfo));
}

void DeviceState::PostCallRecordCmdBeginQuery(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t slot,
                                              VkQueryControlFlags flags, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    if (disabled[query_validation]) {
        return;
    }

    QueryCount query_count(*cb_state);

    for (uint32_t i = 0; i < query_count.count; ++i) {
        QueryObject query_obj = {queryPool, slot + i, flags};
        query_obj.inside_render_pass = query_count.inside_render_pass;
        query_obj.subpass = query_count.subpass;
        cb_state->RecordBeginQuery(query_obj, record_obj.location);
    }

    if (!disabled[command_buffer_state]) {
        auto pool_state = Get<QueryPool>(queryPool);
        cb_state->AddChild(pool_state);
    }
}

void DeviceState::PostCallRecordCmdEndQuery(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t slot,
                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    if (disabled[query_validation]) {
        return;
    }

    QueryCount query_count(*cb_state);

    for (uint32_t i = 0; i < query_count.count; ++i) {
        QueryObject query_obj = {queryPool, slot + i};
        query_obj.inside_render_pass = query_count.inside_render_pass;
        query_obj.subpass = query_count.subpass;
        query_obj.end_command_index = cb_state->command_count;  // counting this command
        cb_state->RecordEndQuery(query_obj, record_obj.location);
    }

    if (!disabled[command_buffer_state]) {
        auto pool_state = Get<QueryPool>(queryPool);
        cb_state->AddChild(pool_state);
    }
}

void DeviceState::PostCallRecordCmdResetQueryPool(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery,
                                                  uint32_t queryCount, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordResetQueryPool(queryPool, firstQuery, queryCount, record_obj.location);
}

void DeviceState::PostCallRecordCmdCopyQueryPoolResults(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery,
                                                        uint32_t queryCount, VkBuffer dstBuffer, VkDeviceSize dstOffset,
                                                        VkDeviceSize stride, VkQueryResultFlags flags,
                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCopyQueryPoolResults(queryPool, dstBuffer, firstQuery, queryCount, dstOffset, stride, flags,
                                         record_obj.location);
}

void DeviceState::PostCallRecordCmdCopyQueryPoolResultsToMemoryKHR(VkCommandBuffer commandBuffer, VkQueryPool queryPool,
                                                                   uint32_t firstQuery, uint32_t queryCount,
                                                                   const VkStridedDeviceAddressRangeKHR* pDstRange,
                                                                   VkAddressCommandFlagsKHR dstFlags,
                                                                   VkQueryResultFlags queryResultFlags,
                                                                   const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCopyQueryPoolResultsToMemory(queryPool, firstQuery, queryCount, pDstRange, dstFlags, queryResultFlags,
                                                 record_obj.location);
    TrackDeviceAddressRange(*cb_state, pDstRange->address, pDstRange->size, VK_BUFFER_USAGE_2_TRANSFER_DST_BIT);
}

void DeviceState::PostCallRecordCmdWriteTimestamp(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage,
                                                  VkQueryPool queryPool, uint32_t slot, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordWriteTimestamp(queryPool, slot, record_obj.location);
}

void DeviceState::PostCallRecordCmdWriteTimestamp2KHR(VkCommandBuffer commandBuffer, VkPipelineStageFlags2KHR pipelineStage,
                                                      VkQueryPool queryPool, uint32_t slot, const RecordObject& record_obj) {
    PostCallRecordCmdWriteTimestamp2(commandBuffer, pipelineStage, queryPool, slot, record_obj);
}

void DeviceState::PostCallRecordCmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 pipelineStage,
                                                   VkQueryPool queryPool, uint32_t slot, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordWriteTimestamp(queryPool, slot, record_obj.location);
}

void DeviceState::PostCallRecordCmdWriteAccelerationStructuresPropertiesKHR(
    VkCommandBuffer commandBuffer, uint32_t accelerationStructureCount, const VkAccelerationStructureKHR* pAccelerationStructures,
    VkQueryType queryType, VkQueryPool queryPool, uint32_t firstQuery, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordWriteAccelerationStructuresProperties(queryPool, firstQuery, accelerationStructureCount, record_obj.location);
}

void DeviceState::PostCallRecordCmdWriteMicromapsPropertiesEXT(VkCommandBuffer commandBuffer, uint32_t micromapCount,
                                                               const VkMicromapEXT* pMicromaps, VkQueryType queryType,
                                                               VkQueryPool queryPool, uint32_t firstQuery,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordWriteAccelerationStructuresProperties(queryPool, firstQuery, micromapCount, record_obj.location);
}

void DeviceState::PostCallRecordCreateVideoSessionKHR(VkDevice device, const VkVideoSessionCreateInfoKHR* pCreateInfo,
                                                      const VkAllocationCallbacks* pAllocator, VkVideoSessionKHR* pVideoSession,
                                                      const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    auto profile_desc = video_profile_cache_.Get(physical_device, pCreateInfo->pVideoProfile);
    Add(std::make_shared<VideoSession>(*this, *pVideoSession, pCreateInfo, std::move(profile_desc)));
}

void DeviceState::PostCallRecordGetVideoSessionMemoryRequirementsKHR(VkDevice device, VkVideoSessionKHR videoSession,
                                                                     uint32_t* pMemoryRequirementsCount,
                                                                     VkVideoSessionMemoryRequirementsKHR* pMemoryRequirements,
                                                                     const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    auto vs_state = Get<VideoSession>(videoSession);
    ASSERT_AND_RETURN(vs_state);

    if (pMemoryRequirements != nullptr) {
        if (*pMemoryRequirementsCount > vs_state->memory_bindings_queried) {
            vs_state->memory_bindings_queried = *pMemoryRequirementsCount;
        }
    } else {
        vs_state->memory_binding_count_queried = true;
    }
}

void DeviceState::PostCallRecordBindVideoSessionMemoryKHR(VkDevice device, VkVideoSessionKHR videoSession,
                                                          uint32_t bindSessionMemoryInfoCount,
                                                          const VkBindVideoSessionMemoryInfoKHR* pBindSessionMemoryInfos,
                                                          const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    auto vs_state = Get<VideoSession>(videoSession);
    ASSERT_AND_RETURN(vs_state);

    for (uint32_t i = 0; i < bindSessionMemoryInfoCount; ++i) {
        vs_state->BindMemoryBindingIndex(pBindSessionMemoryInfos[i].memoryBindIndex);
    }
}

void DeviceState::PreCallRecordDestroyVideoSessionKHR(VkDevice device, VkVideoSessionKHR videoSession,
                                                      const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<VideoSession>(videoSession);
}

void DeviceState::PostCallRecordCreateVideoSessionParametersKHR(VkDevice device,
                                                                const VkVideoSessionParametersCreateInfoKHR* pCreateInfo,
                                                                const VkAllocationCallbacks* pAllocator,
                                                                VkVideoSessionParametersKHR* pVideoSessionParameters,
                                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    Add(std::make_shared<VideoSessionParameters>(*pVideoSessionParameters, pCreateInfo,
                                                 Get<VideoSession>(pCreateInfo->videoSession),
                                                 Get<VideoSessionParameters>(pCreateInfo->videoSessionParametersTemplate)));
}

void DeviceState::PostCallRecordUpdateVideoSessionParametersKHR(VkDevice device, VkVideoSessionParametersKHR videoSessionParameters,
                                                                const VkVideoSessionParametersUpdateInfoKHR* pUpdateInfo,
                                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    Get<VideoSessionParameters>(videoSessionParameters)->Update(pUpdateInfo);
}

void DeviceState::PreCallRecordDestroyVideoSessionParametersKHR(VkDevice device, VkVideoSessionParametersKHR videoSessionParameters,
                                                                const VkAllocationCallbacks* pAllocator,
                                                                const RecordObject& record_obj) {
    Destroy<VideoSessionParameters>(videoSessionParameters);
}

void DeviceState::PostCallRecordCreateFramebuffer(VkDevice device, const VkFramebufferCreateInfo* pCreateInfo,
                                                  const VkAllocationCallbacks* pAllocator, VkFramebuffer* pFramebuffer,
                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    std::vector<std::shared_ptr<ImageView>> views;
    if ((pCreateInfo->flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) == 0) {
        views.resize(pCreateInfo->attachmentCount);

        for (uint32_t i = 0; i < pCreateInfo->attachmentCount; ++i) {
            views[i] = Get<ImageView>(pCreateInfo->pAttachments[i]);
        }
    }

    Add(std::make_shared<Framebuffer>(*pFramebuffer, pCreateInfo, Get<RenderPass>(pCreateInfo->renderPass), std::move(views)));
}

void DeviceState::PostCallRecordCreateRenderPass(VkDevice device, const VkRenderPassCreateInfo* pCreateInfo,
                                                 const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass,
                                                 const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<RenderPass>(*pRenderPass, pCreateInfo));
}

void DeviceState::PostCallRecordCreateRenderPass2KHR(VkDevice device, const VkRenderPassCreateInfo2* pCreateInfo,
                                                     const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass,
                                                     const RecordObject& record_obj) {
    PostCallRecordCreateRenderPass2(device, pCreateInfo, pAllocator, pRenderPass, record_obj);
}

void DeviceState::PostCallRecordCreateRenderPass2(VkDevice device, const VkRenderPassCreateInfo2* pCreateInfo,
                                                  const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass,
                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    Add(std::make_shared<RenderPass>(*pRenderPass, pCreateInfo));
}

void DeviceState::PostCallRecordCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
                                                   VkSubpassContents contents, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    VkSubpassBeginInfo subpass_begin_info = vku::InitStructHelper();
    subpass_begin_info.contents = contents;
    cb_state->RecordBeginRenderPass(*pRenderPassBegin, subpass_begin_info, record_obj.location);
}

void DeviceState::PostCallRecordCmdBeginRenderPass2KHR(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
                                                       const VkSubpassBeginInfo* pSubpassBeginInfo,
                                                       const RecordObject& record_obj) {
    PostCallRecordCmdBeginRenderPass2(commandBuffer, pRenderPassBegin, pSubpassBeginInfo, record_obj);
}

void DeviceState::PostCallRecordCmdBeginCustomResolveEXT(VkCommandBuffer commandBuffer,
                                                         const VkBeginCustomResolveInfoEXT* pBeginCustomResolveInfo,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBeginCustomResolve(record_obj.location);
}

void DeviceState::PostCallRecordCmdBeginVideoCodingKHR(VkCommandBuffer commandBuffer, const VkVideoBeginCodingInfoKHR* pBeginInfo,
                                                       const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBeginVideoCoding(*pBeginInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdBeginTransformFeedbackEXT(VkCommandBuffer commandBuffer, uint32_t firstCounterBuffer,
                                                             uint32_t counterBufferCount, const VkBuffer* pCounterBuffers,
                                                             const VkDeviceSize* pCounterBufferOffsets,
                                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    cb_state->transform_feedback_active = true;
}

void DeviceState::PostCallRecordCmdBeginTransformFeedback2EXT(VkCommandBuffer commandBuffer, uint32_t firstCounterRange,
                                                              uint32_t counterRangeCount,
                                                              const VkBindTransformFeedbackBuffer2InfoEXT* pCounterInfos,
                                                              const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    cb_state->transform_feedback_active = true;
    for (uint32_t i = 0; i < counterRangeCount; ++i) {
        TrackDeviceAddressRange(*cb_state, pCounterInfos[i].addressRange.address, pCounterInfos[i].addressRange.size,
                                VK_BUFFER_USAGE_2_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT);
    }
}

void DeviceState::PostCallRecordCmdEndTransformFeedbackEXT(VkCommandBuffer commandBuffer, uint32_t firstCounterBuffer,
                                                           uint32_t counterBufferCount, const VkBuffer* pCounterBuffers,
                                                           const VkDeviceSize* pCounterBufferOffsets,
                                                           const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    cb_state->transform_feedback_active = false;
}

void DeviceState::PostCallRecordCmdEndTransformFeedback2EXT(VkCommandBuffer commandBuffer, uint32_t firstCounterRange,
                                                            uint32_t counterRangeCount,
                                                            const VkBindTransformFeedbackBuffer2InfoEXT* pCounterInfos,
                                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    cb_state->transform_feedback_active = false;
    for (uint32_t i = 0; i < counterRangeCount; ++i) {
        TrackDeviceAddressRange(*cb_state, pCounterInfos[i].addressRange.address, pCounterInfos[i].addressRange.size,
                                VK_BUFFER_USAGE_2_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT);
    }
}

void DeviceState::PostCallRecordCmdDrawIndirectByteCount2EXT(VkCommandBuffer commandBuffer, uint32_t instanceCount,
                                                             uint32_t firstInstance,
                                                             const VkBindTransformFeedbackBuffer2InfoEXT* pCounterInfo,
                                                             uint32_t counterOffset, uint32_t vertexStride,
                                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pCounterInfo->addressRange.address, pCounterInfo->addressRange.size,
                            VK_BUFFER_USAGE_2_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT);
}

void DeviceState::PostCallRecordCmdDrawIndirectByteCountEXT(VkCommandBuffer commandBuffer, uint32_t instanceCount,
                                                            uint32_t firstInstance, VkBuffer counterBuffer,
                                                            VkDeviceSize counterBufferOffset, uint32_t counterOffset,
                                                            uint32_t vertexStride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
}

void DeviceState::PostCallRecordCmdWriteMarkerToMemoryAMD(VkCommandBuffer commandBuffer, const VkMemoryMarkerInfoAMD* pInfo,
                                                          const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pInfo->dstRange.address, pInfo->dstRange.size, VK_BUFFER_USAGE_2_TRANSFER_DST_BIT);
}

void DeviceState::PostCallRecordCmdBeginConditionalRenderingEXT(
    VkCommandBuffer commandBuffer, const VkConditionalRenderingBeginInfoEXT* pConditionalRenderingBegin,
    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBeginConditionalRendering(record_obj.location);
}

void DeviceState::PostCallRecordCmdEndConditionalRenderingEXT(VkCommandBuffer commandBuffer, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordEndConditionalRendering(record_obj.location);
}

void DeviceState::PostCallRecordCmdBeginConditionalRendering2EXT(
    VkCommandBuffer commandBuffer, const VkConditionalRenderingBeginInfo2EXT* pConditionalRenderingBegin,
    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBeginConditionalRendering(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pConditionalRenderingBegin->addressRange.address,
                            pConditionalRenderingBegin->addressRange.size, VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT);
}

void DeviceState::PostCallRecordCmdBindTileMemoryQCOM(VkCommandBuffer commandBuffer,
                                                      const VkTileMemoryBindInfoQCOM* pTileMemoryBindInfo,
                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    // The bound tile memory can be reset by the application
    cb_state->bound_tile_memory = pTileMemoryBindInfo ? Get<vvl::DeviceMemory>(pTileMemoryBindInfo->memory) : nullptr;
}

void DeviceState::PostCallRecordCmdBeginPerTileExecutionQCOM(VkCommandBuffer commandBuffer, const VkPerTileBeginInfoQCOM* pPerTileBeginInfo,
                                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->per_tile_execution_model_enabled = true;
}

void DeviceState::PostCallRecordCmdEndPerTileExecutionQCOM(VkCommandBuffer commandBuffer, const VkPerTileEndInfoQCOM* pPerTileEndInfo,
                                                           const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->per_tile_execution_model_enabled = false;
}

void DeviceState::PostCallRecordCmdBeginRenderingKHR(VkCommandBuffer commandBuffer, const VkRenderingInfoKHR* pRenderingInfo,
                                                     const RecordObject& record_obj) {
    PostCallRecordCmdBeginRendering(commandBuffer, pRenderingInfo, record_obj);
}

void DeviceState::PostCallRecordCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo,
                                                  const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBeginRendering(*pRenderingInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdEndRenderingKHR(VkCommandBuffer commandBuffer, const RecordObject& record_obj) {
    PostCallRecordCmdEndRendering(commandBuffer, record_obj);
}

void DeviceState::PostCallRecordCmdEndRendering(VkCommandBuffer commandBuffer, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordEndRendering(nullptr, record_obj.location);
}

void DeviceState::PostCallRecordCmdEndRendering2EXT(VkCommandBuffer commandBuffer, const VkRenderingEndInfoEXT* pRenderingEndInfo,
                                                    const RecordObject& record_obj) {
    PostCallRecordCmdEndRendering2KHR(commandBuffer, pRenderingEndInfo, record_obj);
}

void DeviceState::PostCallRecordCmdEndRendering2KHR(VkCommandBuffer commandBuffer, const VkRenderingEndInfoKHR* pRenderingEndInfo,
                                                    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordEndRendering(pRenderingEndInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdBeginRenderPass2(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
                                                    const VkSubpassBeginInfo* pSubpassBeginInfo, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBeginRenderPass(*pRenderPassBegin, *pSubpassBeginInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdNextSubpass(VkCommandBuffer commandBuffer, VkSubpassContents contents,
                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    VkSubpassBeginInfo subpass_begin_info = vku::InitStructHelper();
    subpass_begin_info.contents = contents;
    cb_state->RecordNextSubpass(subpass_begin_info, nullptr, record_obj.location);
}

void DeviceState::PostCallRecordCmdNextSubpass2KHR(VkCommandBuffer commandBuffer, const VkSubpassBeginInfo* pSubpassBeginInfo,
                                                   const VkSubpassEndInfo* pSubpassEndInfo, const RecordObject& record_obj) {
    PostCallRecordCmdNextSubpass2(commandBuffer, pSubpassBeginInfo, pSubpassEndInfo, record_obj);
}

void DeviceState::PostCallRecordCmdNextSubpass2(VkCommandBuffer commandBuffer, const VkSubpassBeginInfo* pSubpassBeginInfo,
                                                const VkSubpassEndInfo* pSubpassEndInfo, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordNextSubpass(*pSubpassBeginInfo, pSubpassEndInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdEndRenderPass(VkCommandBuffer commandBuffer, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordEndRenderPass(nullptr, record_obj.location);
}

void DeviceState::PostCallRecordCmdEndRenderPass2KHR(VkCommandBuffer commandBuffer, const VkSubpassEndInfo* pSubpassEndInfo,
                                                     const RecordObject& record_obj) {
    PostCallRecordCmdEndRenderPass2(commandBuffer, pSubpassEndInfo, record_obj);
}

void DeviceState::PostCallRecordCmdEndRenderPass2(VkCommandBuffer commandBuffer, const VkSubpassEndInfo* pSubpassEndInfo,
                                                  const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordEndRenderPass(pSubpassEndInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdEndVideoCodingKHR(VkCommandBuffer commandBuffer, const VkVideoEndCodingInfoKHR* pEndCodingInfo,
                                                     const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordEndVideoCoding(record_obj.location);
}

void DeviceState::PostCallRecordCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBuffersCount,
                                                   const VkCommandBuffer* pCommandBuffers, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordExecuteCommands({pCommandBuffers, commandBuffersCount}, record_obj.location);
}

void DeviceState::PostCallRecordMapMemory(VkDevice device, VkDeviceMemory mem, VkDeviceSize offset, VkDeviceSize size,
                                          VkFlags flags, void** ppData, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordMappedMemory(mem, offset, size, ppData);
}

void DeviceState::PostCallRecordMapMemory2(VkDevice device, const VkMemoryMapInfo* pMemoryMapInfo, void** ppData,
                                           const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordMappedMemory(pMemoryMapInfo->memory, pMemoryMapInfo->offset, pMemoryMapInfo->size, ppData);
}

void DeviceState::PostCallRecordMapMemory2KHR(VkDevice device, const VkMemoryMapInfoKHR* pMemoryMapInfo, void** ppData,
                                              const RecordObject& record_obj) {
    PostCallRecordMapMemory2(device, pMemoryMapInfo, ppData, record_obj);
}

void DeviceState::PreCallRecordUnmapMemory(VkDevice device, VkDeviceMemory mem, const RecordObject& record_obj) {
    if (auto mem_info = Get<DeviceMemory>(mem)) {
        mem_info->mapped_range = MemRange();
        mem_info->p_driver_data = nullptr;
    }
}

void DeviceState::PreCallRecordUnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* pMemoryUnmapInfo,
                                            const RecordObject& record_obj) {
    if (auto mem_info = Get<DeviceMemory>(pMemoryUnmapInfo->memory)) {
        mem_info->mapped_range = MemRange();
        mem_info->p_driver_data = nullptr;
    }
}

void DeviceState::PreCallRecordUnmapMemory2KHR(VkDevice device, const VkMemoryUnmapInfoKHR* pMemoryUnmapInfo,
                                               const RecordObject& record_obj) {
    PreCallRecordUnmapMemory2(device, pMemoryUnmapInfo, record_obj);
}

void DeviceState::UpdateBindImageMemoryState(const VkBindImageMemoryInfo& bind_info) {
    auto image_state = Get<Image>(bind_info.image);
    if (!image_state) {
        return;
    }

    const auto swapchain_info = vku::FindStructInPNextChain<VkBindImageMemorySwapchainInfoKHR>(bind_info.pNext);
    if (swapchain_info) {
        if (auto swapchain = Get<Swapchain>(swapchain_info->swapchain)) {
            // All images bound to this swapchain and index are aliases
            image_state->SetSwapchain(swapchain, swapchain_info->imageIndex);
        }
    } else {
        // Track bound memory range information
        if (auto mem_info = Get<DeviceMemory>(bind_info.memory)) {
            VkDeviceSize plane_index = 0u;
            if (image_state->disjoint && image_state->IsExternalBuffer() == false) {
                auto plane_info = vku::FindStructInPNextChain<VkBindImagePlaneMemoryInfo>(bind_info.pNext);
                plane_index = vkuGetPlaneIndex(plane_info->planeAspect);
            }
            image_state->BindMemory(
                image_state.get(), mem_info, bind_info.memoryOffset, plane_index,
                image_state->requirements[static_cast<decltype(image_state->requirements)::size_type>(plane_index)].size);
        }
    }
}

void DeviceState::PostCallRecordBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset,
                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    VkBindImageMemoryInfo bind_info = vku::InitStructHelper();
    bind_info.image = image;
    bind_info.memory = memory;
    bind_info.memoryOffset = memoryOffset;
    UpdateBindImageMemoryState(bind_info);
}

void DeviceState::PostCallRecordBindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos,
                                                 const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        // if bindInfoCount is 1, we know for sure if that single image was bound or not
        if (bindInfoCount > 1) {
            for (uint32_t i = 0; i < bindInfoCount; i++) {
                // If user passed in VkBindMemoryStatus, we can update which images are valid or not
                if (auto* bind_memory_status = vku::FindStructInPNextChain<VkBindMemoryStatus>(pBindInfos[i].pNext)) {
                    if (bind_memory_status->pResult && *bind_memory_status->pResult == VK_SUCCESS) {
                        UpdateBindImageMemoryState(pBindInfos[i]);
                    }
                } else if (auto image_state = Get<Image>(pBindInfos[i].image)) {
                    image_state->indeterminate_state = true;
                }
            }
        }
    } else {
        for (uint32_t i = 0; i < bindInfoCount; i++) {
            UpdateBindImageMemoryState(pBindInfos[i]);
        }
    }
}

void DeviceState::PostCallRecordBindImageMemory2KHR(VkDevice device, uint32_t bindInfoCount,
                                                    const VkBindImageMemoryInfo* pBindInfos, const RecordObject& record_obj) {
    PostCallRecordBindImageMemory2(device, bindInfoCount, pBindInfos, record_obj);
}

void DeviceState::PostCallRecordResetEvent(VkDevice device, VkEvent event, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto event_state = Get<Event>(event);
    const bool can_unsignal = event_state && event_state->signaled;
    if (can_unsignal) {
        event_state->signaled = false;
        event_state->signal_dependency_info.reset();
        event_state->signal_src_stage_mask = VK_PIPELINE_STAGE_NONE;
        event_state->signaling_queue = VK_NULL_HANDLE;
        event_state->last_signaling_command = vvl::Func::vkResetEvent;
    }
}

void DeviceState::PostCallRecordSetEvent(VkDevice device, VkEvent event, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto event_state = Get<Event>(event);
    const bool can_signal = event_state && !event_state->signaled;
    if (can_signal) {
        event_state->signaled = true;
        event_state->signal_dependency_info.reset();
        event_state->signal_src_stage_mask = VK_PIPELINE_STAGE_HOST_BIT;
        event_state->signaling_queue = VK_NULL_HANDLE;
        event_state->last_signaling_command = vvl::Func::vkSetEvent;
    }
}

void DeviceState::PostCallRecordImportSemaphoreFdKHR(VkDevice device, const VkImportSemaphoreFdInfoKHR* pImportSemaphoreFdInfo,
                                                     const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordImportSemaphoreState(pImportSemaphoreFdInfo->semaphore, pImportSemaphoreFdInfo->handleType,
                               pImportSemaphoreFdInfo->flags);
}

void DeviceState::RecordGetExternalSemaphoreState(Semaphore& semaphore_state, VkExternalSemaphoreHandleTypeFlagBits handle_type) {
    semaphore_state.Export(handle_type);
}

#ifdef VK_USE_PLATFORM_WIN32_KHR
void InstanceState::PostCallRecordCreateWin32SurfaceKHR(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo,
                                                        const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                        const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::Win32);
}

void DeviceState::PostCallRecordAcquireFullScreenExclusiveModeEXT(VkDevice device, VkSwapchainKHR swapchain,
                                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto swapchain_state = Get<Swapchain>(swapchain);
    ASSERT_AND_RETURN(swapchain_state);
    swapchain_state->exclusive_full_screen_access = true;
}

void DeviceState::PostCallRecordReleaseFullScreenExclusiveModeEXT(VkDevice device, VkSwapchainKHR swapchain,
                                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto swapchain_state = Get<Swapchain>(swapchain);
    ASSERT_AND_RETURN(swapchain_state);
    swapchain_state->exclusive_full_screen_access = false;
}

void DeviceState::PostCallRecordImportSemaphoreWin32HandleKHR(
    VkDevice device, const VkImportSemaphoreWin32HandleInfoKHR* pImportSemaphoreWin32HandleInfo, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordImportSemaphoreState(pImportSemaphoreWin32HandleInfo->semaphore, pImportSemaphoreWin32HandleInfo->handleType,
                               pImportSemaphoreWin32HandleInfo->flags);
}

void DeviceState::PostCallRecordGetSemaphoreWin32HandleKHR(VkDevice device,
                                                           const VkSemaphoreGetWin32HandleInfoKHR* pGetWin32HandleInfo,
                                                           HANDLE* pHandle, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (auto semaphore_state = Get<Semaphore>(pGetWin32HandleInfo->semaphore)) {
        RecordGetExternalSemaphoreState(*semaphore_state, pGetWin32HandleInfo->handleType);
    }
}

void DeviceState::PostCallRecordImportFenceWin32HandleKHR(VkDevice device,
                                                          const VkImportFenceWin32HandleInfoKHR* pImportFenceWin32HandleInfo,
                                                          const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordImportFenceState(pImportFenceWin32HandleInfo->fence, pImportFenceWin32HandleInfo->handleType,
                           pImportFenceWin32HandleInfo->flags);
}

void DeviceState::PostCallRecordGetFenceWin32HandleKHR(VkDevice device, const VkFenceGetWin32HandleInfoKHR* pGetWin32HandleInfo,
                                                       HANDLE* pHandle, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordGetExternalFenceState(pGetWin32HandleInfo->fence, pGetWin32HandleInfo->handleType, record_obj.location);
}

void DeviceState::PostCallRecordGetMemoryWin32HandleKHR(VkDevice device, const VkMemoryGetWin32HandleInfoKHR* pGetWin32HandleInfo,
                                                        HANDLE* pHandle, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (const auto memory_state = Get<DeviceMemory>(pGetWin32HandleInfo->memory)) {
        // For validation purposes we need to keep allocation size and memory type index.
        // There is no need to keep pNext chain.
        ExternalOpaqueInfo external_info = {};
        external_info.allocation_size = memory_state->allocate_info.allocationSize;
        external_info.memory_type_index = memory_state->allocate_info.memoryTypeIndex;
        external_info.dedicated_buffer = memory_state->GetDedicatedBuffer();
        external_info.dedicated_image = memory_state->GetDedicatedImage();
        external_info.device_memory = pGetWin32HandleInfo->memory;

        WriteLockGuard guard(win32_handle_map_lock_);
        // `insert_or_assign` ensures that information is updated when the system decides to re-use
        // closed handle value for a new handle. The validation layer does not track handle close operation
        // which is performed by 'CloseHandle' system call.
        win32_handle_map_.insert_or_assign(*pHandle, external_info);
    }
}
#endif  // VK_USE_PLATFORM_WIN32_KHR

#ifdef VK_USE_PLATFORM_FUCHSIA
void DeviceState::PostCallRecordImportSemaphoreZirconHandleFUCHSIA(
    VkDevice device, const VkImportSemaphoreZirconHandleInfoFUCHSIA* pImportSemaphoreZirconHandleInfo,
    const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordImportSemaphoreState(pImportSemaphoreZirconHandleInfo->semaphore, pImportSemaphoreZirconHandleInfo->handleType,
                               pImportSemaphoreZirconHandleInfo->flags);
}

void DeviceState::PostCallRecordGetSemaphoreZirconHandleFUCHSIA(VkDevice device,
                                                                const VkSemaphoreGetZirconHandleInfoFUCHSIA* pGetZirconHandleInfo,
                                                                zx_handle_t* pZirconHandle, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (auto semaphore_state = Get<vvl::Semaphore>(pGetZirconHandleInfo->semaphore)) {
        RecordGetExternalSemaphoreState(*semaphore_state, pGetZirconHandleInfo->handleType);
    }
}
#endif  // VK_USE_PLATFORM_FUCHSIA

void DeviceState::PostCallRecordGetSemaphoreFdKHR(VkDevice device, const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd,
                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (auto semaphore_state = Get<Semaphore>(pGetFdInfo->semaphore)) {
        // Record before locking with the WriteLockGuard
        RecordGetExternalSemaphoreState(*semaphore_state, pGetFdInfo->handleType);
    }
}

void DeviceState::RecordImportFenceState(VkFence fence, VkExternalFenceHandleTypeFlagBits handle_type, VkFenceImportFlags flags) {
    if (auto fence_node = Get<Fence>(fence)) {
        fence_node->Import(handle_type, flags);
    }
}

void DeviceState::PostCallRecordImportFenceFdKHR(VkDevice device, const VkImportFenceFdInfoKHR* pImportFenceFdInfo,
                                                 const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordImportFenceState(pImportFenceFdInfo->fence, pImportFenceFdInfo->handleType, pImportFenceFdInfo->flags);
}

void DeviceState::RecordGetExternalFenceState(VkFence fence, VkExternalFenceHandleTypeFlagBits handle_type, const Location& loc) {
    if (auto fence_state = Get<Fence>(fence)) {
        // We no longer can track inflight fence after the export - perform early retire.
        fence_state->NotifyAndWait(loc);
        fence_state->Export(handle_type);
    }
}

void DeviceState::PostCallRecordGetFenceFdKHR(VkDevice device, const VkFenceGetFdInfoKHR* pGetFdInfo, int* pFd,
                                              const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordGetExternalFenceState(pGetFdInfo->fence, pGetFdInfo->handleType, record_obj.location);
}

void DeviceState::PostCallRecordCreateEvent(VkDevice device, const VkEventCreateInfo* pCreateInfo,
                                            const VkAllocationCallbacks* pAllocator, VkEvent* pEvent,
                                            const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<Event>(*pEvent, pCreateInfo));
}

void DeviceState::RecordCreateSwapchainState(VkResult result, const VkSwapchainCreateInfoKHR* pCreateInfo,
                                             VkSwapchainKHR* pSwapchain, std::shared_ptr<Surface>&& surface_state,
                                             Swapchain* old_swapchain_state) {
    if (result == VK_SUCCESS) {
        if (surface_state->swapchain) {
            surface_state->RemoveParent(surface_state->swapchain);
        }
        auto swapchain = CreateSwapchainState(pCreateInfo, *pSwapchain);
        surface_state->AddParent(swapchain.get());
        surface_state->swapchain = swapchain.get();
        swapchain->surface = std::move(surface_state);
        auto swapchain_present_modes_ci = vku::FindStructInPNextChain<VkSwapchainPresentModesCreateInfoKHR>(pCreateInfo->pNext);
        if (swapchain_present_modes_ci) {
            const uint32_t present_mode_count = swapchain_present_modes_ci->presentModeCount;
            swapchain->present_modes.reserve(present_mode_count);
            std::copy(swapchain_present_modes_ci->pPresentModes, swapchain_present_modes_ci->pPresentModes + present_mode_count,
                      std::back_inserter(swapchain->present_modes));
        }

        // Initialize swapchain image state
        {
            uint32_t swapchain_image_count = 0;
            DispatchGetSwapchainImagesKHR(device, *pSwapchain, &swapchain_image_count, nullptr);
            std::vector<VkImage> swapchain_images(swapchain_image_count);
            VkResult res = DispatchGetSwapchainImagesKHR(device, *pSwapchain, &swapchain_image_count, swapchain_images.data());
            if (res != VK_SUCCESS) {
                // There is a chance the application is out of memory, it will also fail for the user, but we want to avoid crashing
                // here
                return;
            }
            swapchain->images.resize(swapchain_image_count);
            const auto& image_ci = swapchain->image_create_info;
            for (uint32_t i = 0; i < swapchain_image_count; ++i) {
                auto format_features =
                    instance_state->GetImageFormatFeatures(physical_device, special_supported.vk_khr_format_feature_flags2,
                                                           IsExtEnabled(extensions.vk_ext_image_drm_format_modifier), device,
                                                           swapchain_images[i], image_ci.format, image_ci.tiling);
                auto image_state = CreateImageState(swapchain_images[i], image_ci.ptr(), swapchain->VkHandle(), i, format_features);

                // Detect image resuse from the old swapchain
                //
                // NOTE: The device image map uses VkImage as a key, so only one state object
                // can exist per VkImage handle. In case of handle reuse:
                //  * The image state for the new swapchain replaces the old state in the device map
                //  * The old swapchain's image is updated to point to the new state object
                if (old_swapchain_state) {
                    for (auto& old_swapchain_image : old_swapchain_state->images) {
                        const vvl::Image* old_image_state = old_swapchain_image.image_state;
                        if (old_image_state && old_image_state->VkHandle() == image_state->VkHandle()) {
                            old_swapchain_image.image_state = image_state.get();
                            break;
                        }
                    }
                }

                // Create a copy since image state is needed after move. SetSwapchain modifies image substates.
                auto image_state_ptr_copy = image_state;
                Add(std::move(image_state_ptr_copy));

                image_state->SetSwapchain(swapchain, i);
                image_state->SetInitialLayoutMap();
                swapchain->images[i].image_state = image_state.get();
            }
        }
        if (old_swapchain_state) {
            old_swapchain_state->new_swapchain = swapchain;
            swapchain->old_swapchain = old_swapchain_state->shared_from_this();

            // Add all currently waiting present semaphores on images
            // of the old swapchain to the list that will be cleared when
            // a present completes on the new swapchain.
            // see SwapchainImage::ResetPresentWaitSemaphores()
            for (auto& image : old_swapchain_state->images) {
                swapchain->old_swapchain_present_wait_semaphores.insert(swapchain->old_swapchain_present_wait_semaphores.end(),
                                                                        image.present_wait_semaphores.begin(),
                                                                        image.present_wait_semaphores.end());
            }
        }
        Add(std::move(swapchain));
    } else {
        surface_state->swapchain = nullptr;
    }
    // Spec requires that even if CreateSwapchainKHR fails, oldSwapchain is retired
    // Retired swapchains remain associated with the surface until they are destroyed.
    if (old_swapchain_state) {
        old_swapchain_state->retired = true;
    }
}

void DeviceState::PostCallRecordCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo,
                                                   const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain,
                                                   const RecordObject& record_obj) {
    // Handle result in RecordCreateSwapchainState
    auto surface_state = instance_state->Get<Surface>(pCreateInfo->surface);
    auto old_swapchain_state = Get<Swapchain>(pCreateInfo->oldSwapchain);
    RecordCreateSwapchainState(record_obj.result, pCreateInfo, pSwapchain, std::move(surface_state), old_swapchain_state.get());
}

void DeviceState::PreCallRecordDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain,
                                                   const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<Swapchain>(swapchain);
}

void InstanceState::PostCallRecordCreateDisplayModeKHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display,
                                                       const VkDisplayModeCreateInfoKHR* pCreateInfo,
                                                       const VkAllocationCallbacks* pAllocator, VkDisplayModeKHR* pMode,
                                                       const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        std::ostringstream ss;
        ss << "failed with " << string_VkResult(record_obj.result)
           << ", likely because the pCreateInfo->parameters (visibleRegion: "
           << string_VkExtent2D(pCreateInfo->parameters.visibleRegion) << ", refreshRate: " << pCreateInfo->parameters.refreshRate
           << ") are not compatible.\nHere are the results from vkGetDisplayModePropertiesKHR:\n";
        uint32_t mode_count = 0;
        DispatchGetDisplayModePropertiesKHR(physicalDevice, display, &mode_count, nullptr);
        if (mode_count == 0) {
            ss << "No VkDisplayModePropertiesKHR found for " << FormatHandle(display);
        } else {
            std::vector<VkDisplayModePropertiesKHR> mode_props(mode_count);
            DispatchGetDisplayModePropertiesKHR(physicalDevice, display, &mode_count, mode_props.data());
            for (uint32_t i = 0; i < mode_count; i++) {
                const VkDisplayModePropertiesKHR& mode_prop = mode_props[i];
                ss << " - [" << i << "] " << FormatHandle(mode_prop.displayMode) << '\n';
                ss << "    - visibleRegion: " << string_VkExtent2D(mode_prop.parameters.visibleRegion) << '\n';
                ss << "    - refreshRate: " << mode_prop.parameters.refreshRate << " ( "
                   << (float)mode_prop.parameters.refreshRate / 1000.0f << " Hz)\n";
            }
        }
        LogWarning("WARNING-CreateDisplayModeKHR-Failed", display, record_obj.location, "%s", ss.str().c_str());
        return;
    }
    if (!pMode) {
        return;
    }
    Add(std::make_shared<DisplayMode>(*pMode, physicalDevice, display));
}

void DeviceState::PostCallRecordQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* pPresentInfo,
                                                const RecordObject& record_obj) {
    // spec: If vkQueuePresentKHR fails to enqueue the corresponding set of queue operations, it may return
    // VK_ERROR_OUT_OF_HOST_MEMORY or VK_ERROR_OUT_OF_DEVICE_MEMORY. If it does, the implementation must ensure that the state and
    // contents of any resources or synchronization primitives referenced is unaffected by the call or its failure.
    //
    // If vkQueuePresentKHR fails in such a way that the implementation is unable to make that guarantee, the implementation must
    // return VK_ERROR_DEVICE_LOST.
    //
    // However, if the presentation request is rejected by the presentation engine with an error VK_ERROR_OUT_OF_DATE_KHR,
    // VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT, or VK_ERROR_SURFACE_LOST_KHR, the set of queue operations are still considered
    // to be enqueued and thus any semaphore wait operation specified in VkPresentInfoKHR will execute when the corresponding queue
    // operation is complete.
    if (record_obj.result == VK_ERROR_OUT_OF_HOST_MEMORY || record_obj.result == VK_ERROR_OUT_OF_DEVICE_MEMORY ||
        record_obj.result == VK_ERROR_DEVICE_LOST) {
        return;
    }

    const Location present_loc = record_obj.location.dot(Field::pPresentInfo);
    const auto* present_fence_info = vku::FindStructInPNextChain<VkSwapchainPresentFenceInfoKHR>(pPresentInfo->pNext);

    std::vector<QueueSubmission> present_submissions;  // TODO: use small_vector. Update interfaces to use span
    for (uint32_t i = 0; i < pPresentInfo->swapchainCount; ++i) {
        QueueSubmission& present_submission = present_submissions.emplace_back(present_loc.dot(Field::pSwapchains, i));
        if (present_fence_info) {
            present_submission.AddFence(Get<Fence>(present_fence_info->pFences[i]));
        }
        present_submission.swapchain = Get<Swapchain>(pPresentInfo->pSwapchains[i]);
    }

    vvl::Semaphore::SwapchainWaitInfo semaphore_swapchain_info;
    // Swapchain semaphore tracking reports additional details for the common case of a single swapchain.
    // For multi-swapchain presentation, the semaphore error will also be reported but without some details.
    if (pPresentInfo->swapchainCount == 1) {
        semaphore_swapchain_info.swapchain = Get<Swapchain>(pPresentInfo->pSwapchains[0]);
        semaphore_swapchain_info.image_index = pPresentInfo->pImageIndices[0];

        // Store the value of the acquire counter that corresponds to the presented image.
        // When we get an error we can find where in the acquire history this semaphore was used the last time.
        semaphore_swapchain_info.acquire_counter_value = semaphore_swapchain_info.swapchain->acquire_request_count;

        // Usually acquire_counter_value it's the current vvl::Swapchain::acquire_count but for the case when
        // application acquires multiple images before presenting we iterate to find specific image index.
        if (uint32_t history_length = semaphore_swapchain_info.swapchain->GetAcquireHistoryLength(); history_length > 0) {
            for (int32_t history_index = int32_t(history_length - 1); history_index >= 0; history_index--) {
                uint32_t image_index =
                    semaphore_swapchain_info.swapchain->GetAcquiredImageIndexFromHistory(uint32_t(history_index));
                if (image_index == pPresentInfo->pImageIndices[0]) {
                    break;
                }
                semaphore_swapchain_info.acquire_counter_value--;
            }
        }
    }

    small_vector<std::shared_ptr<vvl::Semaphore>, 1> present_wait_semaphores;
    for (uint32_t i = 0; i < pPresentInfo->waitSemaphoreCount; ++i) {
        if (auto semaphore_state = Get<Semaphore>(pPresentInfo->pWaitSemaphores[i])) {
            semaphore_state->SetSwapchainWaitInfo(semaphore_swapchain_info);
            present_wait_semaphores.emplace_back(semaphore_state);

            // Register present wait semaphores only in the first present batch.
            // NOTE: when presenting images from multiple swapchains, if some swapchains use
            // present fences, waiting on any present fence will retire all previous present batches.
            // As a result, the present wait semaphores from the first batch will always be retired.
            if (!present_submissions.empty()) {
                present_submissions[0].AddWaitSemaphore(std::move(semaphore_state), 0);
            }
        }
    }

    // Provide present fences with information about present wait semaphores.
    // If we wait on the present fence, then it can update present semaphores
    // that they are no longer in use by the swapchain.
    bool has_external_fence = false;
    for (QueueSubmission& present_submission : present_submissions) {
        if (present_submission.fence) {
            present_submission.fence->SetPresentWaitSemaphores(present_wait_semaphores);
            if (present_submission.fence->Scope() != Fence::kInternal) {
                has_external_fence = true;
            }
        }
    }

    auto queue_state = Get<Queue>(queue);
    queue_state->is_used_for_presentation = true;
    const uint64_t last_present_seq = queue_state->PreSubmit(std::move(present_submissions));
    const SubmissionReference present_submission_ref(queue_state.get(), last_present_seq);

    if (!queue_state->is_used_for_regular_submits) {
        queue_state->UpdatePresentOnlyQueueProgress(*this);
    }

    const auto* present_id_info = vku::FindStructInPNextChain<VkPresentIdKHR>(pPresentInfo->pNext);
    const auto* present_id_info_2 = vku::FindStructInPNextChain<VkPresentId2KHR>(pPresentInfo->pNext);
    const auto* present_timings_info = vku::FindStructInPNextChain<VkPresentTimingsInfoEXT>(pPresentInfo->pNext);
    for (uint32_t i = 0; i < pPresentInfo->swapchainCount; ++i) {
        auto swapchain_data = Get<Swapchain>(pPresentInfo->pSwapchains[i]);
        if (!swapchain_data) {
            continue;
        }

        // For multi-swapchain present pResults are always available (chassis adds pResults if necessary)
        assert(pPresentInfo->swapchainCount < 2 || pPresentInfo->pResults);
        auto local_result = pPresentInfo->pResults ? pPresentInfo->pResults[i] : record_obj.result;

        // If the present timing queue reports as full, any waits would have been enqueued and therefore need to
        // be tracked here, however the presentation will be dropped
        if (local_result == VK_ERROR_PRESENT_TIMING_QUEUE_FULL_EXT) {
            queue_state->Notify(last_present_seq);
            for (const auto& semaphore : present_wait_semaphores) {
                semaphore->ClearSwapchainWaitInfo();
            }
            continue;
        }

        // Mark the image as having been released to the WSI
        uint64_t present_id = 0;
        // TODO - need to know what happens if both are included
        // https://gitlab.khronos.org/vulkan/vulkan/-/issues/4317
        if (present_id_info_2 && i < present_id_info_2->swapchainCount && present_id_info_2->pPresentIds) {
            present_id = present_id_info_2->pPresentIds[i];
        } else if (present_id_info && i < present_id_info->swapchainCount && present_id_info->pPresentIds) {
            present_id = present_id_info->pPresentIds[i];
        }

        if (present_timings_info && present_timings_info->pTimingInfos &&
            present_timings_info->pTimingInfos[i].presentStageQueries) {
            const Swapchain::PresentTimingInfo presentTimingInfo{
                CountSetBits(present_timings_info->pTimingInfos[i].presentStageQueries), present_submission_ref};
            swapchain_data->present_timing_stage_queries.push_back({present_id, presentTimingInfo});
        }

        // spec: "However, if the presentation request is rejected by the presentation engine with an error
        // VK_ERROR_OUT_OF_DATE_KHR, VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT, or VK_ERROR_SURFACE_LOST_KHR, the set of queue
        // operations are still considered to be enqueued and thus any semaphore wait operation specified in VkPresentInfoKHR will
        // execute when the corresponding queue operation is complete."
        if (!IsValueIn(local_result, {VK_SUCCESS, VK_SUBOPTIMAL_KHR, VK_ERROR_OUT_OF_DATE_KHR,
                                      VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT, VK_ERROR_SURFACE_LOST_KHR})) {
            continue;
        }

        swapchain_data->PresentImage(pPresentInfo->pImageIndices[i], present_id, present_submission_ref, present_wait_semaphores);
    }

    // Wait on the external fence because we may not be able to track when it's signaled
    if (has_external_fence) {
        queue_state->NotifyAndWait(record_obj.location, last_present_seq);
    }
}

void DeviceState::PostCallRecordReleaseSwapchainImagesKHR(VkDevice device, const VkReleaseSwapchainImagesInfoKHR* pReleaseInfo,
                                                          const RecordObject& record_obj) {
    if (auto swapchain_data = Get<Swapchain>(pReleaseInfo->swapchain)) {
        for (uint32_t i = 0; i < pReleaseInfo->imageIndexCount; ++i) {
            swapchain_data->ReleaseImage(pReleaseInfo->pImageIndices[i]);
        }
    }
}

void DeviceState::PostCallRecordReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo,
                                                          const RecordObject& record_obj) {
    PostCallRecordReleaseSwapchainImagesKHR(device, pReleaseInfo, record_obj);
}

void DeviceState::PostCallRecordSetSwapchainPresentTimingQueueSizeEXT(VkDevice device, VkSwapchainKHR swapchain, uint32_t size,
                                                                      const RecordObject& record_obj) {
    auto swapchain_state = Get<Swapchain>(swapchain);
    swapchain_state->present_timing_queue_size = size;
}

void DeviceState::PostCallRecordGetSwapchainTimeDomainPropertiesEXT(
    VkDevice device, VkSwapchainKHR swapchain, VkSwapchainTimeDomainPropertiesEXT* pSwapchainTimeDomainProperties,
    uint64_t* pTimeDomainsCounter, const RecordObject& record_obj) {
    auto swapchain_state = Get<Swapchain>(swapchain);
    if (pSwapchainTimeDomainProperties->pTimeDomainIds) {
        for (uint32_t i = 0; i < pSwapchainTimeDomainProperties->timeDomainCount; ++i) {
            swapchain_state->time_domains[pSwapchainTimeDomainProperties->pTimeDomainIds[i]] =
                pSwapchainTimeDomainProperties->pTimeDomains[i];
        }
    }
}

void DeviceState::PostCallRecordGetPastPresentationTimingEXT(
    VkDevice device, const VkPastPresentationTimingInfoEXT* pPastPresentationTimingInfo,
    VkPastPresentationTimingPropertiesEXT* pPastPresentationTimingProperties, const RecordObject& record_obj) {
    if (pPastPresentationTimingProperties->pPresentationTimings != nullptr) {
        auto swapchain = Get<Swapchain>(pPastPresentationTimingInfo->swapchain);
        for (uint32_t i = 0; i < pPastPresentationTimingProperties->presentationTimingCount; ++i) {
            if (pPastPresentationTimingProperties->pPresentationTimings[i].reportComplete) {
                const SubmissionReference present_submission_ref =
                    swapchain->present_timing_stage_queries.front().second.present_submission_ref;
                present_submission_ref.queue->NotifyAndWait(record_obj.location, present_submission_ref.seq);
                swapchain->present_timing_stage_queries.pop_front();
            }
        }
    }
}

void DeviceState::PostCallRecordCreateSharedSwapchainsKHR(VkDevice device, uint32_t swapchainCount,
                                                          const VkSwapchainCreateInfoKHR* pCreateInfos,
                                                          const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchains,
                                                          const RecordObject& record_obj) {
    // Handle result in RecordCreateSwapchainState
    if (pCreateInfos) {
        for (uint32_t i = 0; i < swapchainCount; i++) {
            const VkSwapchainCreateInfoKHR& create_info = pCreateInfos[i];
            auto surface_state = instance_state->Get<Surface>(create_info.surface);
            auto old_swapchain_state = Get<Swapchain>(create_info.oldSwapchain);
            RecordCreateSwapchainState(record_obj.result, &create_info, &pSwapchains[i], std::move(surface_state),
                                       old_swapchain_state.get());
        }
    }
}

void DeviceState::RecordAcquireNextImageState(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore,
                                              VkFence fence, uint32_t* pImageIndex, Func command) {
    auto fence_state = Get<Fence>(fence);
    if (fence_state) {
        // Treat as inflight since it is valid to wait on this fence, even in cases where it is technically a temporary
        // import
        fence_state->EnqueueSignal(nullptr, 0);
    }

    auto semaphore_state = Get<Semaphore>(semaphore);
    if (semaphore_state) {
        // Treat as signaled since it is valid to wait on this semaphore, even in cases where it is technically a
        // temporary import
        semaphore_state->EnqueueAcquire(command);
    }

    // Mark the image as acquired.
    auto swapchain_data = Get<Swapchain>(swapchain);
    if (swapchain_data) {
        swapchain_data->AcquireImage(*pImageIndex, semaphore_state, fence_state);
    }
}

void DeviceState::PostCallRecordAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout,
                                                    VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex,
                                                    const RecordObject& record_obj) {
    if ((VK_SUCCESS != record_obj.result) && (VK_SUBOPTIMAL_KHR != record_obj.result)) {
        return;
    }
    RecordAcquireNextImageState(device, swapchain, timeout, semaphore, fence, pImageIndex, record_obj.location.function);
}

void DeviceState::PostCallRecordAcquireNextImage2KHR(VkDevice device, const VkAcquireNextImageInfoKHR* pAcquireInfo,
                                                     uint32_t* pImageIndex, const RecordObject& record_obj) {
    if ((VK_SUCCESS != record_obj.result) && (VK_SUBOPTIMAL_KHR != record_obj.result)) {
        return;
    }
    RecordAcquireNextImageState(device, pAcquireInfo->swapchain, pAcquireInfo->timeout, pAcquireInfo->semaphore,
                                pAcquireInfo->fence, pImageIndex, record_obj.location.function);
}

void DeviceState::RecordWaitForPresent(VkDevice device, VkSwapchainKHR swapchain, uint64_t present_id, const Location& location) {
    if (auto swapchain_state = Get<Swapchain>(swapchain)) {
        // Find the smallest registered present id >= the given present id,
        // and ensure that the queue has progressed to the corresponding seq location
        const uint32_t present_id_info_count = swapchain_state->GetPresentIdInfoCount();
        for (uint32_t i = 0; i < present_id_info_count; i++) {
            const Swapchain::PresentIdInfo present_id_info = swapchain_state->GetPresentIdInfo(i);
            if (present_id_info.present_id >= present_id) {
                present_id_info.present_submission_ref.queue->NotifyAndWait(location, present_id_info.present_submission_ref.seq);
                break;
            }
        }
    }
}

void DeviceState::PostCallRecordWaitForPresentKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t presentId, uint64_t timeout,
                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS && record_obj.result != VK_SUBOPTIMAL_KHR) {
        return;
    }
    RecordWaitForPresent(device, swapchain, presentId, record_obj.location);
}

void DeviceState::PostCallRecordWaitForPresent2KHR(VkDevice device, VkSwapchainKHR swapchain,
                                                   const VkPresentWait2InfoKHR* pPresentWait2Info, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS && record_obj.result != VK_SUBOPTIMAL_KHR) {
        return;
    }
    RecordWaitForPresent(device, swapchain, pPresentWait2Info->presentId, record_obj.location);
}

std::shared_ptr<PhysicalDevice> InstanceState::CreatePhysicalDeviceState(VkPhysicalDevice handle) {
    return std::make_shared<PhysicalDevice>(handle);
}

void InstanceState::PostCallRecordCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator,
                                                 VkInstance* pInstance, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    uint32_t count = 0;
    // this can fail if the allocator fails
    VkResult result = DispatchEnumeratePhysicalDevices(*pInstance, &count, nullptr);
    if (result != VK_SUCCESS) {
        return;
    }
    std::vector<VkPhysicalDevice> physdev_handles(count);
    result = DispatchEnumeratePhysicalDevices(*pInstance, &count, physdev_handles.data());
    if (result != VK_SUCCESS) {
        return;
    }

    for (auto physdev : physdev_handles) {
        Add(CreatePhysicalDeviceState(physdev));
    }

#ifdef VK_USE_PLATFORM_METAL_EXT
    auto export_metal_object_info = vku::FindStructInPNextChain<VkExportMetalObjectCreateInfoEXT>(pCreateInfo->pNext);
    while (export_metal_object_info) {
        export_metal_flags.push_back(export_metal_object_info->exportObjectType);
        export_metal_object_info = vku::FindStructInPNextChain<VkExportMetalObjectCreateInfoEXT>(export_metal_object_info->pNext);
    }
#endif  // VK_USE_PLATFORM_METAL_EXT
}

void InstanceState::PreCallRecordCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo,
                                              const VkAllocationCallbacks* pAllocator, VkDevice* pDevice,
                                              const RecordObject& record_obj, vku::safe_VkDeviceCreateInfo* modified_create_info) {
#if defined(VVL_TRACY_GPU)
    auto ext_already_enabled = [](const vku::safe_VkDeviceCreateInfo* dci, const char* ext_name) {
        bool ext_enabled = false;
        for (auto ext : make_span(dci->ppEnabledExtensionNames, dci->enabledExtensionCount)) {
            if (strcmp(ext, ext_name) == 0) {
                ext_enabled = true;
                break;
            }
        }
        return ext_enabled;
    };

    auto enable_ext = [](vku::safe_VkDeviceCreateInfo* dci, const char* ext_name) {
        const char** tmp_ppEnabledExtensionNames = new const char*[dci->enabledExtensionCount + 1];
        for (uint32_t i = 0; i < dci->enabledExtensionCount; ++i) {
            tmp_ppEnabledExtensionNames[i] = vku::SafeStringCopy(dci->ppEnabledExtensionNames[i]);
        }
        tmp_ppEnabledExtensionNames[dci->enabledExtensionCount] = vku::SafeStringCopy(ext_name);
        dci->ppEnabledExtensionNames = tmp_ppEnabledExtensionNames;
        ++dci->enabledExtensionCount;
    };

    if (!ext_already_enabled(modified_create_info, VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME)) {
        enable_ext(modified_create_info, VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME);
    }
    auto host_query_reset_feature = const_cast<VkPhysicalDeviceHostQueryResetFeatures*>(
        vku::FindStructInPNextChain<VkPhysicalDeviceHostQueryResetFeatures>(pCreateInfo->pNext));
    if (host_query_reset_feature) {
        host_query_reset_feature->hostQueryReset = VK_TRUE;
    } else {
        VkPhysicalDeviceHostQueryResetFeatures new_host_query_reset_feature = vku::InitStructHelper();
        new_host_query_reset_feature.hostQueryReset = VK_TRUE;
        vku::AddToPnext(*modified_create_info, new_host_query_reset_feature);
    }

    if (!ext_already_enabled(modified_create_info, VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME)) {
        enable_ext(modified_create_info, VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME);
    }
#endif
}

// Common function to update state for GetPhysicalDeviceQueueFamilyProperties & 2KHR version
static void StateUpdateCommonGetPhysicalDeviceQueueFamilyProperties(PhysicalDevice* pd_state, uint32_t count) {
    pd_state->queue_family_known_count = std::max(pd_state->queue_family_known_count, count);
}

void InstanceState::PostCallRecordGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice,
                                                                         uint32_t* pQueueFamilyPropertyCount,
                                                                         VkQueueFamilyProperties* pQueueFamilyProperties,
                                                                         const RecordObject& record_obj) {
    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    StateUpdateCommonGetPhysicalDeviceQueueFamilyProperties(pd_state.get(), *pQueueFamilyPropertyCount);
}

void InstanceState::PostCallRecordGetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice physicalDevice,
                                                                          uint32_t* pQueueFamilyPropertyCount,
                                                                          VkQueueFamilyProperties2* pQueueFamilyProperties,
                                                                          const RecordObject& record_obj) {
    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    StateUpdateCommonGetPhysicalDeviceQueueFamilyProperties(pd_state.get(), *pQueueFamilyPropertyCount);
}

void InstanceState::PostCallRecordGetPhysicalDeviceQueueFamilyProperties2KHR(VkPhysicalDevice physicalDevice,
                                                                             uint32_t* pQueueFamilyPropertyCount,
                                                                             VkQueueFamilyProperties2* pQueueFamilyProperties,
                                                                             const RecordObject& record_obj) {
    PostCallRecordGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties,
                                                          record_obj);
}

void InstanceState::PreCallRecordDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface,
                                                   const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    Destroy<Surface>(surface);
}

void InstanceState::RecordVulkanSurface(VkSurfaceKHR* pSurface, SurfaceType type) { Add(std::make_shared<Surface>(*pSurface, type)); }

void InstanceState::PostCallRecordCreateDisplayPlaneSurfaceKHR(VkInstance instance,
                                                               const VkDisplaySurfaceCreateInfoKHR* pCreateInfo,
                                                               const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                               const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::DisplayPlane);
}

#ifdef VK_USE_PLATFORM_ANDROID_KHR
void InstanceState::PostCallRecordCreateAndroidSurfaceKHR(VkInstance instance, const VkAndroidSurfaceCreateInfoKHR* pCreateInfo,
                                                          const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                          const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::Android);
}
#endif  // VK_USE_PLATFORM_ANDROID_KHR

#ifdef VK_USE_PLATFORM_FUCHSIA
void InstanceState::PostCallRecordCreateImagePipeSurfaceFUCHSIA(VkInstance instance,
                                                                const VkImagePipeSurfaceCreateInfoFUCHSIA* pCreateInfo,
                                                                const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::ImagePipe_FUCHSIA);
}
#endif  // VK_USE_PLATFORM_FUCHSIA

#ifdef VK_USE_PLATFORM_IOS_MVK
void InstanceState::PostCallRecordCreateIOSSurfaceMVK(VkInstance instance, const VkIOSSurfaceCreateInfoMVK* pCreateInfo,
                                                      const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                      const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::IOS_MVK);
}
#endif  // VK_USE_PLATFORM_IOS_MVK

#ifdef VK_USE_PLATFORM_MACOS_MVK
void InstanceState::PostCallRecordCreateMacOSSurfaceMVK(VkInstance instance, const VkMacOSSurfaceCreateInfoMVK* pCreateInfo,
                                                        const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                        const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::MacOS_MVK);
}
#endif  // VK_USE_PLATFORM_MACOS_MVK

#ifdef VK_USE_PLATFORM_METAL_EXT
void InstanceState::PostCallRecordCreateMetalSurfaceEXT(VkInstance instance, const VkMetalSurfaceCreateInfoEXT* pCreateInfo,
                                                        const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                        const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::Metal);
}
#endif  // VK_USE_PLATFORM_METAL_EXT

#ifdef VK_USE_PLATFORM_WAYLAND_KHR
void InstanceState::PostCallRecordCreateWaylandSurfaceKHR(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo,
                                                          const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                          const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::Wayland);
}
#endif  // VK_USE_PLATFORM_WAYLAND_KHR

#ifdef VK_USE_PLATFORM_XCB_KHR
void InstanceState::PostCallRecordCreateXcbSurfaceKHR(VkInstance instance, const VkXcbSurfaceCreateInfoKHR* pCreateInfo,
                                                      const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                      const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
#if defined(DEBUG_CAPTURE_KEYBOARD)
    xcb_connection = (void*)pCreateInfo->connection;
#endif
    RecordVulkanSurface(pSurface, SurfaceType::Xcb);
}
#endif  // VK_USE_PLATFORM_XCB_KHR

#ifdef VK_USE_PLATFORM_XLIB_KHR
void InstanceState::PostCallRecordCreateXlibSurfaceKHR(VkInstance instance, const VkXlibSurfaceCreateInfoKHR* pCreateInfo,
                                                       const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                       const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
#if defined(DEBUG_CAPTURE_KEYBOARD)
    xlib_display = (void*)pCreateInfo->dpy;
#endif
    RecordVulkanSurface(pSurface, SurfaceType::Xlib);
}
#endif  // VK_USE_PLATFORM_XLIB_KHR

#ifdef VK_USE_PLATFORM_SCREEN_QNX
void InstanceState::PostCallRecordCreateScreenSurfaceQNX(VkInstance instance, const VkScreenSurfaceCreateInfoQNX* pCreateInfo,
                                                         const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                         const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::Screen_QNX);
}
#endif  // VK_USE_PLATFORM_SCREEN_QNX

void InstanceState::PostCallRecordCreateHeadlessSurfaceEXT(VkInstance instance, const VkHeadlessSurfaceCreateInfoEXT* pCreateInfo,
                                                           const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface,
                                                           const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    RecordVulkanSurface(pSurface, SurfaceType::Headless);
}

void InstanceState::PostCallRecordGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
                                                                          VkSurfaceCapabilitiesKHR* pSurfaceCapabilities,
                                                                          const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    ASSERT_AND_RETURN(pd_state);
    pd_state->SetCallState(record_obj.location.function, CallState::QueryDetails);

    auto surface_state = Get<Surface>(surface);
    ASSERT_AND_RETURN(surface_state);
    surface_state->UpdateCapabilitiesCache(physicalDevice, *pSurfaceCapabilities);
}

void InstanceState::PostCallRecordGetPhysicalDeviceSurfaceCapabilities2KHR(VkPhysicalDevice physicalDevice,
                                                                           const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo,
                                                                           VkSurfaceCapabilities2KHR* pSurfaceCapabilities,
                                                                           const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    ASSERT_AND_RETURN(pd_state);

    pd_state->SetCallState(record_obj.location.function, CallState::QueryDetails);

    if (pSurfaceInfo->surface) {
        auto surface_state = Get<Surface>(pSurfaceInfo->surface);
        ASSERT_AND_RETURN(surface_state);
        if (!pSurfaceInfo->pNext) {
            surface_state->UpdateCapabilitiesCache(physicalDevice, pSurfaceCapabilities->surfaceCapabilities);
        } else if (IsExtEnabled(extensions.vk_khr_surface_maintenance1) || IsExtEnabled(extensions.vk_ext_surface_maintenance1)) {
            const auto* surface_present_mode = vku::FindStructInPNextChain<VkSurfacePresentModeKHR>(pSurfaceInfo->pNext);
            if (surface_present_mode) {
                // The surface caps caching should take into account pSurfaceInfo->pNext chain structure,
                // because each pNext element can affect query result. Here we support caching for a common
                // case when pNext chain is a single VkSurfacePresentModeKHR structure.
                const bool single_pnext_element = (pSurfaceInfo->pNext == surface_present_mode) && !surface_present_mode->pNext;
                if (single_pnext_element) {
                    surface_state->UpdateCapabilitiesCache(physicalDevice, *pSurfaceCapabilities,
                                                           surface_present_mode->presentMode);
                }
            }
        }
    } else if (IsExtEnabled(extensions.vk_google_surfaceless_query) &&
               vku::FindStructInPNextChain<VkSurfaceProtectedCapabilitiesKHR>(pSurfaceCapabilities->pNext)) {
        pd_state->surfaceless_query_state.capabilities = vku::safe_VkSurfaceCapabilities2KHR(pSurfaceCapabilities);
    }
}

void InstanceState::PostCallRecordGetPhysicalDeviceSurfaceCapabilities2EXT(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
                                                                           VkSurfaceCapabilities2EXT* pSurfaceCapabilities,
                                                                           const RecordObject& record_obj) {
    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    ASSERT_AND_RETURN(pd_state);
    pd_state->SetCallState(record_obj.location.function, CallState::Uncalled);

    const VkSurfaceCapabilitiesKHR caps{
        pSurfaceCapabilities->minImageCount,           pSurfaceCapabilities->maxImageCount,
        pSurfaceCapabilities->currentExtent,           pSurfaceCapabilities->minImageExtent,
        pSurfaceCapabilities->maxImageExtent,          pSurfaceCapabilities->maxImageArrayLayers,
        pSurfaceCapabilities->supportedTransforms,     pSurfaceCapabilities->currentTransform,
        pSurfaceCapabilities->supportedCompositeAlpha, pSurfaceCapabilities->supportedUsageFlags,
    };
    auto surface_state = Get<Surface>(surface);
    ASSERT_AND_RETURN(surface_state);
    surface_state->UpdateCapabilitiesCache(physicalDevice, caps);
}

void InstanceState::PostCallRecordGetPhysicalDeviceSurfaceSupportKHR(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex,
                                                                     VkSurfaceKHR surface, VkBool32* pSupported,
                                                                     const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    auto surface_state = Get<Surface>(surface);
    ASSERT_AND_RETURN(surface_state);
    surface_state->SetQueueSupport(physicalDevice, queueFamilyIndex, (*pSupported == VK_TRUE));
}

void InstanceState::PostCallRecordGetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
                                                                          uint32_t* pPresentModeCount,
                                                                          VkPresentModeKHR* pPresentModes,
                                                                          const RecordObject& record_obj) {
    if ((VK_SUCCESS != record_obj.result) && (VK_INCOMPLETE != record_obj.result)) {
        return;
    }

    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    ASSERT_AND_RETURN(pd_state);

    pd_state->SetCallState(record_obj.location.function, pPresentModes != nullptr);

    if (pPresentModes) {
        if (surface) {
            auto surface_state = Get<Surface>(surface);
            ASSERT_AND_RETURN(surface_state);
            surface_state->SetPresentModes(physicalDevice, span<const VkPresentModeKHR>(pPresentModes, *pPresentModeCount));
        } else if (IsExtEnabled(extensions.vk_google_surfaceless_query)) {
            pd_state->surfaceless_query_state.present_modes =
                std::vector<VkPresentModeKHR>(pPresentModes, pPresentModes + *pPresentModeCount);
        }
    }
}

void InstanceState::PostCallRecordGetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
                                                                     uint32_t* pSurfaceFormatCount,
                                                                     VkSurfaceFormatKHR* pSurfaceFormats,
                                                                     const RecordObject& record_obj) {
    if ((VK_SUCCESS != record_obj.result) && (VK_INCOMPLETE != record_obj.result)) {
        return;
    }

    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    if (!pd_state) {
        return;
    }

    pd_state->SetCallState(record_obj.location.function, pSurfaceFormats != nullptr);

    if (pSurfaceFormatCount) {
        pd_state->surface_formats_count = *pSurfaceFormatCount;
    }
    if (pSurfaceFormats) {
        std::vector<vku::safe_VkSurfaceFormat2KHR> formats2(*pSurfaceFormatCount);
        for (uint32_t surface_format_index = 0; surface_format_index < *pSurfaceFormatCount; surface_format_index++) {
            formats2[surface_format_index].surfaceFormat = pSurfaceFormats[surface_format_index];
        }
        if (surface) {
            auto surface_state = Get<Surface>(surface);
            ASSERT_AND_RETURN(surface_state);
            surface_state->SetFormats(physicalDevice, std::move(formats2));
        } else if (IsExtEnabled(extensions.vk_google_surfaceless_query)) {
            ASSERT_AND_RETURN(pd_state);
            pd_state->surfaceless_query_state.formats = std::move(formats2);
        }
    }
}

void InstanceState::PostCallRecordGetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice,
                                                                      const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo,
                                                                      uint32_t* pSurfaceFormatCount,
                                                                      VkSurfaceFormat2KHR* pSurfaceFormats,
                                                                      const RecordObject& record_obj) {
    if ((VK_SUCCESS != record_obj.result) && (VK_INCOMPLETE != record_obj.result)) {
        return;
    }

    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    ASSERT_AND_RETURN(pd_state);
    pd_state->SetCallState(record_obj.location.function, pSurfaceFormats != nullptr);
    if (*pSurfaceFormatCount) {
        pd_state->surface_formats_count = *pSurfaceFormatCount;
    }
    if (pSurfaceFormats) {
        if (pSurfaceInfo->surface) {
            auto surface_state = Get<Surface>(pSurfaceInfo->surface);
            ASSERT_AND_RETURN(surface_state);
            std::vector<vku::safe_VkSurfaceFormat2KHR> formats2(*pSurfaceFormatCount);
            for (uint32_t surface_format_index = 0; surface_format_index < *pSurfaceFormatCount; surface_format_index++) {
                formats2[surface_format_index].initialize(&pSurfaceFormats[surface_format_index]);
            }
            surface_state->SetFormats(physicalDevice, std::move(formats2));
        } else if (IsExtEnabled(extensions.vk_google_surfaceless_query)) {
            pd_state->surfaceless_query_state.formats.clear();
            pd_state->surfaceless_query_state.formats.reserve(*pSurfaceFormatCount);
            for (uint32_t surface_format_index = 0; surface_format_index < *pSurfaceFormatCount; ++surface_format_index) {
                pd_state->surfaceless_query_state.formats.emplace_back(&pSurfaceFormats[surface_format_index]);
            }
        }
    }
}

void DeviceState::PreCallRecordCmdBeginDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo,
                                                          const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    debug_report->BeginCmdDebugUtilsLabel(commandBuffer, pLabelInfo);
}

void DeviceState::PostCallRecordCmdBeginDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo,
                                                           const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->BeginLabel((pLabelInfo && pLabelInfo->pLabelName) ? pLabelInfo->pLabelName : "");
}

void DeviceState::PostCallRecordCmdEndDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    cb_state->EndLabel();
    debug_report->EndCmdDebugUtilsLabel(commandBuffer);
}

void DeviceState::PreCallRecordCmdInsertDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo,
                                                           const RecordObject& record_obj) {
    debug_report->InsertCmdDebugUtilsLabel(commandBuffer, pLabelInfo);

    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
}

void DeviceState::PostCallRecordAcquireProfilingLockKHR(VkDevice device, const VkAcquireProfilingLockInfoKHR* pInfo,
                                                        const RecordObject& record_obj) {
    if (record_obj.result == VK_SUCCESS) performance_lock_acquired = true;
}

void DeviceState::PostCallRecordReleaseProfilingLockKHR(VkDevice device, const RecordObject& record_obj) {
    performance_lock_acquired = false;
    for (auto& cmd_buffer : command_buffer_map_.snapshot()) {
        cmd_buffer.second->performance_lock_released = true;
    }
}

void DeviceState::PreCallRecordDestroyDescriptorUpdateTemplate(VkDevice device, VkDescriptorUpdateTemplate descriptorUpdateTemplate,
                                                               const VkAllocationCallbacks* pAllocator,
                                                               const RecordObject& record_obj) {
    Destroy<DescriptorUpdateTemplate>(descriptorUpdateTemplate);
}

void DeviceState::PreCallRecordDestroyDescriptorUpdateTemplateKHR(VkDevice device,
                                                                  VkDescriptorUpdateTemplate descriptorUpdateTemplate,
                                                                  const VkAllocationCallbacks* pAllocator,
                                                                  const RecordObject& record_obj) {
    PreCallRecordDestroyDescriptorUpdateTemplate(device, descriptorUpdateTemplate, pAllocator, record_obj);
}

void DeviceState::PostCallRecordCreateDescriptorUpdateTemplate(VkDevice device,
                                                               const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo,
                                                               const VkAllocationCallbacks* pAllocator,
                                                               VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate,
                                                               const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<DescriptorUpdateTemplate>(*pDescriptorUpdateTemplate, pCreateInfo));
}

void DeviceState::PostCallRecordCreateDescriptorUpdateTemplateKHR(VkDevice device,
                                                                  const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo,
                                                                  const VkAllocationCallbacks* pAllocator,
                                                                  VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate,
                                                                  const RecordObject& record_obj) {
    PostCallRecordCreateDescriptorUpdateTemplate(device, pCreateInfo, pAllocator, pDescriptorUpdateTemplate, record_obj);
}

void DeviceState::PreCallRecordUpdateDescriptorSetWithTemplate(VkDevice device, VkDescriptorSet descriptorSet,
                                                               VkDescriptorUpdateTemplate descriptorUpdateTemplate,
                                                               const void* pData, const RecordObject& record_obj) {
    if (auto const template_state = Get<DescriptorUpdateTemplate>(descriptorUpdateTemplate)) {
        // TODO: Record template push descriptor updates
        if (template_state->create_info.templateType == VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET) {
            PerformUpdateDescriptorSetsWithTemplateKHR(descriptorSet, *template_state, pData);
        }
    }
}

void DeviceState::PreCallRecordUpdateDescriptorSetWithTemplateKHR(VkDevice device, VkDescriptorSet descriptorSet,
                                                                  VkDescriptorUpdateTemplate descriptorUpdateTemplate,
                                                                  const void* pData, const RecordObject& record_obj) {
    PreCallRecordUpdateDescriptorSetWithTemplate(device, descriptorSet, descriptorUpdateTemplate, pData, record_obj);
}

void DeviceState::PostCallRecordCmdPushDescriptorSetWithTemplate(VkCommandBuffer commandBuffer,
                                                                 VkDescriptorUpdateTemplate descriptorUpdateTemplate,
                                                                 VkPipelineLayout layout, uint32_t set, const void* pData,
                                                                 const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto template_state = Get<DescriptorUpdateTemplate>(descriptorUpdateTemplate);
    auto pipeline_layout = Get<PipelineLayout>(layout);

    if (pipeline_layout->has_descriptor_buffer) {
        cb_state->SetDescriptorMode(DescriptorModeBuffer, record_obj.location.function);
    } else {
        cb_state->SetDescriptorMode(DescriptorModeClassic, record_obj.location.function);
    }

    if (!template_state || !pipeline_layout) {
        return;
    }

    cb_state->RecordCommand(record_obj.location);
    auto dsl = pipeline_layout->set_layouts.list[set];
    // Decode the template into a set of write updates
    DecodedTemplateUpdate decoded_template(*this, VK_NULL_HANDLE, *template_state, pData, dsl->VkHandle());
    cb_state->PushDescriptorSetState(template_state->create_info.pipelineBindPoint, pipeline_layout, set,
                                     static_cast<uint32_t>(decoded_template.desc_writes.size()),
                                     decoded_template.desc_writes.data(), record_obj.location);
}

void DeviceState::PostCallRecordCmdPushDescriptorSetWithTemplateKHR(VkCommandBuffer commandBuffer,
                                                                    VkDescriptorUpdateTemplate descriptorUpdateTemplate,
                                                                    VkPipelineLayout layout, uint32_t set, const void* pData,
                                                                    const RecordObject& record_obj) {
    PostCallRecordCmdPushDescriptorSetWithTemplate(commandBuffer, descriptorUpdateTemplate, layout, set, pData, record_obj);
}

void DeviceState::PostCallRecordCmdPushDescriptorSetWithTemplate2(
    VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfo* pPushDescriptorSetWithTemplateInfo,
    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto template_state = Get<DescriptorUpdateTemplate>(pPushDescriptorSetWithTemplateInfo->descriptorUpdateTemplate);
    auto pipeline_layout = Get<PipelineLayout>(pPushDescriptorSetWithTemplateInfo->layout);

    if (pipeline_layout->has_descriptor_buffer) {
        cb_state->SetDescriptorMode(DescriptorModeBuffer, record_obj.location.function);
    } else {
        cb_state->SetDescriptorMode(DescriptorModeClassic, record_obj.location.function);
    }

    if (!template_state || !pipeline_layout) {
        return;
    }

    cb_state->RecordCommand(record_obj.location);
    auto dsl = pipeline_layout->set_layouts.list[pPushDescriptorSetWithTemplateInfo->set];
    // Decode the template into a set of write updates
    DecodedTemplateUpdate decoded_template(*this, VK_NULL_HANDLE, *template_state, pPushDescriptorSetWithTemplateInfo->pData,
                                           dsl->VkHandle());
    cb_state->PushDescriptorSetState(
        template_state->create_info.pipelineBindPoint, pipeline_layout, pPushDescriptorSetWithTemplateInfo->set,
        static_cast<uint32_t>(decoded_template.desc_writes.size()), decoded_template.desc_writes.data(), record_obj.location);
}

void DeviceState::PostCallRecordCmdPushDescriptorSetWithTemplate2KHR(
    VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfoKHR* pPushDescriptorSetWithTemplateInfo,
    const RecordObject& record_obj) {
    PostCallRecordCmdPushDescriptorSetWithTemplate2(commandBuffer, pPushDescriptorSetWithTemplateInfo, record_obj);
}

void InstanceState::PostCallRecordGetPhysicalDeviceFeatures(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures* pFeatures,
                                                            const RecordObject& record_obj) {
    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    pd_state->SetCallState(record_obj.location.function, true);
}

void InstanceState::PostCallRecordGetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures,
                                                             const RecordObject& record_obj) {
    auto pd_state = Get<PhysicalDevice>(physicalDevice);
    pd_state->SetCallState(record_obj.location.function, true);
}

void InstanceState::PostCallRecordGetPhysicalDeviceFeatures2KHR(VkPhysicalDevice physicalDevice,
                                                                VkPhysicalDeviceFeatures2* pFeatures,
                                                                const RecordObject& record_obj) {
    PostCallRecordGetPhysicalDeviceFeatures2(physicalDevice, pFeatures, record_obj);
}

void DeviceState::PostCallRecordCmdBeginQueryIndexedEXT(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t slot,
                                                        VkQueryControlFlags flags, uint32_t index, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    if (disabled[query_validation]) {
        return;
    }

    QueryCount query_count(*cb_state);

    for (uint32_t i = 0; i < query_count.count; ++i) {
        QueryObject query_obj = {queryPool, slot, flags, 0, true, index + i};
        query_obj.inside_render_pass = query_count.inside_render_pass;
        query_obj.subpass = query_count.subpass;
        cb_state->RecordBeginQuery(query_obj, record_obj.location);
    }

    if (!disabled[command_buffer_state]) {
        auto pool_state = Get<QueryPool>(queryPool);
        cb_state->AddChild(pool_state);
    }
}

void DeviceState::PostCallRecordCmdEndQueryIndexedEXT(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t slot,
                                                      uint32_t index, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    if (disabled[query_validation]) {
        return;
    }

    QueryCount query_count(*cb_state);

    for (uint32_t i = 0; i < query_count.count; ++i) {
        QueryObject query_obj = {queryPool, slot, 0, 0, true, index + i};
        query_obj.inside_render_pass = query_count.inside_render_pass;
        query_obj.subpass = query_count.subpass;
        query_obj.end_command_index = cb_state->command_count;  // counting this command
        cb_state->RecordEndQuery(query_obj, record_obj.location);
    }

    if (!disabled[command_buffer_state]) {
        auto pool_state = Get<QueryPool>(queryPool);
        cb_state->AddChild(pool_state);
    }
}

void DeviceState::PostCallRecordCreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo,
                                                             const VkAllocationCallbacks* pAllocator,
                                                             VkSamplerYcbcrConversion* pYcbcrConversion,
                                                             const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    VkFormatFeatureFlags2 format_features = 0;

    if (pCreateInfo->format != VK_FORMAT_UNDEFINED) {
        format_features = GetPotentialFormatFeatures(pCreateInfo->format);
    } else if (IsExtEnabled(extensions.vk_android_external_memory_android_hardware_buffer)) {
        // If format is VK_FORMAT_UNDEFINED, format_features will be set by external AHB features
        format_features = GetExternalFormatFeaturesANDROID(pCreateInfo->pNext);
    }

    Add(std::make_shared<SamplerYcbcrConversion>(*pYcbcrConversion, pCreateInfo, format_features));
}

void DeviceState::PostCallRecordCreateSamplerYcbcrConversionKHR(VkDevice device,
                                                                const VkSamplerYcbcrConversionCreateInfo* pCreateInfo,
                                                                const VkAllocationCallbacks* pAllocator,
                                                                VkSamplerYcbcrConversion* pYcbcrConversion,
                                                                const RecordObject& record_obj) {
    PostCallRecordCreateSamplerYcbcrConversion(device, pCreateInfo, pAllocator, pYcbcrConversion, record_obj);
}

void DeviceState::PreCallRecordDestroySamplerYcbcrConversion(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion,
                                                             const VkAllocationCallbacks* pAllocator,
                                                             const RecordObject& record_obj) {
    Destroy<SamplerYcbcrConversion>(ycbcrConversion);
}

void DeviceState::PreCallRecordDestroySamplerYcbcrConversionKHR(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion,
                                                                const VkAllocationCallbacks* pAllocator,
                                                                const RecordObject& record_obj) {
    PreCallRecordDestroySamplerYcbcrConversion(device, ycbcrConversion, pAllocator, record_obj);
}

void DeviceState::PostCallRecordResetQueryPoolEXT(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount,
                                                  const RecordObject& record_obj) {
    PostCallRecordResetQueryPool(device, queryPool, firstQuery, queryCount, record_obj);
}

void DeviceState::PostCallRecordResetQueryPool(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount,
                                               const RecordObject& record_obj) {
    // Do nothing if the feature is not enabled.
    if (!enabled_features.hostQueryReset) {
        return;
    } else if (disabled[query_validation]) {
        return;
    }

    // Do nothing if the query pool has been destroyed.
    auto query_pool_state = Get<QueryPool>(queryPool);
    ASSERT_AND_RETURN(query_pool_state);

    // Reset the state of existing entries.
    const uint32_t max_query_count = std::min(queryCount, query_pool_state->create_info.queryCount - firstQuery);
    for (uint32_t i = 0; i < max_query_count; ++i) {
        auto query_index = firstQuery + i;
        query_pool_state->SetQueryState(query_index, 0, QUERYSTATE_RESET);
        if (query_pool_state->create_info.queryType == VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR) {
            for (uint32_t pass_index = 0; pass_index < query_pool_state->n_performance_passes; pass_index++) {
                query_pool_state->SetQueryState(query_index, pass_index, QUERYSTATE_RESET);
            }
        }
    }
}

void DeviceState::PerformUpdateDescriptorSetsWithTemplateKHR(VkDescriptorSet descriptorSet,
                                                             const DescriptorUpdateTemplate& template_state, const void* pData) {
    // Translate the templated update into a normal update for validation...
    DecodedTemplateUpdate decoded_template(*this, descriptorSet, template_state, pData);
    PerformUpdateDescriptorSets(static_cast<uint32_t>(decoded_template.desc_writes.size()), decoded_template.desc_writes.data(), 0,
                                NULL);
}

void DeviceState::PostCallRecordCmdDraw(VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount,
                                        uint32_t firstVertex, uint32_t firstInstance, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
}

void DeviceState::PostCallRecordCmdDrawMultiEXT(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<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
}

void DeviceState::PostCallRecordCmdDrawIndexed(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<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
}

void DeviceState::PostCallRecordCmdDrawMultiIndexedEXT(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<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
}

void DeviceState::PostCallRecordCmdDrawIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t count,
                                                uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto buffer_state = Get<Buffer>(buffer);
    cb_state->RecordDraw(record_obj.location);
    if (!disabled[command_buffer_state]) {
        cb_state->AddChild(buffer_state);
    }
}

void DeviceState::PostCallRecordCmdDrawIndexedIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                       uint32_t count, uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    auto buffer_state = Get<Buffer>(buffer);
    cb_state->RecordDraw(record_obj.location);
    if (!disabled[command_buffer_state]) {
        cb_state->AddChild(buffer_state);
    }
}

void DeviceState::PostCallRecordCmdDispatch(VkCommandBuffer commandBuffer, uint32_t x, uint32_t y, uint32_t z,
                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDispatch(record_obj.location);
}

void DeviceState::PostCallRecordCmdDispatchIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDispatch(record_obj.location);
    if (!disabled[command_buffer_state]) {
        auto buffer_state = Get<Buffer>(buffer);
        cb_state->AddChild(buffer_state);
    }
}

void DeviceState::PostCallRecordCmdDispatchBaseKHR(VkCommandBuffer commandBuffer, uint32_t base_x, uint32_t base_y, uint32_t base_z,
                                                   uint32_t x, uint32_t y, uint32_t z, const RecordObject& record_obj) {
    PostCallRecordCmdDispatchBase(commandBuffer, x, y, z, base_x, base_y, base_z, record_obj);
}

void DeviceState::PostCallRecordCmdDispatchBase(VkCommandBuffer commandBuffer, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t,
                                                uint32_t, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDispatch(record_obj.location);
}

void DeviceState::PostCallRecordCmdDrawIndirectCountKHR(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                        VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                        uint32_t stride, const RecordObject& record_obj) {
    PostCallRecordCmdDrawIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                       record_obj);
}

void DeviceState::PostCallRecordCmdDrawIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                     VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                     uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    if (!disabled[command_buffer_state]) {
        auto buffer_state = Get<Buffer>(buffer);
        auto count_buffer_state = Get<Buffer>(countBuffer);
        cb_state->AddChild(buffer_state);
        cb_state->AddChild(count_buffer_state);
    }
}

void DeviceState::PostCallRecordCmdDrawIndexedIndirectCountKHR(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                               VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                               uint32_t maxDrawCount, uint32_t stride,
                                                               const RecordObject& record_obj) {
    PostCallRecordCmdDrawIndexedIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                              record_obj);
}

void DeviceState::PostCallRecordCmdDrawIndexedIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                            VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                            uint32_t maxDrawCount, uint32_t stride,
                                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    if (!disabled[command_buffer_state]) {
        auto buffer_state = Get<Buffer>(buffer);
        auto count_buffer_state = Get<Buffer>(countBuffer);
        cb_state->AddChild(buffer_state);
        cb_state->AddChild(count_buffer_state);
    }
}

void DeviceState::PostCallRecordCmdDrawMeshTasksNV(VkCommandBuffer commandBuffer, uint32_t taskCount, uint32_t firstTask,
                                                   const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
}

void DeviceState::PostCallRecordCmdDrawMeshTasksIndirectNV(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                           uint32_t drawCount, uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    auto buffer_state = Get<Buffer>(buffer);
    if (!disabled[command_buffer_state] && buffer_state) {
        cb_state->AddChild(buffer_state);
    }
}

void DeviceState::PostCallRecordCmdDrawMeshTasksIndirectCountNV(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                                VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                                uint32_t maxDrawCount, uint32_t stride,
                                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    if (!disabled[command_buffer_state]) {
        if (auto buffer_state = Get<Buffer>(buffer)) {
            cb_state->AddChild(buffer_state);
        }
        if (auto count_buffer_state = Get<Buffer>(countBuffer)) {
            cb_state->AddChild(count_buffer_state);
        }
    }
}

void DeviceState::PostCallRecordCmdDrawMeshTasksEXT(VkCommandBuffer commandBuffer, uint32_t groupCountX, uint32_t groupCountY,
                                                    uint32_t groupCountZ, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
}

void DeviceState::PostCallRecordCmdDrawMeshTasksIndirectEXT(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                            uint32_t drawCount, uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    auto buffer_state = Get<Buffer>(buffer);
    if (!disabled[command_buffer_state] && buffer_state) {
        cb_state->AddChild(buffer_state);
    }
}

void DeviceState::PostCallRecordCmdDrawMeshTasksIndirectCountEXT(VkCommandBuffer commandBuffer, VkBuffer buffer,
                                                                 VkDeviceSize offset, VkBuffer countBuffer,
                                                                 VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                                 uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    if (!disabled[command_buffer_state]) {
        if (auto buffer_state = Get<Buffer>(buffer)) {
            cb_state->AddChild(buffer_state);
        }
        if (auto count_buffer_state = Get<Buffer>(countBuffer)) {
            cb_state->AddChild(count_buffer_state);
        }
    }
}

void DeviceState::PostCallRecordCmdDispatchIndirect2KHR(VkCommandBuffer commandBuffer, const VkDispatchIndirect2InfoKHR* pInfo,
                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDispatch(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pInfo->addressRange.address, pInfo->addressRange.size,
                            VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdDrawIndexedIndirect2KHR(VkCommandBuffer commandBuffer, const VkDrawIndirect2InfoKHR* pInfo,
                                                           const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pInfo->addressRange.address, pInfo->addressRange.size,
                            VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdDrawIndirect2KHR(VkCommandBuffer commandBuffer, const VkDrawIndirect2InfoKHR* pInfo,
                                                    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pInfo->addressRange.address, pInfo->addressRange.size,
                            VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdDrawIndirectCount2KHR(VkCommandBuffer commandBuffer, const VkDrawIndirectCount2InfoKHR* pInfo,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pInfo->addressRange.address, pInfo->addressRange.size,
                            VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdDrawIndexedIndirectCount2KHR(VkCommandBuffer commandBuffer,
                                                                const VkDrawIndirectCount2InfoKHR* pInfo,
                                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pInfo->addressRange.address, pInfo->addressRange.size,
                            VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdDrawMeshTasksIndirect2EXT(VkCommandBuffer commandBuffer, const VkDrawIndirect2InfoKHR* pInfo,
                                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pInfo->addressRange.address, pInfo->addressRange.size,
                            VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdDrawMeshTasksIndirectCount2EXT(VkCommandBuffer commandBuffer,
                                                                  const VkDrawIndirectCount2InfoKHR* pInfo,
                                                                  const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDraw(record_obj.location);
    TrackDeviceAddressRange(*cb_state, pInfo->addressRange.address, pInfo->addressRange.size,
                            VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdTraceRaysNV(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<CommandBuffer>(commandBuffer);
    cb_state->RecordTraceRay(record_obj.location.function);
}

void DeviceState::PostCallRecordCmdTraceRaysKHR(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<CommandBuffer>(commandBuffer);
    cb_state->RecordTraceRay(record_obj.location.function);
    TrackDeviceAddressRange(*cb_state, pRaygenShaderBindingTable->deviceAddress, pRaygenShaderBindingTable->size,
                            VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR);
    TrackDeviceAddressRange(*cb_state, pMissShaderBindingTable->deviceAddress, pMissShaderBindingTable->size,
                            VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR);
    TrackDeviceAddressRange(*cb_state, pHitShaderBindingTable->deviceAddress, pHitShaderBindingTable->size,
                            VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR);
    TrackDeviceAddressRange(*cb_state, pCallableShaderBindingTable->deviceAddress, pCallableShaderBindingTable->size,
                            VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR);
}

void DeviceState::PostCallRecordCmdTraceRaysIndirectKHR(VkCommandBuffer commandBuffer,
                                                        const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable,
                                                        const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable,
                                                        const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable,
                                                        const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable,
                                                        VkDeviceAddress indirectDeviceAddress, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordTraceRay(record_obj.location.function);
    TrackDeviceAddressRange(*cb_state, pRaygenShaderBindingTable->deviceAddress, pRaygenShaderBindingTable->size,
                            VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR);
    TrackDeviceAddressRange(*cb_state, pMissShaderBindingTable->deviceAddress, pMissShaderBindingTable->size,
                            VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR);
    TrackDeviceAddressRange(*cb_state, pHitShaderBindingTable->deviceAddress, pHitShaderBindingTable->size,
                            VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR);
    TrackDeviceAddressRange(*cb_state, pCallableShaderBindingTable->deviceAddress, pCallableShaderBindingTable->size,
                            VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR);
}

void DeviceState::PostCallRecordCmdTraceRaysIndirect2KHR(VkCommandBuffer commandBuffer, VkDeviceAddress indirectDeviceAddress,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordTraceRay(record_obj.location.function);
}

void DeviceState::PostCallRecordCmdExecuteGeneratedCommandsEXT(VkCommandBuffer commandBuffer, VkBool32 isPreprocessed,
                                                               const VkGeneratedCommandsInfoEXT* pGeneratedCommandsInfo,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordExecuteGeneratedCommands(*pGeneratedCommandsInfo, record_obj.location);

    // Spec says to check preprocessSize, not preprocessAddress
    if (pGeneratedCommandsInfo->preprocessSize != 0) {
        TrackDeviceAddressRange(*cb_state, pGeneratedCommandsInfo->preprocessAddress, pGeneratedCommandsInfo->preprocessSize,
                                VK_BUFFER_USAGE_2_PREPROCESS_BUFFER_BIT_EXT);
    }
    if (pGeneratedCommandsInfo->sequenceCountAddress != 0) {
        TrackDeviceAddressRange(*cb_state, pGeneratedCommandsInfo->sequenceCountAddress, sizeof(uint32_t),
                                VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT_KHR);
    }

    TrackDeviceAddressRange(*cb_state, pGeneratedCommandsInfo->indirectAddress, pGeneratedCommandsInfo->indirectAddressSize,
                            VK_BUFFER_USAGE_2_INDIRECT_BUFFER_BIT_KHR);
}

void DeviceState::PreCallRecordCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo,
                                                  const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule,
                                                  const RecordObject& record_obj, chassis::CreateShaderModule& chassis_state) {
    if (pCreateInfo->codeSize == 0 || !pCreateInfo->pCode) {
        return;
    } else if (chassis_state.module_state) {
        // We store the shader module at a chassis stack level (because we need it for PostCallRecord in things like GPU-AV)
        // Only one validation object needs to create it
        return;
    }

    chassis_state.module_state =
        CreateSpirvModuleState(pCreateInfo->codeSize, pCreateInfo->pCode, global_settings, &chassis_state.stateless_data);
    if (chassis_state.module_state && chassis_state.stateless_data.has_group_decoration) {
        spv_target_env spirv_environment = PickSpirvEnv(api_version, IsExtEnabled(extensions.vk_khr_spirv_1_4));
        spvtools::Optimizer optimizer(spirv_environment);
        optimizer.RegisterPass(spvtools::CreateFlattenDecorationPass());
        std::vector<uint32_t> optimized_binary;
        // Run optimizer to flatten decorations only, set skip_validation so as to not re-run validator
        auto result = optimizer.Run(chassis_state.module_state->words_.data(), chassis_state.module_state->words_.size(),
                                    &optimized_binary, spvtools::ValidatorOptions(), true);

        if (result) {
            // Easier to just re-create the ShaderModule as StaticData uses itself when building itself up
            // It is really rare this will get here as Group Decorations have been deprecated and before this was added no one ever
            // raised an issue for a bug that would crash the layers that was around for many releases.
            //
            // We also ignore doing this for any newer way to provide SPIR-V (GPL, shaderObject, RTX, etc) for same reason.
            chassis_state.module_state = CreateSpirvModuleState(optimized_binary.size() * sizeof(uint32_t), optimized_binary.data(),
                                                                global_settings, &chassis_state.stateless_data);
        }
    }
}

void DeviceState::PreCallRecordCreateShadersEXT(VkDevice device, uint32_t createInfoCount,
                                                const VkShaderCreateInfoEXT* pCreateInfos, const VkAllocationCallbacks* pAllocator,
                                                VkShaderEXT* pShaders, const RecordObject& record_obj,
                                                chassis::ShaderObject& chassis_state) {
    for (uint32_t i = 0; i < createInfoCount; ++i) {
        const VkShaderCreateInfoEXT& create_info = pCreateInfos[i];
        if (create_info.codeSize == 0 || !create_info.pCode || create_info.codeType != VK_SHADER_CODE_TYPE_SPIRV_EXT) {
            continue;
        }
        chassis_state.module_states[i] =
            CreateSpirvModuleState(create_info.codeSize, static_cast<const uint32_t*>(create_info.pCode), global_settings,
                                   &chassis_state.stateless_data[i]);
    }
}

void DeviceState::PostCallRecordCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo,
                                                   const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule,
                                                   const RecordObject& record_obj, chassis::CreateShaderModule& chassis_state) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<ShaderModule>(*pShaderModule, chassis_state.module_state));
}

void DeviceState::PostCallRecordCreateShadersEXT(VkDevice device, uint32_t createInfoCount,
                                                 const VkShaderCreateInfoEXT* pCreateInfos, const VkAllocationCallbacks* pAllocator,
                                                 VkShaderEXT* pShaders, const RecordObject& record_obj,
                                                 chassis::ShaderObject& chassis_state) {
    for (uint32_t i = 0; i < createInfoCount; ++i) {
        // If there are multiple shaders being created, and one is bad, will return a non VK_SUCCESS but we need to check if the
        // VkShaderEXT was null or not to actually know if it was created
        const VkShaderEXT shader_handle = pShaders[i];
        if (shader_handle == VK_NULL_HANDLE) {
            continue;
        }
        std::shared_ptr<ShaderObject> shader_object_state =
            std::make_shared<ShaderObject>(*this, pCreateInfos[i], shader_handle, chassis_state.module_states[i]);

        for (uint32_t j = 0; j < createInfoCount; ++j) {
            if (i != j && pShaders[j] != VK_NULL_HANDLE && (pCreateInfos[j].flags & VK_SHADER_CREATE_LINK_STAGE_BIT_EXT) != 0) {
                shader_object_state->linked_shaders.push_back(pShaders[j]);
            }
        }

        if (shader_object_state->descriptor_heap_embedded_samplers_count > 0) {
            descriptor_heap_global_embedded_sampler_count_ += shader_object_state->descriptor_heap_embedded_samplers_count;
        }

        Add(std::move(shader_object_state));
    }
}

void DeviceState::PostCallRecordCopyAccelerationStructureToMemoryKHR(VkDevice device, VkDeferredOperationKHR deferredOperation,
                                                                     const VkCopyAccelerationStructureToMemoryInfoKHR* pInfo,
                                                                     const RecordObject& record_obj) {
    // Might be deferred
    if (record_obj.result < VK_SUCCESS) {
        return;
    }
    // Record the mapping from serialized host address to original AS handle,
    // so that when the data is later deserialized, we can copy the build info.
    if (pInfo->mode == VK_COPY_ACCELERATION_STRUCTURE_MODE_SERIALIZE_KHR && pInfo->dst.hostAddress != nullptr) {
        WriteLockGuard guard(serialized_as_map_lock_);
        serialized_as_map_[pInfo->dst.hostAddress] = pInfo->src;
    }
}

void DeviceState::PostCallRecordCopyMemoryToAccelerationStructureKHR(VkDevice device, VkDeferredOperationKHR deferredOperation,
                                                                     const VkCopyMemoryToAccelerationStructureInfoKHR* pInfo,
                                                                     const RecordObject& record_obj) {
    // Might be deferred
    if (record_obj.result < VK_SUCCESS) {
        return;
    }
    auto dst_as_state = Get<AccelerationStructureKHR>(pInfo->dst);
    ASSERT_AND_RETURN(dst_as_state);
    if (pInfo->mode == VK_COPY_ACCELERATION_STRUCTURE_MODE_DESERIALIZE_KHR) {
        dst_as_state->MarkAsDeserialized();
    }
}

void DeviceState::PostCallRecordCmdCopyAccelerationStructureKHR(VkCommandBuffer commandBuffer,
                                                                const VkCopyAccelerationStructureInfoKHR* pInfo,
                                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);

    cb_state->RecordCommand(record_obj.location);
    auto src_as_state = Get<AccelerationStructureKHR>(pInfo->src);
    auto dst_as_state = Get<AccelerationStructureKHR>(pInfo->dst);
    if (dst_as_state && src_as_state) {
        if (!disabled[command_buffer_state]) {
            cb_state->AddChild(dst_as_state);
            cb_state->AddChild(src_as_state);
        }
    }
}

void DeviceState::PostCallRecordCmdCopyAccelerationStructureToMemoryKHR(VkCommandBuffer commandBuffer,
                                                                        const VkCopyAccelerationStructureToMemoryInfoKHR* pInfo,
                                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    auto src_as_state = Get<AccelerationStructureKHR>(pInfo->src);
    if (!disabled[command_buffer_state]) {
        cb_state->AddChild(src_as_state);
    }
    // Issue https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/6461
    // showed that it is incorrect to try to add buffers obtained through a call to GetBuffersByAddress as children to a command
    // buffer
}

void DeviceState::PostCallRecordCmdCopyMemoryToAccelerationStructureKHR(VkCommandBuffer commandBuffer,
                                                                        const VkCopyMemoryToAccelerationStructureInfoKHR* pInfo,
                                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);

    auto dst_as_state = Get<AccelerationStructureKHR>(pInfo->dst);
    ASSERT_AND_RETURN(dst_as_state);

    if (pInfo->mode == VK_COPY_ACCELERATION_STRUCTURE_MODE_DESERIALIZE_KHR) {
        dst_as_state->MarkAsDeserialized();
    }

    if (!disabled[command_buffer_state]) {
        cb_state->AddChild(dst_as_state);

        // Issue https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/6461
        // showed that it is incorrect to try to add buffers obtained through a call to GetBuffersByAddress as children to a
        // command buffer
    }
}

void DeviceState::PostCallRecordCmdCopyMemoryIndirectKHR(VkCommandBuffer commandBuffer,
                                                         const VkCopyMemoryIndirectInfoKHR* pCopyMemoryIndirectInfo,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCopyMemoryIndirect(*pCopyMemoryIndirectInfo, record_obj.location);
    TrackDeviceAddressRange(*cb_state, pCopyMemoryIndirectInfo->copyAddressRange.address,
                            pCopyMemoryIndirectInfo->copyAddressRange.size, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdCopyMemoryToImageIndirectKHR(
    VkCommandBuffer commandBuffer, const VkCopyMemoryToImageIndirectInfoKHR* pCopyMemoryToImageIndirectInfo,
    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCopyMemoryToImageIndirect(*pCopyMemoryToImageIndirectInfo, record_obj.location);
    TrackDeviceAddressRange(*cb_state, pCopyMemoryToImageIndirectInfo->copyAddressRange.address,
                            pCopyMemoryToImageIndirectInfo->copyAddressRange.size, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
}

void DeviceState::PostCallRecordCmdSetCullModeEXT(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode,
                                                  const RecordObject& record_obj) {
    PostCallRecordCmdSetCullMode(commandBuffer, cullMode, record_obj);
}

void DeviceState::PostCallRecordCmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode,
                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_CULL_MODE);
    cb_state->dynamic_state_value.cull_mode = cullMode;
}

void DeviceState::PostCallRecordCmdSetFrontFaceEXT(VkCommandBuffer commandBuffer, VkFrontFace frontFace,
                                                   const RecordObject& record_obj) {
    PostCallRecordCmdSetFrontFace(commandBuffer, frontFace, record_obj);
}

void DeviceState::PostCallRecordCmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace,
                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_FRONT_FACE);
}

void DeviceState::PostCallRecordCmdSetPrimitiveTopologyEXT(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology,
                                                           const RecordObject& record_obj) {
    PostCallRecordCmdSetPrimitiveTopology(commandBuffer, primitiveTopology, record_obj);
}

void DeviceState::PostCallRecordCmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology,
                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY);
    cb_state->dynamic_state_value.primitive_topology = primitiveTopology;
}

void DeviceState::PostCallRecordCmdSetViewportWithCountEXT(VkCommandBuffer commandBuffer, uint32_t viewportCount,
                                                           const VkViewport* pViewports, const RecordObject& record_obj) {
    PostCallRecordCmdSetViewportWithCount(commandBuffer, viewportCount, pViewports, record_obj);
}

void DeviceState::PostCallRecordCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount,
                                                        const VkViewport* pViewports, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetViewportWithCount(viewportCount, pViewports);
}

void DeviceState::PostCallRecordCmdSetScissorWithCountEXT(VkCommandBuffer commandBuffer, uint32_t scissorCount,
                                                          const VkRect2D* pScissors, const RecordObject& record_obj) {
    PostCallRecordCmdSetScissorWithCount(commandBuffer, scissorCount, pScissors, record_obj);
}

void DeviceState::PostCallRecordCmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount,
                                                       const VkRect2D* pScissors, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetScissorWithCount(scissorCount);
}

void DeviceState::PostCallRecordCmdBindVertexBuffers2EXT(VkCommandBuffer commandBuffer, uint32_t firstBinding,
                                                         uint32_t bindingCount, const VkBuffer* pBuffers,
                                                         const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes,
                                                         const VkDeviceSize* pStrides, const RecordObject& record_obj) {
    PostCallRecordCmdBindVertexBuffers2(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets, pSizes, pStrides,
                                        record_obj);
}

void DeviceState::PostCallRecordCmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount,
                                                      const VkBuffer* pBuffers, const VkDeviceSize* pOffsets,
                                                      const VkDeviceSize* pSizes, const VkDeviceSize* pStrides,
                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    if (pStrides) {
        cb_state->RecordStateCmd(CB_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE);
        cb_state->stride_set_with_bind_vertex_buffer_3 = false;
    }
    cb_state->bind_vertex_buffer_3_used = false;

    for (uint32_t i = 0; i < bindingCount; ++i) {
        auto buffer_state = Get<vvl::Buffer>(pBuffers[i]);

        const VkDeviceSize* stride_ptr = pStrides ? &pStrides[i] : nullptr;
        const VkDeviceSize effective_size = GetBufferRegionSize(buffer_state, pOffsets[i], pSizes ? pSizes[i] : VK_WHOLE_SIZE);

        cb_state->current_vertex_buffer_binding_info[i + firstBinding].Set(pBuffers[i], effective_size, pOffsets[i], stride_ptr);

        // Add binding for this vertex buffer to this commandbuffer
        if (!disabled[command_buffer_state] && pBuffers[i]) {
            cb_state->AddChild(buffer_state);
        }
    }
}

void DeviceState::PostCallRecordCmdBindVertexBuffers3KHR(VkCommandBuffer commandBuffer, uint32_t firstBinding,
                                                         uint32_t bindingCount, const VkBindVertexBuffer3InfoKHR* pBindingInfos,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->bind_vertex_buffer_3_used = true;

    for (uint32_t i = 0; i < bindingCount; ++i) {
        const VkBindVertexBuffer3InfoKHR& binding_info = pBindingInfos[i];

        const VkDeviceSize* stride_ptr = binding_info.setStride ? &binding_info.addressRange.stride : nullptr;
        cb_state->current_vertex_buffer_binding_info[i + firstBinding].Set(binding_info.addressRange.address,
                                                                           binding_info.addressRange.size, stride_ptr);

        TrackDeviceAddressRange(*cb_state, binding_info.addressRange.address, binding_info.addressRange.size,
                                VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);

        // https://gitlab.khronos.org/vulkan/vulkan/-/issues/3381
        // The stride is "last to set it wins"
        if (binding_info.setStride) {
            cb_state->stride_set_with_bind_vertex_buffer_3 = true;
        }
    }
    if (cb_state->stride_set_with_bind_vertex_buffer_3) {
        cb_state->RecordStateCmd(CB_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE);
    }
}

void DeviceState::PostCallRecordCmdSetDepthTestEnableEXT(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable,
                                                         const RecordObject& record_obj) {
    PostCallRecordCmdSetDepthTestEnable(commandBuffer, depthTestEnable, record_obj);
}

void DeviceState::PostCallRecordCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable,
                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetDepthTestEnable(depthTestEnable);
}

void DeviceState::PostCallRecordCmdSetDepthWriteEnableEXT(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable,
                                                          const RecordObject& record_obj) {
    PostCallRecordCmdSetDepthWriteEnable(commandBuffer, depthWriteEnable, record_obj);
}

void DeviceState::PostCallRecordCmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable,
                                                       const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_WRITE_ENABLE);
    cb_state->dynamic_state_value.depth_write_enable = depthWriteEnable;
}

void DeviceState::PostCallRecordCmdSetDepthCompareOpEXT(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp,
                                                        const RecordObject& record_obj) {
    PostCallRecordCmdSetDepthCompareOp(commandBuffer, depthCompareOp, record_obj);
}

void DeviceState::PostCallRecordCmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp,
                                                     const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetDepthCompareOp(depthCompareOp);
}

void DeviceState::PostCallRecordCmdSetDepthBoundsTestEnableEXT(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable,
                                                               const RecordObject& record_obj) {
    PostCallRecordCmdSetDepthBoundsTestEnable(commandBuffer, depthBoundsTestEnable, record_obj);
}

void DeviceState::PostCallRecordCmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable,
                                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE);
    cb_state->dynamic_state_value.depth_bounds_test_enable = depthBoundsTestEnable;
}

void DeviceState::PostCallRecordCmdSetStencilTestEnableEXT(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable,
                                                           const RecordObject& record_obj) {
    PostCallRecordCmdSetStencilTestEnable(commandBuffer, stencilTestEnable, record_obj);
}

void DeviceState::PostCallRecordCmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable,
                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_STENCIL_TEST_ENABLE);
    cb_state->dynamic_state_value.stencil_test_enable = stencilTestEnable;
}

void DeviceState::PostCallRecordCmdSetStencilOpEXT(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp,
                                                   VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp,
                                                   const RecordObject& record_obj) {
    PostCallRecordCmdSetStencilOp(commandBuffer, faceMask, failOp, passOp, depthFailOp, compareOp, record_obj);
}

void DeviceState::PostCallRecordCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp,
                                                VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp,
                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_STENCIL_OP);
    if (faceMask == VK_STENCIL_FACE_FRONT_BIT || faceMask == VK_STENCIL_FACE_FRONT_AND_BACK) {
        cb_state->dynamic_state_value.fail_op_front = failOp;
        cb_state->dynamic_state_value.pass_op_front = passOp;
        cb_state->dynamic_state_value.depth_fail_op_front = depthFailOp;
    }
    if (faceMask == VK_STENCIL_FACE_BACK_BIT || faceMask == VK_STENCIL_FACE_FRONT_AND_BACK) {
        cb_state->dynamic_state_value.fail_op_back = failOp;
        cb_state->dynamic_state_value.pass_op_back = passOp;
        cb_state->dynamic_state_value.depth_fail_op_back = depthFailOp;
    }
}

void DeviceState::PostCallRecordCmdSetDiscardRectangleEXT(VkCommandBuffer commandBuffer, uint32_t firstDiscardRectangle,
                                                          uint32_t discardRectangleCount, const VkRect2D* pDiscardRectangles,
                                                          const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DISCARD_RECTANGLE_EXT);
    for (uint32_t i = 0; i < discardRectangleCount; i++) {
        cb_state->dynamic_state_value.discard_rectangles.set(firstDiscardRectangle + i);
    }
}

void DeviceState::PostCallRecordCmdSetDiscardRectangleEnableEXT(VkCommandBuffer commandBuffer, VkBool32 discardRectangleEnable,
                                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DISCARD_RECTANGLE_ENABLE_EXT);
    cb_state->dynamic_state_value.discard_rectangle_enable = discardRectangleEnable;
}

void DeviceState::PostCallRecordCmdSetDiscardRectangleModeEXT(VkCommandBuffer commandBuffer,
                                                              VkDiscardRectangleModeEXT discardRectangleMode,
                                                              const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DISCARD_RECTANGLE_MODE_EXT);
}

void DeviceState::PostCallRecordCmdSetSampleLocationsEXT(VkCommandBuffer commandBuffer,
                                                         const VkSampleLocationsInfoEXT* pSampleLocationsInfo,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT);
    cb_state->dynamic_state_value.sample_locations_info = *pSampleLocationsInfo;
}

void DeviceState::PostCallRecordCmdSetCoarseSampleOrderNV(VkCommandBuffer commandBuffer, VkCoarseSampleOrderTypeNV sampleOrderType,
                                                          uint32_t customSampleOrderCount,
                                                          const VkCoarseSampleOrderCustomNV* pCustomSampleOrders,
                                                          const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_VIEWPORT_COARSE_SAMPLE_ORDER_NV);
}

void DeviceState::PostCallRecordCmdSetPatchControlPointsEXT(VkCommandBuffer commandBuffer, uint32_t patchControlPoints,
                                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT);
}

void DeviceState::PostCallRecordCmdSetLogicOpEXT(VkCommandBuffer commandBuffer, VkLogicOp logicOp, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_LOGIC_OP_EXT);
}

void DeviceState::PostCallRecordCmdSetRasterizerDiscardEnableEXT(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable,
                                                                 const RecordObject& record_obj) {
    PostCallRecordCmdSetRasterizerDiscardEnable(commandBuffer, rasterizerDiscardEnable, record_obj);
}

void DeviceState::PostCallRecordCmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable,
                                                              const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE);
    cb_state->dynamic_state_value.rasterizer_discard_enable = (rasterizerDiscardEnable == VK_TRUE);
}

void DeviceState::PostCallRecordCmdSetDepthBiasEnableEXT(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable,
                                                         const RecordObject& record_obj) {
    PostCallRecordCmdSetDepthBiasEnable(commandBuffer, depthBiasEnable, record_obj);
}

void DeviceState::PostCallRecordCmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable,
                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_BIAS_ENABLE);
    cb_state->dynamic_state_value.depth_bias_enable = depthBiasEnable;
}

void DeviceState::PostCallRecordCmdSetPrimitiveRestartEnableEXT(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable,
                                                                const RecordObject& record_obj) {
    PostCallRecordCmdSetPrimitiveRestartEnable(commandBuffer, primitiveRestartEnable, record_obj);
}

void DeviceState::PostCallRecordCmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable,
                                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE);
    cb_state->dynamic_state_value.primitive_restart_enable = primitiveRestartEnable;
}

void DeviceState::PostCallRecordCmdSetFragmentShadingRateKHR(VkCommandBuffer commandBuffer, const VkExtent2D* pFragmentSize,
                                                             const VkFragmentShadingRateCombinerOpKHR combinerOps[2],
                                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR);
    cb_state->dynamic_state_value.fragment_size = *pFragmentSize;
}

void DeviceState::PostCallRecordCmdSetRenderingAttachmentLocations(VkCommandBuffer commandBuffer,
                                                                   const VkRenderingAttachmentLocationInfo* pLocationInfo,
                                                                   const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetRenderingAttachmentLocations(pLocationInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdSetRenderingAttachmentLocationsKHR(VkCommandBuffer commandBuffer,
                                                                      const VkRenderingAttachmentLocationInfoKHR* pLocationInfo,
                                                                      const RecordObject& record_obj) {
    PostCallRecordCmdSetRenderingAttachmentLocations(commandBuffer, pLocationInfo, record_obj);
}

void DeviceState::PostCallRecordCmdSetRenderingInputAttachmentIndices(VkCommandBuffer commandBuffer,
                                                                      const VkRenderingInputAttachmentIndexInfo* pLocationInfo,
                                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetRenderingInputAttachmentIndices(pLocationInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdSetRenderingInputAttachmentIndicesKHR(
    VkCommandBuffer commandBuffer, const VkRenderingInputAttachmentIndexInfoKHR* pLocationInfo, const RecordObject& record_obj) {
    PostCallRecordCmdSetRenderingInputAttachmentIndices(commandBuffer, pLocationInfo, record_obj);
}

void DeviceState::PostCallRecordCmdSetRayTracingPipelineStackSizeKHR(VkCommandBuffer commandBuffer, uint32_t pipelineStackSize,
                                                                     const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordCommand(record_obj.location);
    // CB_DYNAMIC_STATE_RAY_TRACING_PIPELINE_STACK_SIZE_KHR);
    cb_state->dynamic_state_status.rtx_stack_size_cb = true;
    cb_state->dynamic_state_status.rtx_stack_size_pipeline = true;
}

void DeviceState::PostCallRecordCmdSetVertexInputEXT(VkCommandBuffer commandBuffer, uint32_t vertexBindingDescriptionCount,
                                                     const VkVertexInputBindingDescription2EXT* pVertexBindingDescriptions,
                                                     uint32_t vertexAttributeDescriptionCount,
                                                     const VkVertexInputAttributeDescription2EXT* pVertexAttributeDescriptions,
                                                     const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT);
    cb_state->stride_set_with_bind_vertex_buffer_3 = false;

    const auto pipeline_state = cb_state->GetLastBoundGraphics().pipeline_state;
    if (pipeline_state && pipeline_state->IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE)) {
        cb_state->RecordDynamicState(CB_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE);
    }
    auto& vertex_bindings = cb_state->dynamic_state_value.vertex_bindings;

    // When using Dynamic state, anything not set is invalid, so need to reset map
    // "The vertex attribute description for any location not specified in the pVertexAttributeDescriptions array becomes undefined"
    vertex_bindings.clear();

    for (const auto [i, description] : vvl::enumerate(pVertexBindingDescriptions, vertexBindingDescriptionCount)) {
        vertex_bindings.insert_or_assign(description.binding, VertexBindingState(i, &description));

        cb_state->current_vertex_buffer_binding_info[description.binding].stride = description.stride;
    }

    for (const auto [i, description] : vvl::enumerate(pVertexAttributeDescriptions, vertexAttributeDescriptionCount)) {
        if (auto* binding_state = vvl::Find(vertex_bindings, description.binding)) {
            binding_state->locations.insert_or_assign(description.location, VertexAttrState(i, &description));
        }
    }
}

void DeviceState::PostCallRecordCmdSetColorWriteEnableEXT(VkCommandBuffer commandBuffer, uint32_t attachmentCount,
                                                          const VkBool32* pColorWriteEnables, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT);
    cb_state->dynamic_state_value.color_write_enable_attachment_count = attachmentCount;
    for (uint32_t i = 0; i < attachmentCount; ++i) {
        if (pColorWriteEnables[i]) {
            cb_state->dynamic_state_value.color_write_enabled.set(i);
        } else {
            cb_state->dynamic_state_value.color_write_enabled.reset(i);
        }
    }
}

void DeviceState::PostCallRecordCmdSetAttachmentFeedbackLoopEnableEXT(VkCommandBuffer commandBuffer, VkImageAspectFlags aspectMask,
                                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_ATTACHMENT_FEEDBACK_LOOP_ENABLE_EXT);
    cb_state->dynamic_state_value.attachment_feedback_loop_enable = aspectMask;
}

void DeviceState::PostCallRecordCmdSetTessellationDomainOriginEXT(VkCommandBuffer commandBuffer,
                                                                  VkTessellationDomainOrigin domainOrigin,
                                                                  const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_TESSELLATION_DOMAIN_ORIGIN_EXT);
}

void DeviceState::PostCallRecordCmdSetDepthClampEnableEXT(VkCommandBuffer commandBuffer, VkBool32 depthClampEnable,
                                                          const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT);
    cb_state->dynamic_state_value.depth_clamp_enable = depthClampEnable;
}

void DeviceState::PostCallRecordCmdSetDepthClampRangeEXT(VkCommandBuffer commandBuffer, VkDepthClampModeEXT depthClampMode,
                                                         const VkDepthClampRangeEXT* pDepthClampRange,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_CLAMP_RANGE_EXT);
}

void DeviceState::PostCallRecordCmdSetPolygonModeEXT(VkCommandBuffer commandBuffer, VkPolygonMode polygonMode,
                                                     const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_POLYGON_MODE_EXT);
    cb_state->dynamic_state_value.polygon_mode = polygonMode;
}

void DeviceState::PostCallRecordCmdSetRasterizationSamplesEXT(VkCommandBuffer commandBuffer,
                                                              VkSampleCountFlagBits rasterizationSamples,
                                                              const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT);
    cb_state->dynamic_state_value.rasterization_samples = rasterizationSamples;
}

void DeviceState::PostCallRecordCmdSetSampleMaskEXT(VkCommandBuffer commandBuffer, VkSampleCountFlagBits samples,
                                                    const VkSampleMask* pSampleMask, const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_SAMPLE_MASK_EXT);
    cb_state->dynamic_state_value.samples_mask_samples = samples;
    // Note from the spec, if the need to record pSampleMask rises:
    // If the maintenance10 feature is enabled, and this (pSampleMask) parameter is set to NULL,
    // it is treated as if the mask has all bits set to 1.
}

void DeviceState::PostCallRecordCmdSetAlphaToCoverageEnableEXT(VkCommandBuffer commandBuffer, VkBool32 alphaToCoverageEnable,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT);
    cb_state->dynamic_state_value.alpha_to_coverage_enable = alphaToCoverageEnable;
}

void DeviceState::PostCallRecordCmdSetAlphaToOneEnableEXT(VkCommandBuffer commandBuffer, VkBool32 alphaToOneEnable,
                                                          const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT);
    cb_state->dynamic_state_value.alpha_to_one_enable = alphaToOneEnable;
}

void DeviceState::PostCallRecordCmdSetLogicOpEnableEXT(VkCommandBuffer commandBuffer, VkBool32 logicOpEnable,
                                                       const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT);
    cb_state->dynamic_state_value.logic_op_enable = logicOpEnable;
}

void DeviceState::PostCallRecordCmdSetColorBlendEnableEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment,
                                                          uint32_t attachmentCount, const VkBool32* pColorBlendEnables,
                                                          const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT);
    for (uint32_t i = 0; i < attachmentCount; i++) {
        cb_state->dynamic_state_value.color_blend_enable_attachments.set(firstAttachment + i);
        if (pColorBlendEnables[i]) {
            cb_state->dynamic_state_value.color_blend_enabled.set(firstAttachment + i);
        } else {
            cb_state->dynamic_state_value.color_blend_enabled.reset(firstAttachment + i);
        }
    }
}

void DeviceState::PostCallRecordCmdSetColorBlendEquationEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment,
                                                            uint32_t attachmentCount,
                                                            const VkColorBlendEquationEXT* pColorBlendEquations,
                                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT);
    if (cb_state->dynamic_state_value.color_blend_equations.size() < firstAttachment + attachmentCount) {
        cb_state->dynamic_state_value.color_blend_equations.resize(firstAttachment + attachmentCount);
    }
    for (uint32_t i = 0; i < attachmentCount; i++) {
        cb_state->dynamic_state_value.color_blend_equation_attachments.set(firstAttachment + i);
        cb_state->dynamic_state_value.color_blend_equations[firstAttachment + i] = pColorBlendEquations[i];
    }
}

void DeviceState::PostCallRecordCmdSetColorWriteMaskEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment,
                                                        uint32_t attachmentCount, const VkColorComponentFlags* pColorWriteMasks,
                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT);
    if (cb_state->dynamic_state_value.color_write_masks.size() < firstAttachment + attachmentCount) {
        cb_state->dynamic_state_value.color_write_masks.resize(firstAttachment + attachmentCount);
    }
    for (uint32_t i = 0; i < attachmentCount; i++) {
        cb_state->dynamic_state_value.color_write_mask_attachments.set(firstAttachment + i);
        cb_state->dynamic_state_value.color_write_masks[i] = pColorWriteMasks[i];
    }
}

void DeviceState::PostCallRecordCmdSetRasterizationStreamEXT(VkCommandBuffer commandBuffer, uint32_t rasterizationStream,
                                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_RASTERIZATION_STREAM_EXT);
    cb_state->dynamic_state_value.rasterization_stream = rasterizationStream;
}

void DeviceState::PostCallRecordCmdSetConservativeRasterizationModeEXT(
    VkCommandBuffer commandBuffer, VkConservativeRasterizationModeEXT conservativeRasterizationMode,
    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT);
    cb_state->dynamic_state_value.conservative_rasterization_mode = conservativeRasterizationMode;
}

void DeviceState::PostCallRecordCmdSetExtraPrimitiveOverestimationSizeEXT(VkCommandBuffer commandBuffer,
                                                                          float extraPrimitiveOverestimationSize,
                                                                          const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_EXTRA_PRIMITIVE_OVERESTIMATION_SIZE_EXT);
}

void DeviceState::PostCallRecordCmdSetDepthClipEnableEXT(VkCommandBuffer commandBuffer, VkBool32 depthClipEnable,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT);
}

void DeviceState::PostCallRecordCmdSetSampleLocationsEnableEXT(VkCommandBuffer commandBuffer, VkBool32 sampleLocationsEnable,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT);
    cb_state->dynamic_state_value.sample_locations_enable = sampleLocationsEnable;
}

void DeviceState::PostCallRecordCmdSetColorBlendAdvancedEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment,
                                                            uint32_t attachmentCount,
                                                            const VkColorBlendAdvancedEXT* pColorBlendAdvanced,
                                                            const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT);
    for (uint32_t i = 0; i < attachmentCount; i++) {
        cb_state->dynamic_state_value.color_blend_advanced_attachments.set(firstAttachment + i);
    }
}

void DeviceState::PostCallRecordCmdSetProvokingVertexModeEXT(VkCommandBuffer commandBuffer,
                                                             VkProvokingVertexModeEXT provokingVertexMode,
                                                             const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT);
}

void DeviceState::PostCallRecordCmdSetLineRasterizationModeEXT(VkCommandBuffer commandBuffer,
                                                               VkLineRasterizationModeEXT lineRasterizationMode,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT);
    cb_state->dynamic_state_value.line_rasterization_mode = lineRasterizationMode;
}

void DeviceState::PostCallRecordCmdSetLineStippleEnableEXT(VkCommandBuffer commandBuffer, VkBool32 stippledLineEnable,
                                                           const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT);
    cb_state->dynamic_state_value.stippled_line_enable = stippledLineEnable;
}

void DeviceState::PostCallRecordCmdSetDepthClipNegativeOneToOneEXT(VkCommandBuffer commandBuffer, VkBool32 negativeOneToOne,
                                                                   const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_CLIP_NEGATIVE_ONE_TO_ONE_EXT);
}

void DeviceState::PostCallRecordCmdSetViewportWScalingEnableNV(VkCommandBuffer commandBuffer, VkBool32 viewportWScalingEnable,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_VIEWPORT_W_SCALING_ENABLE_NV);
    cb_state->dynamic_state_value.viewport_w_scaling_enable = viewportWScalingEnable;
}

void DeviceState::PostCallRecordCmdSetViewportSwizzleNV(VkCommandBuffer commandBuffer, uint32_t firstViewport,
                                                        uint32_t viewportCount, const VkViewportSwizzleNV* pViewportSwizzles,
                                                        const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_VIEWPORT_SWIZZLE_NV);
    cb_state->dynamic_state_value.viewport_swizzle_count = viewportCount;
}

void DeviceState::PostCallRecordCmdSetCoverageToColorEnableNV(VkCommandBuffer commandBuffer, VkBool32 coverageToColorEnable,
                                                              const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COVERAGE_TO_COLOR_ENABLE_NV);
    cb_state->dynamic_state_value.coverage_to_color_enable = coverageToColorEnable;
}

void DeviceState::PostCallRecordCmdSetCoverageToColorLocationNV(VkCommandBuffer commandBuffer, uint32_t coverageToColorLocation,
                                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COVERAGE_TO_COLOR_LOCATION_NV);
    cb_state->dynamic_state_value.coverage_to_color_location = coverageToColorLocation;
}

void DeviceState::PostCallRecordCmdSetCoverageModulationModeNV(VkCommandBuffer commandBuffer,
                                                               VkCoverageModulationModeNV coverageModulationMode,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COVERAGE_MODULATION_MODE_NV);
    cb_state->dynamic_state_value.coverage_modulation_mode = coverageModulationMode;
}

void DeviceState::PostCallRecordCmdSetCoverageModulationTableEnableNV(VkCommandBuffer commandBuffer,
                                                                      VkBool32 coverageModulationTableEnable,
                                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_ENABLE_NV);
    cb_state->dynamic_state_value.coverage_modulation_table_enable = coverageModulationTableEnable;
}

void DeviceState::PostCallRecordCmdSetCoverageModulationTableNV(VkCommandBuffer commandBuffer,
                                                                uint32_t coverageModulationTableCount,
                                                                const float* pCoverageModulationTable,
                                                                const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_NV);
}

void DeviceState::PostCallRecordCmdSetShadingRateImageEnableNV(VkCommandBuffer commandBuffer, VkBool32 shadingRateImageEnable,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_SHADING_RATE_IMAGE_ENABLE_NV);
    cb_state->dynamic_state_value.shading_rate_image_enable = shadingRateImageEnable;
}

void DeviceState::PostCallRecordCmdSetRepresentativeFragmentTestEnableNV(VkCommandBuffer commandBuffer,
                                                                         VkBool32 representativeFragmentTestEnable,
                                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_REPRESENTATIVE_FRAGMENT_TEST_ENABLE_NV);
}

void DeviceState::PostCallRecordCmdSetCoverageReductionModeNV(VkCommandBuffer commandBuffer,
                                                              VkCoverageReductionModeNV coverageReductionMode,
                                                              const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordStateCmd(CB_DYNAMIC_STATE_COVERAGE_REDUCTION_MODE_NV);
}

void DeviceState::PostCallRecordCmdControlVideoCodingKHR(VkCommandBuffer commandBuffer,
                                                         const VkVideoCodingControlInfoKHR* pCodingControlInfo,
                                                         const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordControlVideoCoding(*pCodingControlInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdDecodeVideoKHR(VkCommandBuffer commandBuffer, const VkVideoDecodeInfoKHR* pDecodeInfo,
                                                  const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordDecodeVideo(*pDecodeInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdEncodeVideoKHR(VkCommandBuffer commandBuffer, const VkVideoEncodeInfoKHR* pEncodeInfo,
                                                  const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordEncodeVideo(*pEncodeInfo, record_obj.location);
}

void DeviceState::PostCallRecordGetShaderModuleIdentifierEXT(VkDevice, const VkShaderModule shaderModule,
                                                             VkShaderModuleIdentifierEXT* pIdentifier,
                                                             const RecordObject& record_obj) {
    if (const auto shader_state = Get<ShaderModule>(shaderModule); shader_state) {
        WriteLockGuard guard(shader_identifier_map_lock_);
        shader_identifier_map_.emplace(*pIdentifier, std::move(shader_state));
    }
}

void DeviceState::PostCallRecordGetShaderModuleCreateInfoIdentifierEXT(VkDevice, const VkShaderModuleCreateInfo* pCreateInfo,
                                                                       VkShaderModuleIdentifierEXT* pIdentifier,
                                                                       const RecordObject& record_obj) {
    WriteLockGuard guard(shader_identifier_map_lock_);
    shader_identifier_map_.emplace(*pIdentifier, std::make_shared<ShaderModule>());
}

void DeviceState::PostCallRecordGetBufferDeviceAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo,
                                                       const RecordObject& record_obj) {
    if (record_obj.device_address == 0) {
        return;
    }
    if (auto buffer_state = Get<Buffer>(pInfo->buffer)) {
        WriteLockGuard guard(buffer_address_lock_);
        // address is used for GPU-AV and ray tracing buffer validation
        buffer_state->deviceAddress = record_obj.device_address;
        const vvl::range<VkDeviceAddress> address_range = buffer_state->DeviceAddressRange();

        BufferAddressInfillUpdateOps ops{{buffer_state.get()}};
        sparse_container::infill_update_range(buffer_address_map_, address_range, ops);
        buffer_device_address_ranges_version++;
    }
}

void DeviceState::PostCallRecordGetBufferDeviceAddressKHR(VkDevice device, const VkBufferDeviceAddressInfo* pInfo,
                                                          const RecordObject& record_obj) {
    PostCallRecordGetBufferDeviceAddress(device, pInfo, record_obj);
}

void DeviceState::PostCallRecordGetBufferDeviceAddressEXT(VkDevice device, const VkBufferDeviceAddressInfo* pInfo,
                                                          const RecordObject& record_obj) {
    PostCallRecordGetBufferDeviceAddress(device, pInfo, record_obj);
}

std::shared_ptr<Swapchain> DeviceState::CreateSwapchainState(const VkSwapchainCreateInfoKHR* create_info, VkSwapchainKHR handle) {
    return std::make_shared<Swapchain>(*this, create_info, handle);
}

std::shared_ptr<CommandBuffer> DeviceState::CreateCmdBufferState(VkCommandBuffer handle,
                                                                 const VkCommandBufferAllocateInfo* allocate_info,
                                                                 const CommandPool& pool) {
    return std::make_shared<CommandBuffer>(*this, handle, allocate_info, pool);
}

std::shared_ptr<DeviceMemory> DeviceState::CreateDeviceMemoryState(VkDeviceMemory handle, const VkMemoryAllocateInfo* allocate_info,
                                                                   uint64_t fake_address, const VkMemoryType& memory_type,
                                                                   const VkMemoryHeap& memory_heap,
                                                                   VulkanTypedHandle dedicated_binding,
                                                                   uint32_t physical_device_count) {
    return std::make_shared<DeviceMemory>(handle, allocate_info, fake_address, memory_type, memory_heap, dedicated_binding,
                                          physical_device_count);
}

void DeviceState::PostCallRecordCmdSetPrimitiveRestartIndexEXT(VkCommandBuffer commandBuffer, uint32_t primitiveRestartIndex,
                                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordSetPrimitiveRestartIndex(primitiveRestartIndex, record_obj.location);
}

void DeviceState::PostCallRecordCmdBindTransformFeedbackBuffersEXT(VkCommandBuffer commandBuffer, uint32_t firstBinding,
                                                                   uint32_t bindingCount, const VkBuffer* pBuffers,
                                                                   const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes,
                                                                   const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->transform_feedback_buffers_bound = bindingCount;
}

void DeviceState::PostCallRecordCmdBindTransformFeedbackBuffers2EXT(VkCommandBuffer commandBuffer, uint32_t firstBinding,
                                                                    uint32_t bindingCount,
                                                                    const VkBindTransformFeedbackBuffer2InfoEXT* pBindingInfos,
                                                                    const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->transform_feedback_buffers_bound = bindingCount;
    for (uint32_t i = 0; i < bindingCount; ++i) {
        TrackDeviceAddressRange(*cb_state, pBindingInfos[i].addressRange.address, pBindingInfos[i].addressRange.size,
                                VK_BUFFER_USAGE_2_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT);
    }
}

void DeviceState::PostCallRecordGetAccelerationStructureDeviceAddressKHR(VkDevice device,
                                                                         const VkAccelerationStructureDeviceAddressInfoKHR* pInfo,
                                                                         const RecordObject& record_obj) {
    if (!pInfo) {
        return;
    }
    if (record_obj.device_address == 0) {
        return;
    }
    if (auto as_state = Get<vvl::AccelerationStructureKHR>(pInfo->accelerationStructure)) {
        if (as_state->GetAccelerationStructureAddress() != 0) {
            return;
        }
        as_state->SetAccelerationStructureAddress(record_obj.device_address);
        WriteLockGuard lock(as_with_addresses.array_mutex);
        if (as_with_addresses.array.capacity() <= (as_with_addresses.array.size() + 1)) {
            as_with_addresses.array.reserve(as_with_addresses.array.capacity() * 2);
        }
        as_with_addresses.array.emplace_back(as_state.get());
    }
}

NearestBufferResult DeviceState::GetNearestBuffersByAddress(VkDeviceAddress address) const {
    ReadLockGuard guard(buffer_address_lock_);

    NearestBufferResult result = {vvl::range<VkDeviceAddress>(), vvl::range<VkDeviceAddress>(),
                                  vvl::make_span<vvl::Buffer* const>(nullptr, static_cast<size_t>(0)),
                                  vvl::make_span<vvl::Buffer* const>(nullptr, static_cast<size_t>(0))};

    if (buffer_address_map_.empty()) {
        return result;
    }

    // lower_bound returns the first range that ends *after* the address.
    // Since we assume 'find' has already failed (address is in a gap),
    // this will point to the nearest range *above* the requested address.
    const auto range_key = vvl::range<VkDeviceAddress>(address, address + 1);
    auto it = buffer_address_map_.lower_bound(range_key);

    if (it != buffer_address_map_.end()) {
        result.above_range = it->first;
        result.above_buffers = it->second;
    }

    // If at the beginning, there is nothing below.
    // Otherwise, the element immediately preceding the lower_bound
    // is the nearest range below the address.
    if (it != buffer_address_map_.begin()) {
        auto prev = it;
        --prev;
        result.below_range = prev->first;
        result.below_buffers = prev->second;
    }

    return result;
}

void DeviceState::GetBufferAddressRanges(BufferAddressRange* ranges) const {
    ReadLockGuard guard(buffer_address_lock_);

    size_t written_count = 0;
    for (const auto& [address_range, buffers] : buffer_address_map_) {
        ranges[written_count++] = address_range;
    }
}

small_vector<const vvl::AccelerationStructureKHR*, 2> DeviceState::GetAccelerationStructuresByAddress(
    VkDeviceAddress address) const {
    small_vector<const vvl::AccelerationStructureKHR*, 2> as_vector;
    if (address == 0) {
        return as_vector;
    }

    ReadLockGuard lock(as_with_addresses.array_mutex);
    auto as_found_it =
        std::find_if(as_with_addresses.array.begin(), as_with_addresses.array.end(),
                     [address](vvl::AccelerationStructureKHR* as) { return as->GetAccelerationStructureAddress() == address; });

    while (as_found_it != as_with_addresses.array.end()) {
        as_vector.emplace_back(*as_found_it);
        as_found_it = std::find_if(as_found_it + 1, as_with_addresses.array.end(), [address](vvl::AccelerationStructureKHR* as) {
            return as->GetAccelerationStructureAddress() == address;
        });
    }
    return as_vector;
}

void DeviceState::PreCallRecordLatencySleepNV(VkDevice device, VkSwapchainKHR swapchain, const VkLatencySleepInfoNV* pSleepInfo,
                                              const RecordObject& record_obj) {
    if (auto semaphore_state = Get<Semaphore>(pSleepInfo->signalSemaphore)) {
        auto value = pSleepInfo->value;
        semaphore_state->EnqueueSignal(SubmissionReference{}, value);
    }
}

// TODO: PostRecord is not needed. Test this. WaitSemaphores will retire the signal.
// LatencySleepNV does not perform wait but provides information about semaphore to the driver.
void DeviceState::PostCallRecordLatencySleepNV(VkDevice device, VkSwapchainKHR swapchain, const VkLatencySleepInfoNV* pSleepInfo,
                                               const RecordObject& record_obj) {
    if (auto semaphore_state = Get<Semaphore>(pSleepInfo->signalSemaphore)) {
        semaphore_state->RetireWait(nullptr, pSleepInfo->value, record_obj.location);
    }
}

void DeviceState::PostCallRecordCreateIndirectExecutionSetEXT(VkDevice device,
                                                              const VkIndirectExecutionSetCreateInfoEXT* pCreateInfo,
                                                              const VkAllocationCallbacks* pAllocator,
                                                              VkIndirectExecutionSetEXT* pIndirectExecutionSet,
                                                              const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }

    std::shared_ptr<IndirectExecutionSet> indirect_execution_state =
        std::make_shared<IndirectExecutionSet>(*this, *pIndirectExecutionSet, pCreateInfo);

    if (indirect_execution_state->is_pipeline && pCreateInfo->info.pPipelineInfo) {
        const VkIndirectExecutionSetPipelineInfoEXT& pipeline_info = *pCreateInfo->info.pPipelineInfo;
        indirect_execution_state->initial_pipeline = Get<Pipeline>(pipeline_info.initialPipeline);
        indirect_execution_state->shader_stage_flags = indirect_execution_state->initial_pipeline->active_shaders;
    } else if (indirect_execution_state->is_shader_objects && pCreateInfo->info.pShaderInfo) {
        const VkIndirectExecutionSetShaderInfoEXT& shader_info = *pCreateInfo->info.pShaderInfo;
        for (uint32_t i = 0; i < shader_info.shaderCount; i++) {
            const VkShaderEXT shader_handle = shader_info.pInitialShaders[i];
            const auto shader_object = Get<ShaderObject>(shader_handle);
            ASSERT_AND_CONTINUE(shader_object);
            indirect_execution_state->shader_stage_flags |= shader_object->create_info.stage;
            if (i == 0) {
                indirect_execution_state->initial_shader_object = shader_object;
            }
            if (shader_object->create_info.stage == VK_SHADER_STAGE_FRAGMENT_BIT) {
                indirect_execution_state->initial_fragment_shader_object = shader_object;
            }
        }
    }

    Add(std::move(indirect_execution_state));
}

void DeviceState::PreCallRecordDestroyIndirectExecutionSetEXT(VkDevice device, VkIndirectExecutionSetEXT indirectExecutionSet,
                                                              const VkAllocationCallbacks* pAllocator,
                                                              const RecordObject& record_obj) {
    Destroy<IndirectExecutionSet>(indirectExecutionSet);
}

void DeviceState::PostCallRecordCreateIndirectCommandsLayoutEXT(VkDevice device,
                                                                const VkIndirectCommandsLayoutCreateInfoEXT* pCreateInfo,
                                                                const VkAllocationCallbacks* pAllocator,
                                                                VkIndirectCommandsLayoutEXT* pIndirectCommandsLayout,
                                                                const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    Add(std::make_shared<IndirectCommandsLayout>(*this, *pIndirectCommandsLayout, pCreateInfo));
}

void DeviceState::PreCallRecordDestroyIndirectCommandsLayoutEXT(VkDevice device, VkIndirectCommandsLayoutEXT indirectCommandsLayout,
                                                                const VkAllocationCallbacks* pAllocator,
                                                                const RecordObject& record_obj) {
    Destroy<IndirectCommandsLayout>(indirectCommandsLayout);
}

struct CommandBufferReservedAddressInfillUpdateOps {
    using Map = typename DeviceState::DescriptorHeapReservedAddress::RangeMap;
    using Iterator = typename Map::iterator;
    using Value = typename Map::value_type;
    using Mapped = typename Map::mapped_type;
    using Range = typename Map::key_type;

    void infill(Map& map, const Iterator& pos, const Range& infill_range) const {
        map.insert(pos, Value(infill_range, insert_value));
    }

    void update(const Iterator& pos) const {
        auto& current_buffer_list = pos->second;
        assert(!current_buffer_list.empty());
        const auto buffer_found_it = std::find(current_buffer_list.begin(), current_buffer_list.end(), insert_value[0]);
        if (buffer_found_it == current_buffer_list.end()) {
            if (current_buffer_list.capacity() <= (current_buffer_list.size() + 1)) {
                current_buffer_list.reserve(current_buffer_list.capacity() * 2);
            }
            current_buffer_list.emplace_back(insert_value[0]);
        }
    }
    const Mapped& insert_value;
};

struct CommandBufferReservedAddressRemoveOps {
    using Map = DeviceState::DescriptorHeapReservedAddress::RangeMap;
    using Mapped = Map::mapped_type;

    bool operator()(Mapped& cmd_buffer_list) const {
        auto it = std::find(cmd_buffer_list.begin(), cmd_buffer_list.end(), removed_value);
        if (it != cmd_buffer_list.end()) {
            if (cmd_buffer_list.size() == 1) {
                return true;
            } else {
                // Swap with last element and resize to remove it
                size_t idx = std::distance(cmd_buffer_list.begin(), it);
                if (idx != cmd_buffer_list.size() - 1) {
                    std::swap(cmd_buffer_list[idx], cmd_buffer_list.back());
                }
                cmd_buffer_list.resize(cmd_buffer_list.size() - 1);
                return false;
            }
        }
        return false;
    }

    const vvl::CommandBuffer* removed_value;
};

void DeviceState::UpdateCommandBufferHeapReservedAddressMap(vvl::CommandBuffer* cb_state,
                                                            const vvl::range<VkDeviceAddress>& new_range, bool is_sampler) {
    assert(cb_state != nullptr);

    WriteLockGuard guard(descriptor_heap_reserved_address.lock);

    const vvl::range<VkDeviceAddress>& existing_range =
        is_sampler ? cb_state->descriptor_heap.sampler_reserved : cb_state->descriptor_heap.resource_reserved;
    DescriptorHeapReservedAddress::RangeMap& cmd_buffer_map =
        is_sampler ? descriptor_heap_reserved_address.sampler_map : descriptor_heap_reserved_address.resource_map;

    if (existing_range.non_empty()) {
        CommandBufferReservedAddressRemoveOps remove_ops{cb_state};
        cmd_buffer_map.erase_range_or_touch(existing_range, remove_ops);
    }

    if (new_range.non_empty()) {
        CommandBufferReservedAddressInfillUpdateOps update_ops{{cb_state}};
        sparse_container::infill_update_range(cmd_buffer_map, new_range, update_ops);
    }
}

void DeviceState::RemoveCommandBufferHeapReservedAddressMap(vvl::CommandBuffer* cb_state) {
    assert(cb_state != nullptr);

    WriteLockGuard guard(descriptor_heap_reserved_address.lock);

    const vvl::range<VkDeviceAddress>& resource_existing_range = cb_state->descriptor_heap.resource_reserved;
    if (resource_existing_range.non_empty()) {
        CommandBufferReservedAddressRemoveOps remove_ops{cb_state};
        descriptor_heap_reserved_address.resource_map.erase_range_or_touch(resource_existing_range, remove_ops);
    }

    const vvl::range<VkDeviceAddress>& sampler_existing_range = cb_state->descriptor_heap.sampler_reserved;
    if (sampler_existing_range.non_empty()) {
        CommandBufferReservedAddressRemoveOps remove_ops{cb_state};
        descriptor_heap_reserved_address.sampler_map.erase_range_or_touch(sampler_existing_range, remove_ops);
    }
}

void DeviceState::PostCallRecordCmdBindSamplerHeapEXT(VkCommandBuffer commandBuffer, const VkBindHeapInfoEXT* pBindInfo,
                                                      const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBindSamplerHeap(*this, *pBindInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdBindResourceHeapEXT(VkCommandBuffer commandBuffer, const VkBindHeapInfoEXT* pBindInfo,
                                                       const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordBindResourceHeap(*this, *pBindInfo, record_obj.location);
}

void DeviceState::PostCallRecordCmdPushDataEXT(VkCommandBuffer commandBuffer, const VkPushDataInfoEXT* pPushDataInfo,
                                               const RecordObject& record_obj) {
    auto cb_state = GetWrite<CommandBuffer>(commandBuffer);
    cb_state->RecordPushData(*pPushDataInfo, record_obj.location);

    cb_state->SetDescriptorMode(DescriptorModeHeap, record_obj.location.function);
}

void DeviceState::PostCallRecordWriteResourceDescriptorsEXT(VkDevice device, uint32_t resourceCount,
                                                            const VkResourceDescriptorInfoEXT* pResources,
                                                            const VkHostAddressRangeEXT* pDescriptors,
                                                            const RecordObject& record_obj) {
    if (!global_settings.descriptor_hashing) {
        return;
    }
    WriteLockGuard guard(descriptor_hashing->map_lock);
    for (uint32_t i = 0; i < resourceCount; i++) {
        const VkResourceDescriptorInfoEXT& resource = pResources[i];
        const VkHostAddressRangeEXT& host_range = pDescriptors[i];
        // pDescriptors[i].size might be larger than the real descriptor
        const VkDeviceSize descriptor_size = cached_descriptor_size.GetSize(resource.type);
        const uint64_t key = descriptor_hashing->Hash(host_range.address, descriptor_size);
        const uint8_t vvl_type = (uint8_t)GetMaskFromDescriptorType(resource.type);
        if (const auto* debug_info = vku::FindStructInPNextChain<VkDebugUtilsObjectNameInfoEXT>(resource.pNext)) {
            if (debug_info->pObjectName) {
                descriptor_hashing->debug_names.emplace(key, debug_info->pObjectName);
            }
        }

        bool null_descriptor = false;

        switch (resource.type) {
            case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
            case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: {
                if (resource.data.pAddressRange) {
                    const bool added = descriptor_hashing->table.Insert(
                        key, DescriptorHashTable::Entry(vvl_type, DescriptorHashTable::EntryBuffer{*resource.data.pAddressRange}),
                        *this, record_obj.location);
                    if (added) {
                        auto buffer_list = GetBuffersByAddress(resource.data.pAddressRange->address);
                        // This is not a perfect system, and could leak if multiple VkBuffer to a single address
                        // but in practice, we find there is noramlly a single buffer tied to an address to make this "good enough"
                        if (buffer_list.size() == 1) {
                            buffer_list[0]->descriptor_hashes.emplace_back(key);
                        }
                    }
                } else {
                    null_descriptor = true;
                }
            } break;
            case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
            case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
            case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: {
                if (resource.data.pImage) {
                    const VkImageViewCreateInfo& view_ci = *resource.data.pImage->pView;
                    const bool added = descriptor_hashing->table.Insert(
                        key,
                        DescriptorHashTable::Entry(
                            vvl_type, DescriptorHashTable::EntryImage{view_ci.image, view_ci.format, view_ci.viewType}),
                        *this, record_obj.location);
                    if (added) {
                        if (auto image_state = Get<vvl::Image>(view_ci.image)) {
                            image_state->descriptor_hashes.emplace_back(key);
                        }
                    }
                } else {
                    null_descriptor = true;
                }
            } break;
            case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
            case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: {
                if (pResources[i].data.pTexelBuffer) {
                    const bool added = descriptor_hashing->table.Insert(
                        key,
                        DescriptorHashTable::Entry(vvl_type,
                                                   DescriptorHashTable::EntryBuffer{resource.data.pTexelBuffer->addressRange}),
                        *this, record_obj.location);
                    if (added) {
                        auto buffer_list = GetBuffersByAddress(resource.data.pTexelBuffer->addressRange.address);
                        if (buffer_list.size() == 1) {
                            buffer_list[0]->descriptor_hashes.emplace_back(key);
                        }
                    }
                } else {
                    null_descriptor = true;
                }
            } break;
            case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR: {
                if (pResources[i].data.pAddressRange) {
                    descriptor_hashing->table.Insert(
                        key, DescriptorHashTable::Entry(vvl_type, DescriptorHashTable::EntryBuffer{*resource.data.pAddressRange}),
                        *this, record_obj.location);
                    // TODO - Add somewhere to be removed when AS are deleted
                } else {
                    null_descriptor = true;
                }
            } break;
            default:
                break;
        }

        // TODO - might be lots of descriptor, should cache if added already
        if (null_descriptor) {
            descriptor_hashing->table.Insert(key, DescriptorHashTable::Entry(vvl_type, DescriptorHashTable::EntryNull{}), *this,
                                             record_obj.location);
        }
    }
}

void DeviceState::PostCallRecordWriteSamplerDescriptorsEXT(VkDevice device, uint32_t samplerCount,
                                                           const VkSamplerCreateInfo* pSamplers,
                                                           const VkHostAddressRangeEXT* pDescriptors,
                                                           const RecordObject& record_obj) {
    if (!global_settings.descriptor_hashing) {
        return;
    }
    WriteLockGuard guard(descriptor_hashing->map_lock);
    for (uint32_t i = 0; i < samplerCount; i++) {
        const VkHostAddressRangeEXT& host_range = pDescriptors[i];

        const VkDeviceSize descriptor_size = phys_dev_ext_props.descriptor_heap_props.samplerDescriptorSize;
        const uint64_t key = descriptor_hashing->Hash(host_range.address, descriptor_size);

        if (const auto* debug_info = vku::FindStructInPNextChain<VkDebugUtilsObjectNameInfoEXT>(pSamplers->pNext)) {
            if (debug_info->pObjectName) {
                descriptor_hashing->debug_names.emplace(key, debug_info->pObjectName);
            }
        }

        // Because there is no concept of a VkSampler anymore, there is no way to know if the user will be done using this sampler
        // descriptor. This means we will never be able to remove this descriptor from the hash map.
        descriptor_hashing->table.Insert(key, DescriptorHashTable::Entry(DescriptorHashTable::EntrySampler{}), *this,
                                         record_obj.location);
    }
}

void DeviceState::PostCallRecordGetDescriptorEXT(VkDevice device, const VkDescriptorGetInfoEXT* pDescriptorInfo, size_t dataSize,
                                                 void* pDescriptor, const RecordObject& record_obj) {
    if (!global_settings.descriptor_hashing) {
        return;
    }
    if (pDescriptorInfo->type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) {
        return;  // TODO - need to handle
    }
    WriteLockGuard guard(descriptor_hashing->map_lock);

    const VkDescriptorType vk_type = pDescriptorInfo->type;
    const uint8_t vvl_type = (uint8_t)GetMaskFromDescriptorType(vk_type);
    // dataSize might be larger than the real descriptor
    const VkDeviceSize descriptor_size = cached_descriptor_size.GetSize(vk_type, false);
    const uint64_t key = descriptor_hashing->Hash(pDescriptor, descriptor_size);
    if (const auto* debug_info = vku::FindStructInPNextChain<VkDebugUtilsObjectNameInfoEXT>(pDescriptorInfo->pNext)) {
        if (debug_info->pObjectName) {
            descriptor_hashing->debug_names.emplace(key, debug_info->pObjectName);
        }
    }

    bool null_descriptor = false;

    switch (vk_type) {
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: {
            const VkDescriptorAddressInfoEXT* address_info = vk_type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
                                                                 ? pDescriptorInfo->data.pStorageBuffer
                                                                 : pDescriptorInfo->data.pUniformBuffer;
            if (address_info && address_info->address != 0) {
                VkDeviceAddressRangeEXT address_range{address_info->address, address_info->range};
                const bool added = descriptor_hashing->table.Insert(
                    key, DescriptorHashTable::Entry(vvl_type, DescriptorHashTable::EntryBuffer{address_range}), *this,
                    record_obj.location);
                if (added) {
                    auto buffer_list = GetBuffersByAddress(address_info->address);
                    if (buffer_list.size() == 1) {
                        buffer_list[0]->descriptor_hashes.emplace_back(key);
                    }
                }
            } else {
                null_descriptor = true;
            }
        } break;
        case VK_DESCRIPTOR_TYPE_SAMPLER:
            if (pDescriptorInfo->data.pSampler) {
                descriptor_hashing->table.Insert(key, DescriptorHashTable::Entry(DescriptorHashTable::EntrySampler{}), *this,
                                                 record_obj.location);
            }
            break;
        case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
        case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
        case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: {
            const VkDescriptorImageInfo* image_info =
                vk_type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE   ? pDescriptorInfo->data.pSampledImage
                : vk_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ? pDescriptorInfo->data.pStorageImage
                                                              : pDescriptorInfo->data.pInputAttachmentImage;
            if (image_info && image_info->imageView != VK_NULL_HANDLE) {
                if (const auto view_state = Get<vvl::ImageView>(image_info->imageView)) {
                    const bool added = descriptor_hashing->table.Insert(
                        key,
                        DescriptorHashTable::Entry(
                            vvl_type, DescriptorHashTable::EntryImage{view_state->create_info.image, view_state->create_info.format,
                                                                      view_state->create_info.viewType}),
                        *this, record_obj.location);
                    if (added && view_state->image_state) {
                        view_state->image_state->descriptor_hashes.emplace_back(key);
                    }
                }
            } else {
                null_descriptor = true;
            }
        } break;
        case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
        case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: {
            const VkDescriptorAddressInfoEXT* address_info = vk_type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER
                                                                 ? pDescriptorInfo->data.pStorageTexelBuffer
                                                                 : pDescriptorInfo->data.pUniformTexelBuffer;
            if (address_info && address_info->address != 0) {
                VkDeviceAddressRangeEXT address_range{address_info->address, address_info->range};
                const bool added = descriptor_hashing->table.Insert(
                    key, DescriptorHashTable::Entry(vvl_type, DescriptorHashTable::EntryBuffer{address_range}), *this,
                    record_obj.location);
                if (added) {
                    auto buffer_list = GetBuffersByAddress(address_info->address);
                    if (buffer_list.size() == 1) {
                        buffer_list[0]->descriptor_hashes.emplace_back(key);
                    }
                }
            } else {
                null_descriptor = true;
            }
        } break;
        case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR: {
            if (pDescriptorInfo->data.accelerationStructure) {
                VkDeviceAddressRangeEXT address_range{pDescriptorInfo->data.accelerationStructure, 0};
                descriptor_hashing->table.Insert(
                    key, DescriptorHashTable::Entry(vvl_type, DescriptorHashTable::EntryBuffer{address_range}), *this,
                    record_obj.location);
                // TODO - Add somewhere to be removed when AS are deleted
            } else {
                null_descriptor = true;
            }
        } break;
        default:
            break;
    }

    // TODO - might be lots of descriptor, should cache if added already
    if (null_descriptor) {
        descriptor_hashing->table.Insert(key, DescriptorHashTable::Entry(vvl_type, DescriptorHashTable::EntryNull{}), *this,
                                         record_obj.location);
    }
}

}  // namespace vvl
