/* 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 "state_tracker/cmd_buffer_state.h"
#include <vulkan/vulkan_core.h>
#include <vulkan/utility/vk_format_utils.h>
#include <memory>
#include "containers/limits.h"
#include "error_message/error_location.h"
#include "generated/command_validation.h"
#include "generated/vk_object_types.h"
#include "state_tracker/descriptor_mode.h"
#include "state_tracker/descriptor_sets.h"
#include "state_tracker/last_bound_state.h"
#include "state_tracker/render_pass_state.h"
#include "state_tracker/pipeline_state.h"
#include "state_tracker/buffer_state.h"
#include "state_tracker/event_state.h"
#include "state_tracker/image_state.h"
#include "state_tracker/queue_state.h"
#include "state_tracker/vertex_index_buffer_state.h"
#include "utils/assert_utils.h"
#include "utils/image_utils.h"
#include "containers/container_utils.h"

// Submit time validation helper. Since CmdBeginRendering is itself an action command,
// if the first detected action or sync command is CmdBeginRendering, it means there are
// no other action commands before it.
bool HasActionOrSyncCommandBeforeBeginRendering(vvl::Func first_action_or_sync_command) {
    return first_action_or_sync_command != vvl::Func::Empty &&
           !IsValueIn(first_action_or_sync_command, {vvl::Func::vkCmdBeginRendering, vvl::Func::vkCmdBeginRenderingKHR});
}

using RangeGenerator = subresource_adapter::RangeGenerator;

// Dynamic Rendering we know it is depth only, but for VkRenderPass, we need to check incase it is a stencil only attachment
bool AttachmentInfo::IsDepth() const {
    return type == Type::Depth ||
           (type == Type::DepthStencil && image_view && vkuFormatHasDepth(image_view->image_state->GetFormat()));
}

bool AttachmentInfo::IsStencil() const {
    return type == Type::Stencil ||
           (type == Type::DepthStencil && image_view && vkuFormatHasStencil(image_view->image_state->GetFormat()));
}

// For Traditional RenderPasses, the index is simply the index into the VkRenderPassCreateInfo::pAttachments,
// but for dynamic rendering, there is no "standard" way to map the index, instead we have our own custom indexing and it is not
// obvious at all to the user where it came from
std::string AttachmentInfo::Describe(const vvl::CommandBuffer& cb_state, uint32_t rp_index) const {
    std::ostringstream ss;
    if (cb_state.attachment_source == AttachmentSource::DynamicRendering) {
        ss << "VkRenderingInfo::";
        if (type == Type::Color) {
            ss << "pColorAttachments[" << rp_index << "].imageView";
        } else if (type == Type::ColorResolve) {
            // This assumes the caller calculated the correct index with GetDynamicRenderingColorResolveAttachmentIndex
            ss << "pColorAttachments[" << rp_index << "].resolveImageView";
        } else if (type == Type::Depth) {
            ss << "pDepthAttachment->imageView";
        } else if (type == Type::DepthResolve) {
            ss << "pStencilAttachment->resolveImageView";
        } else if (type == Type::Stencil) {
            ss << "pStencilAttachment->imageView";
        } else if (type == Type::StencilResolve) {
            ss << "pStencilAttachment->resolveImageView";
        } else if (type == Type::FragmentDensityMap) {
            ss << "pNext<VkRenderingFragmentDensityMapAttachmentInfoEXT>.imageView";
        } else if (type == Type::FragmentShadingRate) {
            ss << "pNext<VkRenderingFragmentShadingRateAttachmentInfoKHR>.imageView";
        }
    } else {
        // if the user has a [color, depth, color] the last color would have
        //   rp_index == 2
        //   index == 1
        ss << "VkRenderPassCreateInfo::pAttachments[" << rp_index << "] (Subpass " << cb_state.GetActiveSubpass() << ", ";

        if (type == Type::Empty) {
            ss << "VK_ATTACHMENT_UNUSED";
        } else if (type == Type::Input) {
            ss << "VkSubpassDescription::pInputAttachments[" << type_index << "]";
        } else if (type == Type::Color) {
            ss << "VkSubpassDescription::pColorAttachments[" << type_index << "]";
        } else if (type == Type::ColorResolve) {
            ss << "VkSubpassDescription::pResolveAttachments[" << type_index << "]";
        } else if (type == Type::DepthStencil) {
            ss << "VkSubpassDescription::pDepthStencilAttachment";
        } else if (type == Type::FragmentDensityMap) {
            ss << "VkRenderPassFragmentDensityMapCreateInfoEXT::fragmentDensityMapAttachment";
        } else if (type == Type::FragmentShadingRate) {
            ss << "VkFragmentShadingRateAttachmentInfoKHR::pFragmentShadingRateAttachment";
        } else {
            ss << "Unknown Type";
        }

        ss << ")";
    }
    return ss.str();
}

namespace vvl {

CommandPool::CommandPool(DeviceState& dev, VkCommandPool handle, const VkCommandPoolCreateInfo* create_info, VkQueueFlags flags)
    : StateObject(handle, kVulkanObjectTypeCommandPool),
      dev_data(dev),
      createFlags(create_info->flags),
      queueFamilyIndex(create_info->queueFamilyIndex),
      queue_flags(flags),
      unprotected((create_info->flags & VK_COMMAND_POOL_CREATE_PROTECTED_BIT) == 0) {}

void CommandPool::Allocate(const VkCommandBufferAllocateInfo* allocate_info, const VkCommandBuffer* command_buffers) {
    for (uint32_t i = 0; i < allocate_info->commandBufferCount; i++) {
        auto new_cb = dev_data.CreateCmdBufferState(command_buffers[i], allocate_info, *this);
        commandBuffers.emplace(command_buffers[i], new_cb.get());
        dev_data.Add(std::move(new_cb));
    }
}

void CommandPool::Free(uint32_t count, const VkCommandBuffer* command_buffers) {
    for (uint32_t i = 0; i < count; i++) {
        auto iter = commandBuffers.find(command_buffers[i]);
        if (iter != commandBuffers.end()) {
            dev_data.Destroy<CommandBuffer>(iter->first);
            commandBuffers.erase(iter);
        }
    }
}

void CommandPool::Reset(const Location& loc) {
    for (auto& entry : commandBuffers) {
        auto guard = entry.second->WriteLock();
        entry.second->Reset(loc);
    }
}

void CommandPool::Destroy() {
    for (auto& entry : commandBuffers) {
        dev_data.Destroy<CommandBuffer>(entry.first);
    }
    commandBuffers.clear();
    StateObject::Destroy();
}

void CommandBuffer::SetActiveSubpass(uint32_t subpass) {
    active_subpass_ = subpass;
    // Always reset stored rasterization samples count
    active_subpass_sample_count_ = std::nullopt;
}

// Put here, instead of vvl::RenderPass for ease of access
const char* CommandBuffer::DescribeActiveColorAttachment() const {
    if (!active_render_pass) {
        return "";
    } else if (active_render_pass->UsesDynamicRendering()) {
        return "Active color attachments are those where VkRenderingInfo::pColorAttachments[i].imageView != VK_NULL_HANDLE";
    } else {
        return "Active color attachments are those where pSubpasses[i].pColorAttachments[i].attachment != VK_ATTACHMENT_UNUSED";
    }
}

CommandBuffer::CommandBuffer(DeviceState& dev, VkCommandBuffer handle, const VkCommandBufferAllocateInfo* allocate_info,
                             const vvl::CommandPool& pool)
    : RefcountedStateObject(handle, kVulkanObjectTypeCommandBuffer),
      allocate_info(*allocate_info),
      unprotected(pool.unprotected),
      command_pool(pool),
      dev_data(dev),
      lastBound({{{*this, VK_PIPELINE_BIND_POINT_GRAPHICS},
                  {*this, VK_PIPELINE_BIND_POINT_COMPUTE},
                  {*this, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR},
                  {*this, VK_PIPELINE_BIND_POINT_DATA_GRAPH_ARM}}}) {
    ResetCBState();
}

// Get the image viewstate for a given framebuffer attachment
vvl::ImageView* CommandBuffer::GetActiveAttachmentImageViewState(uint32_t index) {
    assert(!active_attachments.empty() && index != VK_ATTACHMENT_UNUSED && (index < active_attachments.size()));
    return active_attachments[index].image_view;
}

// Get the image viewstate for a given framebuffer attachment
const vvl::ImageView* CommandBuffer::GetActiveAttachmentImageViewState(uint32_t index) const {
    if (active_attachments.empty() || index == VK_ATTACHMENT_UNUSED || (index >= active_attachments.size())) {
        return nullptr;
    }
    return active_attachments[index].image_view;
}

void CommandBuffer::AddChild(std::shared_ptr<StateObject>& child_node) {
    assert(child_node);
    if (child_node->AddParent(this)) {
        object_bindings.insert(child_node);
    }
}

void CommandBuffer::RemoveChild(std::shared_ptr<StateObject>& child_node) {
    assert(child_node);
    child_node->RemoveParent(this);
    object_bindings.erase(child_node);
}

void CommandBuffer::DescriptorHeap::Update(const VkBindHeapInfoEXT& bind_info, bool is_sampler) {
    vvl::range<VkDeviceAddress> range = {bind_info.heapRange.address, bind_info.heapRange.address + bind_info.heapRange.size};
    vvl::range<VkDeviceAddress> reserved = {
        bind_info.heapRange.address + bind_info.reservedRangeOffset,
        bind_info.heapRange.address + bind_info.reservedRangeOffset + bind_info.reservedRangeSize};

    if (is_sampler) {
        sampler_bound = true;
        sampler_reserved = reserved;
        sampler_range = range;
    } else {
        resource_bound = true;
        resource_reserved = reserved;
        resource_range = range;
    }
}

std::string CommandBuffer::DescriptorHeap::Describe(bool is_sampler) const {
    std::stringstream ss;
    if (is_sampler) {
        ss << "heapRange = { address = 0x" << std::hex << sampler_range.begin << ", size = " << std::dec << sampler_range.size()
           << " }, ";
        ss << "reservedRangeOffset = " << (sampler_reserved.begin - sampler_range.begin) << ", ";
        ss << "reservedRangeSize = " << sampler_reserved.size() << "";
    } else {
        ss << "heapRange = { address = 0x" << std::hex << resource_range.begin << ", size = " << std::dec << resource_range.size()
           << " }, ";
        ss << "reservedRangeOffset = " << (resource_reserved.begin - resource_range.begin) << ", ";
        ss << "reservedRangeSize = " << resource_reserved.size() << "";
    }
    return ss.str();
}

// This tracking is awful, in practice people are using only a single descriptor mode per command buffer
// But in theory you |COULD| do some crazy stuff, like
//   - Set graphics to use classic descriptor
//   - Set compute to use Descriptor buffer
//   - Override both using Heaps
// To "properly" do this, we would need to really devide LastBound state into 3 structs for each
// For the practical future, we will try and get away just assuming these crazy cases are not happening
void CommandBuffer::SetDescriptorMode(vvl::DescriptorMode new_mode, vvl::Func function) {
    // 99% of time, all LastBound will be the same mode
    bool reset_heap = false;
    bool reset_buffer = false;

    for (uint32_t i = 0; i < vvl::BindPointCount; i++) {
        const vvl::DescriptorMode old_mode = lastBound[i].GetDescriptorMode();
        if (old_mode == new_mode) {
            continue;  // common case, nothing to do/change
        }

        if (old_mode == vvl::DescriptorMode::DescriptorModeHeap && !reset_heap) {
            // mappings in device state object to this command buffer state
            dev_data.RemoveCommandBufferHeapReservedAddressMap(this);

            descriptor_heap.Reset();
            reset_heap = true;
        } else if (old_mode == vvl::DescriptorMode::DescriptorModeBuffer && !reset_buffer) {
            descriptor_buffer.Reset();
            reset_buffer = true;
        }

        lastBound[i].SetDescriptorMode(new_mode, function);
    }

    if (new_mode == vvl::DescriptorMode::DescriptorModeHeap) {
        push_constant_ranges_layout.reset();
    }
}

// Some functions like vkCmdPushConstant are valid in Classic/Buffer, but invalid in Heap
// So calling it doesn't "set" a mode, but instead only "invalidates"
void CommandBuffer::InvalidateDescriptorMode(vvl::DescriptorMode invalidate_mode, vvl::DescriptorMode new_mode,
                                             vvl::Func function) {
    for (uint32_t i = 0; i < vvl::BindPointCount; i++) {
        const vvl::DescriptorMode current_mode = lastBound[i].GetDescriptorMode();
        if (current_mode == invalidate_mode) {
            SetDescriptorMode(new_mode, function);
            break;
        }
    }
}

// Reset the command buffer state
// Maintain the createInfo and set state to CB_NEW, but clear all other state
void CommandBuffer::ResetCBState() {
    // Remove object bindings
    for (const auto& obj : object_bindings) {
        obj->RemoveParent(this);
    }
    object_bindings.clear();
    broken_bindings.clear();
    broken_internal_device_range.reset();

    begin_info_flags = 0;
    has_inheritance = false;
    inheritance_info = {};

    state = CbState::New;
    command_count = 0;
    submit_count = 0;
    image_layout_change_count = 1;  // Start at 1. 0 is insert value for validation cache versions, s.t. new == dirty

    dynamic_state_status.cb.reset();
    dynamic_state_status.pipeline.reset();
    dynamic_state_status.history.reset();
    dynamic_state_status.rtx_stack_size_cb = false;
    dynamic_state_status.rtx_stack_size_pipeline = false;
    CBDynamicFlags all;
    dynamic_state_value.reset(all.set());
    memset(&invalidated_state_pipe, 0, sizeof(VkPipeline) * CB_DYNAMIC_STATE_STATUS_NUM);

    dirty_static_state = false;

    has_render_pass_instance = false;
    resumes_render_pass_instance = false;
    per_tile_execution_model_enabled = false;
    last_suspend_state = SuspendState::Empty;
    first_action_or_sync_command = Func::Empty;
    first_rendering_info = {};
    first_rendering_info_loc.reset();
    last_rendering_info = {};
    active_render_pass = nullptr;
    sample_locations_begin_info = {};
    attachment_source = AttachmentSource::Empty;
    active_attachments.clear();
    active_subpasses.clear();
    active_color_attachments_index.clear();
    has_render_pass_striped = false;
    striped_count = 0;
    active_subpass_contents = VK_SUBPASS_CONTENTS_INLINE;
    SetActiveSubpass(0);
    rendering_attachments.Reset();
    events.clear();
    active_queries.clear();
    started_queries.clear();
    render_pass_queries.clear();
    image_layout_registry.clear();
    aliased_image_layout_map.clear();
    current_vertex_buffer_binding_info.clear();
    primary_command_buffer = VK_NULL_HANDLE;
    linked_command_buffers.clear();
    bound_tile_memory = nullptr;

    // Both SetDescriptorMode and lastBound.Reset() will reset the descriptor mode, but SetDescriptorMode will also clear up the
    // descriptor heaps/buffers state if used, so it needs to be called first
    SetDescriptorMode(vvl::DescriptorModeUnknown, vvl::Func::Empty);
    for (auto& item : lastBound) {
        item.Reset();
    }
    active_framebuffer = VK_NULL_HANDLE;
    index_buffer_binding = {};

    tensor_barriers.clear();

    // Clean up video specific states
    bound_video_session = nullptr;
    bound_video_session_parameters = nullptr;
    bound_video_picture_resources.clear();
    video_encode_quality_level.reset();
    video_session_updates.clear();

    // Clean up the label data
    label_stack_depth_ = 0;
    label_commands_.clear();

    push_constant_ranges_layout.reset();
    push_data_dword_mask.clear();

    transform_feedback_active = false;
    transform_feedback_buffers_bound = 0;

    bind_vertex_buffer_3_used = false;
    stride_set_with_bind_vertex_buffer_3 = false;

    // Need to reset on initalization
    descriptor_heap.Reset();
    descriptor_buffer.Reset();

    // Clean up the label data
    dev_data.debug_report->ResetCmdDebugUtilsLabel(VkHandle());
}

void CommandBuffer::Reset(const Location& loc) {
    ResetCBState();
    // Remove reverse command buffer links.
    Invalidate(true);
    for (auto& item : sub_states_) {
        item.second->Reset(loc);
    }
}

void CommandBuffer::Destroy() {
    // Remove the cb debug labels
    dev_data.debug_report->EraseCmdDebugUtilsLabel(VkHandle());
    {
        auto guard = WriteLock();
        ResetCBState();
    }
    for (auto& item : sub_states_) {
        item.second->Destroy();
    }
    sub_states_.clear();
    StateObject::Destroy();
}

void CommandBuffer::NotifyInvalidate(const StateObject::NodeList& invalid_nodes, bool unlink) {
    {
        auto guard = WriteLock();
        assert(!invalid_nodes.empty());
        // Save all of the vulkan handles between the command buffer and the now invalid node
        LogObjectList log_list;
        for (auto& obj : invalid_nodes) {
            log_list.add(obj->Handle());
        }

        bool found_invalid = false;
        for (auto& obj : invalid_nodes) {
            // Only record a broken binding if one of the nodes in the invalid chain is still
            // being tracked by the command buffer. This is to try to avoid race conditions
            // caused by separate CommandBuffer and StateObject::parent_nodes locking.
            if (object_bindings.erase(obj)) {
                obj->RemoveParent(this);
                found_invalid = true;
            }
            switch (obj->Type()) {
                case kVulkanObjectTypeCommandBuffer:
                    if (unlink) {
                        linked_command_buffers.erase(static_cast<CommandBuffer*>(obj.get()));
                    }
                    break;
                case kVulkanObjectTypeImage:
                    if (unlink) {
                        image_layout_registry.erase(obj->Handle().Cast<VkImage>());
                    }
                    break;
                case kVulkanObjectTypeInternalDeviceRange:
                    if (unlink) {
                        const auto* internal_device_range = obj->Handle().Cast<vvl::InternalDeviceRange*>();
                        assert(internal_device_range);
                        // Note - If 2 calls in the command buffer have broken ranges, we are only going to print the last one
                        // This is the compromise to reduce memory overhead
                        broken_internal_device_range = std::make_unique<vvl::InternalDeviceRange>(*internal_device_range);
                    }
                    break;
                default:
                    break;
            }
        }
        if (found_invalid) {
            if (state == CbState::Recording) {
                state = CbState::InvalidIncomplete;
            } else if (state == CbState::Recorded) {
                state = CbState::InvalidComplete;
            }
            broken_bindings.emplace(invalid_nodes[0]->Handle(), log_list);
        }
    }
    for (auto& item : sub_states_) {
        item.second->NotifyInvalidate(invalid_nodes, unlink);
    }
    StateObject::NotifyInvalidate(invalid_nodes, unlink);
}

// The const variant only need the image as it is the key for the map
std::shared_ptr<const CommandBufferImageLayoutMap> CommandBuffer::GetImageLayoutMap(VkImage image) const {
    auto it = image_layout_registry.find(image);
    if (it == image_layout_registry.cend()) {
        return nullptr;
    }
    return it->second;
}

// The non-const variant only needs the image state, as the factory requires it to construct a new entry
std::shared_ptr<CommandBufferImageLayoutMap> CommandBuffer::GetOrCreateImageLayoutMap(const vvl::Image& image_state) {
    // Make sure we don't create a nullptr keyed entry for a zombie Image
    if (image_state.Destroyed() || !image_state.layout_map) {
        return nullptr;
    }
    auto iter = image_layout_registry.find(image_state.VkHandle());
    if (iter != image_layout_registry.end() && iter->second && image_state.GetId() == iter->second->image_id) {
        return iter->second;
    }
    std::shared_ptr<CommandBufferImageLayoutMap> image_layout_map;
    if (image_state.CanAlias()) {
        // Aliasing images need to share the same local layout map.
        // Since they use the same global layout state, use it as a key
        // for the local state. We don't need a lock on the global range
        // map to do a lookup based on its pointer.
        const auto* p_global_layout_map = image_state.layout_map.get();
        auto alias_iter = aliased_image_layout_map.find(p_global_layout_map);
        if (alias_iter != aliased_image_layout_map.end()) {
            image_layout_map = alias_iter->second;
        } else {
            image_layout_map = std::make_shared<CommandBufferImageLayoutMap>(image_state.subresource_encoder.SubresourceCount(),
                                                                             image_state.GetId());
            // Save the local layout map for the next aliased image.
            // The global layout map pointer is only used as a key into the local lookup
            // table so it doesn't need to be locked.
            aliased_image_layout_map.emplace(p_global_layout_map, image_layout_map);
        }
    } else {
        image_layout_map =
            std::make_shared<CommandBufferImageLayoutMap>(image_state.subresource_encoder.SubresourceCount(), image_state.GetId());
    }
    if (iter != image_layout_registry.end()) {
        // overwrite the stale entry
        iter->second = image_layout_map;
    } else {
        // add a new entry
        image_layout_registry.insert({image_state.VkHandle(), image_layout_map});
    }
    return image_layout_map;
}

void CommandBuffer::RecordCommand(const Location& loc) {
    command_count++;

    if (first_action_or_sync_command == Func::Empty) {
        const CommandValidationInfo& info = GetCommandValidationInfo(loc.function);
        if (info.action || info.synchronization) {
            first_action_or_sync_command = loc.function;
        }
    }
}

void CommandBuffer::RecordBeginQuery(const QueryObject& query_obj, const Location& loc) {
    active_queries.insert(query_obj);
    started_queries.insert(query_obj);

    updated_queries.insert(query_obj);
    if (query_obj.inside_render_pass) {
        render_pass_queries.insert(query_obj);
    }

    for (auto& item : sub_states_) {
        item.second->RecordBeginQuery(query_obj, loc);
    }
}

void CommandBuffer::RecordEndQuery(const QueryObject& query_obj, const Location& loc) {
    active_queries.erase(query_obj);
    updated_queries.insert(query_obj);
    if (query_obj.inside_render_pass) {
        render_pass_queries.erase(query_obj);
    }

    for (auto& item : sub_states_) {
        item.second->RecordEndQuery(query_obj, loc);
    }
}

bool CommandBuffer::UpdatesQuery(const QueryObject& query_obj) const {
    // Clear out the perf_pass from the caller because it isn't known when the command buffer is recorded.
    auto key = query_obj;
    key.perf_pass = 0;
    for (auto* sub_cb : linked_command_buffers) {
        if (sub_cb->updated_queries.find(key) != sub_cb->updated_queries.end()) {
            return true;
        }
    }
    return updated_queries.find(key) != updated_queries.end();
}

void CommandBuffer::RecordEndQueries(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount) {
    for (uint32_t slot = firstQuery; slot < (firstQuery + queryCount); slot++) {
        QueryObject query_obj = {queryPool, slot};
        active_queries.erase(query_obj);
        updated_queries.insert(query_obj);
    }

    for (auto& item : sub_states_) {
        item.second->RecordEndQueries(queryPool, firstQuery, queryCount);
    }
}

void CommandBuffer::RecordWriteTimestamp(VkQueryPool queryPool, uint32_t slot, const Location& loc) {
    RecordCommand(loc);
    if (dev_data.disabled[query_validation]) {
        return;
    }

    if (!dev_data.disabled[command_buffer_state]) {
        auto pool_state = dev_data.Get<vvl::QueryPool>(queryPool);
        AddChild(pool_state);
    }

    QueryObject query_obj = {queryPool, slot};
    for (auto& item : sub_states_) {
        item.second->RecordWriteTimestamp(query_obj, loc);
    }

    // Acts like an end query
    active_queries.erase(query_obj);
    updated_queries.insert(query_obj);
    if (query_obj.inside_render_pass) {
        render_pass_queries.erase(query_obj);
    }
}

void CommandBuffer::RecordResetQueryPool(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, const Location& loc) {
    RecordCommand(loc);
    if (dev_data.disabled[query_validation]) {
        return;
    }

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

    for (uint32_t slot = firstQuery; slot < (firstQuery + queryCount); slot++) {
        QueryObject query_obj = {queryPool, slot};
        updated_queries.insert(query_obj);
    }

    const bool is_perf_query = pool_state->create_info.queryType == VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR;
    for (auto& item : sub_states_) {
        item.second->RecordResetQueryPool(queryPool, firstQuery, queryCount, is_perf_query, loc);
    }
}

void CommandBuffer::RecordCopyQueryPoolResults(VkQueryPool queryPool, VkBuffer dstBuffer, uint32_t firstQuery, uint32_t queryCount,
                                               VkDeviceSize dstOffset, VkDeviceSize stride, VkQueryResultFlags flags,
                                               const Location& loc) {
    RecordCommand(loc);
    if (dev_data.disabled[query_validation]) {
        return;
    }

    auto buffer_state = dev_data.Get<Buffer>(dstBuffer);
    auto pool_state = dev_data.Get<QueryPool>(queryPool);
    ASSERT_AND_RETURN(buffer_state && pool_state);
    if (!dev_data.disabled[command_buffer_state]) {
        AddChild(buffer_state);
        AddChild(pool_state);
    }

    for (auto& item : sub_states_) {
        item.second->RecordCopyQueryPoolResults(*pool_state, *buffer_state, firstQuery, queryCount, dstOffset, stride, flags, loc);
    }
}

void CommandBuffer::RecordCopyQueryPoolResultsToMemory(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount,
                                                       const VkStridedDeviceAddressRangeKHR* pDstRange,
                                                       VkAddressCommandFlagsKHR dstFlags, VkQueryResultFlags queryResultFlags,
                                                       const Location& loc) {
    RecordCommand(loc);
    if (dev_data.disabled[query_validation]) {
        return;
    }

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

    for (auto& item : sub_states_) {
        item.second->RecordCopyQueryPoolResultsToMemory(*pool_state, firstQuery, queryCount, queryResultFlags, loc);
    }
}

void CommandBuffer::RecordWriteAccelerationStructuresProperties(VkQueryPool queryPool, uint32_t firstQuery,
                                                                uint32_t accelerationStructureCount, const Location& loc) {
    RecordCommand(loc);
    if (dev_data.disabled[query_validation]) {
        return;
    }

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

    for (auto& item : sub_states_) {
        item.second->RecordWriteAccelerationStructuresProperties(queryPool, firstQuery, accelerationStructureCount, loc);
    }

    // Same idea as RecordEndQueries
    for (uint32_t slot = firstQuery; slot < (firstQuery + accelerationStructureCount); slot++) {
        QueryObject query_obj = {queryPool, slot};
        active_queries.erase(query_obj);
        updated_queries.insert(query_obj);
    }
}

void CommandBuffer::UpdateSubpassAttachments() {
    ASSERT_AND_RETURN(active_render_pass);
    const auto& subpass = active_render_pass->create_info.pSubpasses[GetActiveSubpass()];
    assert(active_subpasses.size() == active_attachments.size());

    for (size_t i = 0; i < active_attachments.size(); ++i) {
        active_attachments[i].type = AttachmentInfo::Type::Empty;
    }

    for (uint32_t index = 0; index < subpass.inputAttachmentCount; ++index) {
        const uint32_t attachment_index = subpass.pInputAttachments[index].attachment;
        if (attachment_index != VK_ATTACHMENT_UNUSED) {
            active_attachments[attachment_index].type = AttachmentInfo::Type::Input;
            active_attachments[attachment_index].layout = subpass.pInputAttachments[index].layout;
            active_attachments[attachment_index].type_index = index;
            active_subpasses[attachment_index].used = true;
            active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
            active_subpasses[attachment_index].aspectMask = subpass.pInputAttachments[index].aspectMask;
        }
    }

    for (uint32_t index = 0; index < subpass.colorAttachmentCount; ++index) {
        const uint32_t attachment_index = subpass.pColorAttachments[index].attachment;
        if (attachment_index != VK_ATTACHMENT_UNUSED) {
            active_attachments[attachment_index].type = AttachmentInfo::Type::Color;
            active_attachments[attachment_index].layout = subpass.pColorAttachments[index].layout;
            active_attachments[attachment_index].type_index = index;
            active_color_attachments_index.insert(index);
            active_subpasses[attachment_index].used = true;
            active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
            active_subpasses[attachment_index].aspectMask = subpass.pColorAttachments[index].aspectMask;
        }
        if (subpass.pResolveAttachments) {
            const uint32_t attachment_index2 = subpass.pResolveAttachments[index].attachment;
            if (attachment_index2 != VK_ATTACHMENT_UNUSED) {
                active_attachments[attachment_index2].type = AttachmentInfo::Type::ColorResolve;
                active_attachments[attachment_index2].layout = subpass.pResolveAttachments[index].layout;
                active_attachments[attachment_index2].type_index = index;
                active_subpasses[attachment_index2].used = true;
                active_subpasses[attachment_index2].usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
                active_subpasses[attachment_index2].aspectMask = subpass.pResolveAttachments[index].aspectMask;
            }
        }
    }

    if (subpass.pDepthStencilAttachment) {
        const uint32_t attachment_index = subpass.pDepthStencilAttachment->attachment;
        if (attachment_index != VK_ATTACHMENT_UNUSED) {
            active_attachments[attachment_index].type = AttachmentInfo::Type::DepthStencil;
            active_attachments[attachment_index].layout = subpass.pDepthStencilAttachment->layout;
            // Look for potential dedicated stencil layout
            if (const auto* stencil_layout =
                    vku::FindStructInPNextChain<VkAttachmentReferenceStencilLayout>(subpass.pDepthStencilAttachment->pNext)) {
                active_attachments[attachment_index].separate_stencil_layout = stencil_layout->stencilLayout;
            }
            active_subpasses[attachment_index].used = true;
            active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
            active_subpasses[attachment_index].aspectMask = subpass.pDepthStencilAttachment->aspectMask;
        }
    }

    if (auto rdm_ci =
            vku::FindStructInPNextChain<VkRenderPassFragmentDensityMapCreateInfoEXT>(active_render_pass->create_info.pNext)) {
        const uint32_t attachment_index = rdm_ci->fragmentDensityMapAttachment.attachment;
        if (attachment_index != VK_ATTACHMENT_UNUSED) {
            active_attachments[attachment_index].type = AttachmentInfo::Type::FragmentDensityMap;
            active_attachments[attachment_index].layout = rdm_ci->fragmentDensityMapAttachment.layout;
            active_subpasses[attachment_index].used = true;
            active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT;
            active_subpasses[attachment_index].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        }
    }

    if (auto rdr_attachment_ci = vku::FindStructInPNextChain<VkFragmentShadingRateAttachmentInfoKHR>(subpass.pNext)) {
        if (rdr_attachment_ci->pFragmentShadingRateAttachment) {
            const uint32_t attachment_index = rdr_attachment_ci->pFragmentShadingRateAttachment->attachment;
            if (attachment_index != VK_ATTACHMENT_UNUSED) {
                active_attachments[attachment_index].type = AttachmentInfo::Type::FragmentShadingRate;
                active_attachments[attachment_index].layout = rdr_attachment_ci->pFragmentShadingRateAttachment->layout;
                active_subpasses[attachment_index].used = true;
                active_subpasses[attachment_index].usage = VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR;
                active_subpasses[attachment_index].aspectMask = rdr_attachment_ci->pFragmentShadingRateAttachment->aspectMask;
            }
        }
    }
}

// For non Dynamic Renderpass we update the attachments
void CommandBuffer::UpdateAttachmentsView(const VkRenderPassBeginInfo* pRenderPassBegin) {
    const bool imageless = (active_framebuffer->create_info.flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) != 0;
    const VkRenderPassAttachmentBeginInfo* attachment_info_struct = nullptr;
    if (pRenderPassBegin)
        attachment_info_struct = vku::FindStructInPNextChain<VkRenderPassAttachmentBeginInfo>(pRenderPassBegin->pNext);

    for (uint32_t i = 0; i < active_attachments.size(); ++i) {
        if (imageless) {
            if (attachment_info_struct && i < attachment_info_struct->attachmentCount) {
                active_attachments[i].image_view = dev_data.Get<vvl::ImageView>(attachment_info_struct->pAttachments[i]).get();
            }
        } else {
            active_attachments[i].image_view = active_framebuffer->attachments_view_state[i].get();
        }
    }

    // While updating the subpass we will set the active_attachments type
    UpdateSubpassAttachments();
}

void CommandBuffer::RecordBeginRenderPass(const VkRenderPassBeginInfo& render_pass_begin,
                                          const VkSubpassBeginInfo& subpass_begin_info, const Location& loc) {
    RecordCommand(loc);
    active_framebuffer = dev_data.Get<vvl::Framebuffer>(render_pass_begin.framebuffer);
    active_render_pass = dev_data.Get<vvl::RenderPass>(render_pass_begin.renderPass);
    render_area = render_pass_begin.renderArea;
    SetActiveSubpass(0);
    active_subpass_contents = subpass_begin_info.contents;
    render_pass_queries.clear();

    // Connect this RP to cmdBuffer
    if (!dev_data.disabled[command_buffer_state]) {
        AddChild(active_render_pass);
    }

    if (auto sample_locations_begin =
            vku::FindStructInPNextChain<VkRenderPassSampleLocationsBeginInfoEXT>(render_pass_begin.pNext)) {
        sample_locations_begin_info = sample_locations_begin;
    }

    if (auto rp_striped_begin = vku::FindStructInPNextChain<VkRenderPassStripeBeginInfoARM>(render_pass_begin.pNext)) {
        has_render_pass_striped = true;
        striped_count += rp_striped_begin->stripeInfoCount;
    }

    // Spec states that after BeginRenderPass all resources should be rebound
    if (active_render_pass->has_multiview_enabled) {
        UnbindResources();
    }

    auto chained_device_group_struct = vku::FindStructInPNextChain<VkDeviceGroupRenderPassBeginInfo>(render_pass_begin.pNext);
    render_pass_device_mask = chained_device_group_struct ? chained_device_group_struct->deviceMask : initial_device_mask;

    attachment_source = AttachmentSource::RenderPass;
    active_subpasses.clear();
    active_attachments.clear();

    if (active_framebuffer) {
        active_subpasses.resize(active_framebuffer->create_info.attachmentCount);
        active_attachments.resize(active_framebuffer->create_info.attachmentCount);
        UpdateAttachmentsView(&render_pass_begin);

        // Connect this framebuffer and its children to this cmdBuffer
        AddChild(active_framebuffer);
    }

    for (auto& item : sub_states_) {
        item.second->RecordBeginRenderPass(render_pass_begin, subpass_begin_info, loc);
    }
}

void CommandBuffer::RecordNextSubpass(const VkSubpassBeginInfo& subpass_begin_info, const VkSubpassEndInfo* subpass_end_info,
                                      const Location& loc) {
    RecordCommand(loc);
    SetActiveSubpass(GetActiveSubpass() + 1);
    active_subpass_contents = subpass_begin_info.contents;
    ASSERT_AND_RETURN(active_render_pass);

    if (active_framebuffer) {
        active_subpasses.clear();
        active_subpasses.resize(active_framebuffer->create_info.attachmentCount);

        if (GetActiveSubpass() < active_render_pass->create_info.subpassCount) {
            UpdateSubpassAttachments();
        }
    }

    // Spec states that after NextSubpass all resources should be rebound
    if (active_render_pass->has_multiview_enabled) {
        UnbindResources();
    }

    for (auto& item : sub_states_) {
        item.second->RecordNextSubpass(subpass_begin_info, subpass_end_info, loc);
    }
}

void CommandBuffer::RecordEndRenderPass(const VkSubpassEndInfo* subpass_end_info, const Location& loc) {
    // Call first so SubState can use render pass object before we destroy it
    for (auto& item : sub_states_) {
        item.second->RecordEndRenderPass(subpass_end_info, loc);
    }

    RecordCommand(loc);
    active_render_pass = nullptr;
    attachment_source = AttachmentSource::Empty;
    active_attachments.clear();
    active_subpasses.clear();
    active_color_attachments_index.clear();
    SetActiveSubpass(0);
    active_framebuffer = VK_NULL_HANDLE;
    sample_locations_begin_info = {};
    per_tile_execution_model_enabled = false;
}

static void InitDefaultRenderingAttachments(CommandBuffer::RenderingAttachment& attachments, uint32_t count) {
    attachments.color_locations.resize(count);
    attachments.color_indexes.resize(count);
    attachments.depth_index = nullptr;
    attachments.stencil_index = nullptr;
    attachments.set_color_locations = false;
    attachments.set_color_indexes = false;
    for (uint32_t i = 0; i < count; i++) {
        // Default from spec
        attachments.color_locations[i] = i;
        attachments.color_indexes[i] = i;
    }
}

void CommandBuffer::RecordBeginRendering(const VkRenderingInfo& rendering_info, const Location& loc) {
    RecordCommand(loc);
    active_render_pass = std::make_shared<vvl::RenderPass>(rendering_info);
    render_area = rendering_info.renderArea;
    render_pass_queries.clear();

    InitDefaultRenderingAttachments(rendering_attachments, rendering_info.colorAttachmentCount);

    auto chained_device_group_struct = vku::FindStructInPNextChain<VkDeviceGroupRenderPassBeginInfo>(rendering_info.pNext);
    render_pass_device_mask = chained_device_group_struct ? chained_device_group_struct->deviceMask : initial_device_mask;

    auto rp_striped_begin = vku::FindStructInPNextChain<VkRenderPassStripeBeginInfoARM>(rendering_info.pNext);
    if (rp_striped_begin) {
        has_render_pass_striped = true;
        striped_count += rp_striped_begin->stripeInfoCount;
    }

    active_subpass_contents = ((rendering_info.flags & VK_RENDERING_CONTENTS_SECONDARY_COMMAND_BUFFERS_BIT)
                                   ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS
                                   : VK_SUBPASS_CONTENTS_INLINE);

    // Track if the first render pass instance does resume
    if (!has_render_pass_instance && (rendering_info.flags & VK_RENDERING_RESUMING_BIT)) {
        resumes_render_pass_instance = true;
    }
    // Track the last suspension state. Notice that both RESUMING/SUSPENDING flags can be specified.
    // The ordering is that suspension action goes after resuming action.
    if (rendering_info.flags & VK_RENDERING_RESUMING_BIT) {
        last_suspend_state = SuspendState::Resumed;
    }
    if (rendering_info.flags & VK_RENDERING_SUSPENDING_BIT) {
        last_suspend_state = SuspendState::Suspended;
    }
    if (!first_rendering_info.has_value()) {
        first_rendering_info = &rendering_info;
        first_rendering_info_loc = std::make_unique<LocationCapture>(loc.dot(vvl::Field::pRenderingInfo));
    }
    last_rendering_info = &rendering_info;

    has_render_pass_instance = true;

    attachment_source = AttachmentSource::DynamicRendering;
    active_attachments.clear();
    // add 2 for the Depth and Stencil
    // multiple by 2 because every attachment might have a resolve
    uint32_t attachment_count = (rendering_info.colorAttachmentCount + 2) * 2;
    attachment_count += 1;  // FragmentDensityMap (doesn't need a resolve)
    attachment_count += 1;  // FragmentShadingRate (doesn't need a resolve)

    // Currently reserve the maximum possible size for |active_attachments| so when looping, we NEED to check for null
    active_attachments.resize(attachment_count);

    for (uint32_t i = 0; i < rendering_info.colorAttachmentCount; ++i) {
        const auto& rendering_attachment = rendering_info.pColorAttachments[i];
        if (rendering_attachment.imageView != VK_NULL_HANDLE) {
            auto& color_attachment = active_attachments[GetDynamicRenderingColorAttachmentIndex(i)];
            color_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_attachment.imageView).get();
            color_attachment.type = AttachmentInfo::Type::Color;
            color_attachment.layout = rendering_attachment.imageLayout;
            color_attachment.type_index = i;
            active_color_attachments_index.insert(i);
            if (color_attachment.image_view) {
                TrackImageViewFirstLayout(*color_attachment.image_view, rendering_attachment.imageLayout,
                                          "VUID-vkCmdBeginRendering-pRenderingInfo-09592");
            }
            if (rendering_attachment.resolveMode != VK_RESOLVE_MODE_NONE &&
                rendering_attachment.resolveImageView != VK_NULL_HANDLE) {
                auto& resolve_attachment = active_attachments[GetDynamicRenderingColorResolveAttachmentIndex(i)];
                resolve_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_attachment.resolveImageView).get();
                resolve_attachment.type = AttachmentInfo::Type::ColorResolve;
                resolve_attachment.layout = rendering_attachment.resolveImageLayout;
                resolve_attachment.type_index = i;
            }
        }
    }

    if (rendering_info.pDepthAttachment && rendering_info.pDepthAttachment->imageView != VK_NULL_HANDLE) {
        auto& depth_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::Depth)];
        depth_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_info.pDepthAttachment->imageView).get();
        depth_attachment.type = AttachmentInfo::Type::Depth;
        depth_attachment.layout = rendering_info.pDepthAttachment->imageLayout;
        if (depth_attachment.image_view) {
            TrackDepthAttachmentFirstLayout(*depth_attachment.image_view, rendering_info.pDepthAttachment->imageLayout,
                                            "VUID-vkCmdBeginRendering-pRenderingInfo-09588");
        }
        if (rendering_info.pDepthAttachment->resolveMode != VK_RESOLVE_MODE_NONE &&
            rendering_info.pDepthAttachment->resolveImageView != VK_NULL_HANDLE) {
            auto& resolve_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::DepthResolve)];
            resolve_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_info.pDepthAttachment->resolveImageView).get();
            resolve_attachment.type = AttachmentInfo::Type::DepthResolve;
            resolve_attachment.layout = rendering_info.pDepthAttachment->resolveImageLayout;
        }
    }

    if (rendering_info.pStencilAttachment && rendering_info.pStencilAttachment->imageView != VK_NULL_HANDLE) {
        auto& stencil_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::Stencil)];
        stencil_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_info.pStencilAttachment->imageView).get();
        stencil_attachment.type = AttachmentInfo::Type::Stencil;
        stencil_attachment.layout = rendering_info.pStencilAttachment->imageLayout;
        if (stencil_attachment.image_view) {
            TrackStencilAttachmentFirstLayout(*stencil_attachment.image_view, rendering_info.pStencilAttachment->imageLayout,
                                              "VUID-vkCmdBeginRendering-pRenderingInfo-09590");
        }
        if (rendering_info.pStencilAttachment->resolveMode != VK_RESOLVE_MODE_NONE &&
            rendering_info.pStencilAttachment->resolveImageView != VK_NULL_HANDLE) {
            auto& resolve_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::StencilResolve)];
            resolve_attachment.image_view = dev_data.Get<vvl::ImageView>(rendering_info.pStencilAttachment->resolveImageView).get();
            resolve_attachment.type = AttachmentInfo::Type::StencilResolve;
            resolve_attachment.layout = rendering_info.pStencilAttachment->resolveImageLayout;
        }
    }

    if (auto fragment_density_map_info =
            vku::FindStructInPNextChain<VkRenderingFragmentDensityMapAttachmentInfoEXT>(rendering_info.pNext)) {
        auto& fdm_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::FragmentDensityMap)];
        fdm_attachment.image_view = dev_data.Get<vvl::ImageView>(fragment_density_map_info->imageView).get();
        fdm_attachment.type = AttachmentInfo::Type::FragmentDensityMap;
        fdm_attachment.layout = fragment_density_map_info->imageLayout;
        if (fdm_attachment.image_view) {
            TrackImageViewFirstLayout(*fdm_attachment.image_view, fdm_attachment.layout,
                                      "VUID-vkCmdBeginRendering-imageView-12276");
        }
    }

    if (auto fsr_attachment_info =
            vku::FindStructInPNextChain<VkRenderingFragmentShadingRateAttachmentInfoKHR>(rendering_info.pNext)) {
        auto& fsr_attachment = active_attachments[GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type::FragmentShadingRate)];
        fsr_attachment.image_view = dev_data.Get<vvl::ImageView>(fsr_attachment_info->imageView).get();
        fsr_attachment.type = AttachmentInfo::Type::FragmentShadingRate;
        fsr_attachment.layout = fsr_attachment_info->imageLayout;
        if (fsr_attachment.image_view) {
            TrackImageViewFirstLayout(*fsr_attachment.image_view, fsr_attachment.layout,
                                      "VUID-vkCmdBeginRendering-imageView-12277");
        }
    }

    for (auto& item : sub_states_) {
        item.second->RecordBeginRendering(rendering_info, loc);
    }
}

void CommandBuffer::RecordEndRendering(const VkRenderingEndInfoEXT* pRenderingEndInfo, const Location& loc) {
    // Call first so SubState can use render pass object before we destroy it
    for (auto& item : sub_states_) {
        item.second->RecordEndRendering(pRenderingEndInfo);
    }

    RecordCommand(loc);
    active_render_pass = nullptr;
    active_color_attachments_index.clear();
    per_tile_execution_model_enabled = false;
}

void CommandBuffer::RecordBeginCustomResolve(const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordBeginCustomResolve();
    }
}

void CommandBuffer::RecordBeginVideoCoding(const VkVideoBeginCodingInfoKHR& begin_info, const Location& loc) {
    RecordCommand(loc);
    bound_video_session = dev_data.Get<vvl::VideoSession>(begin_info.videoSession);
    ASSERT_AND_RETURN(bound_video_session);
    bound_video_session_parameters = dev_data.Get<vvl::VideoSessionParameters>(begin_info.videoSessionParameters);

    // Connect this video session to cmdBuffer
    if (!dev_data.disabled[command_buffer_state]) {
        AddChild(bound_video_session);
    }

    if (bound_video_session_parameters) {
        // Connect this video session parameters object to cmdBuffer
        if (!dev_data.disabled[command_buffer_state]) {
            AddChild(bound_video_session_parameters);
        }
    }

    // Need to record substate first
    for (auto& item : sub_states_) {
        item.second->RecordBeginVideoCoding(*bound_video_session, begin_info, loc);
    }

    if (bound_video_session->IsEncode()) {
        video_encode_rate_control_state = VideoEncodeRateControlState(bound_video_session->GetCodecOp(), &begin_info);
        video_encode_quality_level.reset();
    }

    if (begin_info.referenceSlotCount > 0) {
        size_t deactivated_slot_count = 0;

        for (uint32_t i = 0; i < begin_info.referenceSlotCount; ++i) {
            // Initialize the set of bound video picture resources
            if (begin_info.pReferenceSlots[i].pPictureResource != nullptr) {
                int32_t slot_index = begin_info.pReferenceSlots[i].slotIndex;
                vvl::VideoPictureResource res(dev_data, *begin_info.pReferenceSlots[i].pPictureResource);
                bound_video_picture_resources.emplace(std::make_pair(res, slot_index));

                if (!dev_data.disabled[command_buffer_state]) {
                    if (auto reference_image_view =
                            dev_data.Get<vvl::ImageView>(begin_info.pReferenceSlots[i].pPictureResource->imageViewBinding)) {
                        // Connect the image view to cmdBuffer
                        AddChild(reference_image_view);
                    }
                }
            }

            if (begin_info.pReferenceSlots[i].slotIndex >= 0 && begin_info.pReferenceSlots[i].pPictureResource == nullptr) {
                deactivated_slot_count++;
            }
        }

        if (deactivated_slot_count > 0) {
            std::vector<int32_t> deactivated_slots{};
            deactivated_slots.reserve(deactivated_slot_count);
            for (uint32_t i = 0; i < begin_info.referenceSlotCount; ++i) {
                if (begin_info.pReferenceSlots[i].slotIndex >= 0 && begin_info.pReferenceSlots[i].pPictureResource == nullptr) {
                    deactivated_slots.emplace_back(begin_info.pReferenceSlots[i].slotIndex);
                }
            }

            // Enqueue submission time DPB slot deactivation
            video_session_updates[bound_video_session->VkHandle()].emplace_back(
                [deactivated_slots](const vvl::VideoSession* vs_state, vvl::VideoSessionDeviceState& dev_state, bool do_validate) {
                    for (const auto& slot_index : deactivated_slots) {
                        dev_state.Deactivate(slot_index);
                    }
                    return false;
                });
        }
    }
}

void CommandBuffer::RecordEndVideoCoding(const Location& loc) {
    RecordCommand(loc);
    bound_video_session = nullptr;
    bound_video_session_parameters = nullptr;
    bound_video_picture_resources.clear();
    video_encode_quality_level.reset();
}

void CommandBuffer::RecordControlVideoCoding(const VkVideoCodingControlInfoKHR& control_info, const Location& loc) {
    RecordCommand(loc);
    if (!bound_video_session) {
        return;
    }

    // Need to record substate first
    for (auto& item : sub_states_) {
        item.second->RecordControlVideoCoding(*bound_video_session, control_info, loc);
    }

    if (control_info.flags & VK_VIDEO_CODING_CONTROL_RESET_BIT_KHR) {
        // Remove DPB slot index association for bound video picture resources
        for (auto& binding : bound_video_picture_resources) {
            binding.second = -1;
        }

        // Enqueue submission time video session state reset/initialization
        video_session_updates[bound_video_session->VkHandle()].emplace_back(
            [](const vvl::VideoSession* vs_state, vvl::VideoSessionDeviceState& dev_state, bool do_validate) {
                dev_state.Reset();
                return false;
            });
    }

    if (bound_video_session->IsEncode() && control_info.flags & VK_VIDEO_CODING_CONTROL_ENCODE_RATE_CONTROL_BIT_KHR) {
        auto state = VideoEncodeRateControlState(bound_video_session->GetCodecOp(), &control_info);
        if (state) {
            video_encode_rate_control_state = state;

            // Enqueue rate control specific device state changes
            video_session_updates[bound_video_session->VkHandle()].emplace_back(
                [state](const vvl::VideoSession* vs_state, vvl::VideoSessionDeviceState& dev_state, bool do_validate) {
                    dev_state.SetRateControlState(state);
                    return false;
                });
        }
    }

    if (bound_video_session->IsEncode() && control_info.flags & VK_VIDEO_CODING_CONTROL_ENCODE_QUALITY_LEVEL_BIT_KHR) {
        auto quality_level_info = vku::FindStructInPNextChain<VkVideoEncodeQualityLevelInfoKHR>(control_info.pNext);
        if (quality_level_info != nullptr) {
            uint32_t quality_level = quality_level_info->qualityLevel;
            video_encode_quality_level = quality_level;

            // Enqueue encode quality level device state change
            video_session_updates[bound_video_session->VkHandle()].emplace_back(
                [quality_level](const vvl::VideoSession* vs_state, vvl::VideoSessionDeviceState& dev_state, bool do_validate) {
                    dev_state.SetEncodeQualityLevel(quality_level);
                    return false;
                });
        }
    }
}

void vvl::CommandBuffer::RecordVideoInlineQueries(const VkVideoInlineQueryInfoKHR& query_info) {
    for (auto& item : sub_states_) {
        item.second->RecordVideoInlineQueries(query_info);
    }

    for (uint32_t i = 0; i < query_info.queryCount; i++) {
        updated_queries.insert(QueryObject(query_info.queryPool, query_info.firstQuery + i));

        // Connect this query pool to cmdBuffer
        if (!dev_data.disabled[command_buffer_state]) {
            if (auto pool_state = dev_data.Get<vvl::QueryPool>(query_info.queryPool)) {
                AddChild(pool_state);
            }
        }
    }
}

void vvl::CommandBuffer::RecordVideoEncodeQuantizationMap(const VkVideoEncodeQuantizationMapInfoKHR &quant_map_info) {
    for (auto& item : sub_states_) {
        item.second->RecordVideoEncodeQuantizationMap(quant_map_info);
    }

    // Connect the quantization map image view to cmdBuffer
    if (!dev_data.disabled[command_buffer_state]) {
        if (auto quant_map_image_view_state = dev_data.Get<vvl::ImageView>(quant_map_info.quantizationMap)) {
            AddChild(quant_map_image_view_state);
        }
    }
}

void CommandBuffer::RecordDecodeVideo(const VkVideoDecodeInfoKHR& decode_info, const Location& loc) {
    RecordCommand(loc);
    if (!bound_video_session) {
        return;
    }

    // Need to record substate first
    for (auto& item : sub_states_) {
        item.second->RecordDecodeVideo(*bound_video_session, decode_info, loc);
    }

    if (!dev_data.disabled[command_buffer_state]) {
        // Connect the bitstream buffer to cmdBuffer
        if (auto bitstream_buffer_state = dev_data.Get<vvl::Buffer>(decode_info.srcBuffer)) {
            AddChild(bitstream_buffer_state);
        }

        // Connect the decode output image view to cmdBuffer
        if (auto output_image_view_state = dev_data.Get<vvl::ImageView>(decode_info.dstPictureResource.imageViewBinding)) {
            AddChild(output_image_view_state);
        }
    }

    if (decode_info.pSetupReferenceSlot && decode_info.pSetupReferenceSlot->pPictureResource) {
        vvl::VideoReferenceSlot setup_slot(dev_data, *bound_video_session->profile, *decode_info.pSetupReferenceSlot);

        // Update bound video picture resource DPB slot index association
        bound_video_picture_resources[setup_slot.resource] = setup_slot.index;

        // Enqueue submission time reference slot setup or invalidation
        bool reference_setup_requested = bound_video_session->ReferenceSetupRequested(decode_info);
        video_session_updates[bound_video_session->VkHandle()].emplace_back(
            [setup_slot, reference_setup_requested](const vvl::VideoSession* vs_state, vvl::VideoSessionDeviceState& dev_state,
                                                    bool do_validate) {
                if (reference_setup_requested) {
                    dev_state.Activate(setup_slot.index, setup_slot.picture_id, setup_slot.resource);
                } else {
                    dev_state.Invalidate(setup_slot.index, setup_slot.picture_id);
                }
                return false;
            });
    }

    // Update active query indices
    for (auto& query : active_queries) {
        uint32_t op_count = bound_video_session->GetVideoDecodeOperationCount(&decode_info);
        query.active_query_index += op_count;
    }

    // Update inline queries
    if (bound_video_session->create_info.flags & VK_VIDEO_SESSION_CREATE_INLINE_QUERIES_BIT_KHR) {
        const auto inline_query_info = vku::FindStructInPNextChain<VkVideoInlineQueryInfoKHR>(decode_info.pNext);
        if (inline_query_info != nullptr && inline_query_info->queryPool != VK_NULL_HANDLE) {
            RecordVideoInlineQueries(*inline_query_info);
        }
    }
}

void vvl::CommandBuffer::RecordEncodeVideo(const VkVideoEncodeInfoKHR& encode_info, const Location& loc) {
    RecordCommand(loc);
    if (!bound_video_session) {
        return;
    }

    // Need to record substate first
    for (auto& item : sub_states_) {
        item.second->RecordEncodeVideo(*bound_video_session, encode_info, loc);
    }

    if (!dev_data.disabled[command_buffer_state]) {
        // Connect the encode input image view to cmdBuffer
        if (auto input_image_view_state = dev_data.Get<vvl::ImageView>(encode_info.srcPictureResource.imageViewBinding)) {
            AddChild(input_image_view_state);
        }

        // Connect the bitstream buffer to cmdBuffer
        if (auto bitstream_buffer_state = dev_data.Get<vvl::Buffer>(encode_info.dstBuffer)) {
            AddChild(bitstream_buffer_state);
        }
    }

    if (encode_info.pSetupReferenceSlot && encode_info.pSetupReferenceSlot->pPictureResource) {
        vvl::VideoReferenceSlot setup_slot(dev_data, *bound_video_session->profile, *encode_info.pSetupReferenceSlot);

        // Update bound video picture resource DPB slot index association
        bound_video_picture_resources[setup_slot.resource] = setup_slot.index;

        // Enqueue submission time reference slot setup or invalidation
        bool reference_setup_requested = bound_video_session->ReferenceSetupRequested(encode_info);
        video_session_updates[bound_video_session->VkHandle()].emplace_back(
            [setup_slot, reference_setup_requested](const vvl::VideoSession* vs_state, vvl::VideoSessionDeviceState& dev_state,
                                                    bool do_validate) {
                if (reference_setup_requested) {
                    dev_state.Activate(setup_slot.index, setup_slot.picture_id, setup_slot.resource);
                } else {
                    dev_state.Invalidate(setup_slot.index, setup_slot.picture_id);
                }
                return false;
            });
    }

    // Handle quantization map info
    if ((encode_info.flags & (VK_VIDEO_ENCODE_WITH_QUANTIZATION_DELTA_MAP_BIT_KHR | VK_VIDEO_ENCODE_WITH_EMPHASIS_MAP_BIT_KHR)) != 0) {
        const auto quant_map_info = vku::FindStructInPNextChain<VkVideoEncodeQuantizationMapInfoKHR>(encode_info.pNext);
        if (quant_map_info != nullptr) {
            RecordVideoEncodeQuantizationMap(*quant_map_info);
        }
    }

    // Update active query indices
    for (auto& query : active_queries) {
        uint32_t op_count = bound_video_session->GetVideoEncodeOperationCount(&encode_info);
        query.active_query_index += op_count;
    }

    // Update inline queries
    if (bound_video_session->create_info.flags & VK_VIDEO_SESSION_CREATE_INLINE_QUERIES_BIT_KHR) {
        const auto inline_query_info = vku::FindStructInPNextChain<VkVideoInlineQueryInfoKHR>(encode_info.pNext);
        if (inline_query_info != nullptr && inline_query_info->queryPool != VK_NULL_HANDLE) {
            RecordVideoInlineQueries(*inline_query_info);
        }
    }
}

static void SetRenderingAttachmentLocations(CommandBuffer::RenderingAttachment& attachments,
                                            const VkRenderingAttachmentLocationInfo* pLocationInfo) {
    attachments.color_locations.resize(pLocationInfo->colorAttachmentCount);
    const uint32_t* locations = pLocationInfo->pColorAttachmentLocations;
    for (uint32_t i = 0; i < pLocationInfo->colorAttachmentCount; ++i) {
        attachments.color_locations[i] = locations ? locations[i] : i;
    }
}

static void SetRenderingInputAttachmentIndices(CommandBuffer::RenderingAttachment& attachments,
                                               const VkRenderingInputAttachmentIndexInfo* pLocationInfo) {
    attachments.color_indexes.resize(pLocationInfo->colorAttachmentCount);
    const uint32_t* indexes = pLocationInfo->pColorAttachmentInputIndices;
    for (uint32_t i = 0; i < pLocationInfo->colorAttachmentCount; ++i) {
        attachments.color_indexes[i] = indexes ? indexes[i] : i;
    }
    if (pLocationInfo->pDepthInputAttachmentIndex) {
        attachments.depth_index_storage = *pLocationInfo->pDepthInputAttachmentIndex;
        attachments.depth_index = &attachments.depth_index_storage;
    } else {
        attachments.depth_index = nullptr;
    }
    if (pLocationInfo->pStencilInputAttachmentIndex) {
        attachments.stencil_index_storage = *pLocationInfo->pStencilInputAttachmentIndex;
        attachments.stencil_index = &attachments.stencil_index_storage;
    } else {
        attachments.stencil_index = nullptr;
    }
}

void CommandBuffer::Begin(const VkCommandBufferBeginInfo* pBeginInfo) {
    if (IsRecorded(state)) {
        Location loc(Func::vkBeginCommandBuffer);
        Reset(loc);
    }

    // Set updated state here in case implicit reset occurs above
    state = CbState::Recording;
    ASSERT_AND_RETURN(pBeginInfo);

    begin_info_flags = pBeginInfo->flags;

    if (pBeginInfo->pInheritanceInfo && IsSecondary()) {
        // pInheritanceInfo could be valid, but ignored, if in a primary command buffer
        has_inheritance = true;
        inheritance_info.initialize(pBeginInfo->pInheritanceInfo);

        // If we are a secondary command-buffer and inheriting.  Update the items we should inherit.
        if (begin_info_flags & VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT) {
            if (inheritance_info.renderPass) {
                active_render_pass = dev_data.Get<vvl::RenderPass>(inheritance_info.renderPass);
                SetActiveSubpass(inheritance_info.subpass);

                if (inheritance_info.framebuffer) {
                    active_framebuffer = dev_data.Get<vvl::Framebuffer>(inheritance_info.framebuffer);
                    attachment_source = AttachmentSource::Inheritance;
                    active_subpasses.clear();
                    active_attachments.clear();

                    if (active_framebuffer) {
                        active_subpasses.resize(active_framebuffer->create_info.attachmentCount);
                        active_attachments.resize(active_framebuffer->create_info.attachmentCount);
                        UpdateAttachmentsView(nullptr);

                        // Connect this framebuffer and its children to this cmdBuffer
                        if (!dev_data.disabled[command_buffer_state]) {
                            AddChild(active_framebuffer);
                        }
                    }
                }
            } else {
                auto inheritance_rendering_info =
                    vku::FindStructInPNextChain<VkCommandBufferInheritanceRenderingInfo>(pBeginInfo->pInheritanceInfo->pNext);
                if (inheritance_rendering_info) {
                    active_render_pass = std::make_shared<vvl::RenderPass>(inheritance_rendering_info);

                    InitDefaultRenderingAttachments(rendering_attachments, inheritance_rendering_info->colorAttachmentCount);
                    if (auto locations =
                            vku::FindStructInPNextChain<VkRenderingAttachmentLocationInfo>(pBeginInfo->pInheritanceInfo->pNext)) {
                        SetRenderingAttachmentLocations(rendering_attachments, locations);
                    }
                    if (auto indexes =
                            vku::FindStructInPNextChain<VkRenderingInputAttachmentIndexInfo>(pBeginInfo->pInheritanceInfo->pNext)) {
                        SetRenderingInputAttachmentIndices(rendering_attachments, indexes);
                    }
                }
            }
        }

        if (auto descriptor_heap_info =
                vku::FindStructInPNextChain<VkCommandBufferInheritanceDescriptorHeapInfoEXT>(pBeginInfo->pInheritanceInfo->pNext)) {
            if (descriptor_heap_info->pResourceHeapBindInfo) {
                descriptor_heap.Update(*descriptor_heap_info->pResourceHeapBindInfo, false);
                descriptor_heap.is_resource_inherited = true;
            }
            if (descriptor_heap_info->pSamplerHeapBindInfo) {
                descriptor_heap.Update(*descriptor_heap_info->pSamplerHeapBindInfo, true);
                descriptor_heap.is_sampler_inherited = true;
            }
        }
    }

    auto chained_device_group_struct = vku::FindStructInPNextChain<VkDeviceGroupCommandBufferBeginInfo>(pBeginInfo->pNext);
    if (chained_device_group_struct) {
        initial_device_mask = chained_device_group_struct->deviceMask;
    } else {
        initial_device_mask = (1 << dev_data.physical_device_count) - 1;
    }

    performance_lock_acquired = dev_data.performance_lock_acquired;
    updated_queries.clear();

    for (auto& item : sub_states_) {
        item.second->Begin(*pBeginInfo);
    }
}

void CommandBuffer::End(VkResult result) {
    if (result == VK_SUCCESS) {
        state = CbState::Recorded;
    }
    for (auto& item : sub_states_) {
        item.second->End();
    }
}

void CommandBuffer::RecordExecuteCommands(vvl::span<const VkCommandBuffer> secondary_command_buffers, const Location& loc) {
    RecordCommand(loc);
    uint32_t cmd_index = 0;
    for (const VkCommandBuffer sub_command_buffer : secondary_command_buffers) {
        auto secondary_cb_state = dev_data.GetWrite<CommandBuffer>(sub_command_buffer);
        ASSERT_AND_RETURN(secondary_cb_state);
        if (!(secondary_cb_state->begin_info_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT)) {
            if (begin_info_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT) {
                // TODO: Because this is a state change, clearing the VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT needs to be moved
                // from the validation step to the recording step
                begin_info_flags &= ~VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
            }
        }

        // Propagate inital layout and current layout state to the primary cmd buffer
        // NOTE: The update/population of the image_layout_map is done in CoreChecks, but for other classes derived from
        // Device these maps will be empty, so leaving the propagation in the the state tracker should be a no-op
        // for those other classes.
        for (const auto& [image, secondary_cb_layout_map] : secondary_cb_state->image_layout_registry) {
            const auto image_state = dev_data.Get<vvl::Image>(image);
            if (!image_state || image_state->Destroyed() || !secondary_cb_layout_map ||
                image_state->GetId() != secondary_cb_layout_map->image_id) {
                continue;
            }
            if (auto cb_layout_map = GetOrCreateImageLayoutMap(*image_state)) {
                struct Updater {
                    void update(ImageLayoutState& dst, const ImageLayoutState& src) const {
                        if (src.current_layout != kInvalidLayout && src.current_layout != dst.current_layout) {
                            dst.current_layout = src.current_layout;
                        }
                    }
                    std::optional<ImageLayoutState> insert(const ImageLayoutState& src) const {
                        return std::optional<ImageLayoutState>(vvl::in_place, src);
                    }
                };
                sparse_container::splice(*cb_layout_map, *secondary_cb_layout_map, Updater());
            }
        }

        secondary_cb_state->primary_command_buffer = VkHandle();
        linked_command_buffers.insert(secondary_cb_state.get());
        AddChild(secondary_cb_state);

        for (auto& event : secondary_cb_state->events) {
            events.push_back(event);
        }

        if (first_action_or_sync_command == Func::Empty) {
            first_action_or_sync_command = secondary_cb_state->first_action_or_sync_command;
        }

        // Handle secondary command buffer updates for dynamic rendering.
        if (!has_render_pass_instance) {
            resumes_render_pass_instance = secondary_cb_state->resumes_render_pass_instance;
        }
        has_render_pass_instance |= secondary_cb_state->has_render_pass_instance;
        if (secondary_cb_state->last_suspend_state != SuspendState::Empty) {
            last_suspend_state = secondary_cb_state->last_suspend_state;
        }
        if (!first_rendering_info.has_value() && secondary_cb_state->first_rendering_info.has_value()) {
            first_rendering_info = secondary_cb_state->first_rendering_info;
            first_rendering_info_loc = std::make_unique<LocationCapture>(secondary_cb_state->first_rendering_info_loc->Get());
        }
        if (secondary_cb_state->last_rendering_info.has_value()) {
            last_rendering_info = secondary_cb_state->last_rendering_info;
        }

        if (secondary_cb_state->descriptor_heap.sampler_bound && !secondary_cb_state->descriptor_heap.is_sampler_inherited) {
            descriptor_heap.sampler_bound = false;
            descriptor_heap.is_sampler_invalidated = true;
        }
        if (secondary_cb_state->descriptor_heap.resource_bound && !secondary_cb_state->descriptor_heap.is_resource_inherited) {
            descriptor_heap.resource_bound = false;
            descriptor_heap.is_resource_invalidated = true;
        }

        // Handle debug labels
        label_stack_depth_ += secondary_cb_state->label_stack_depth_;
        label_commands_.insert(label_commands_.end(), secondary_cb_state->label_commands_.begin(),
                               secondary_cb_state->label_commands_.end());

        for (auto& item : sub_states_) {
            item.second->RecordExecuteCommand(*secondary_cb_state, cmd_index, loc);
        }

        cmd_index++;
    }
}

void CommandBuffer::PushDescriptorSetState(VkPipelineBindPoint pipelineBindPoint,
                                           std::shared_ptr<const vvl::PipelineLayout> pipeline_layout, uint32_t set,
                                           uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites,
                                           const Location& loc) {
    // Short circuit invalid updates
    if ((set >= pipeline_layout->set_layouts.list.size()) || !pipeline_layout->set_layouts.list[set] ||
        !pipeline_layout->set_layouts.list[set]->IsPushDescriptor()) {
        return;
    }

    // We need a descriptor set to update the bindings with, compatible with the passed layout
    const auto& dsl = pipeline_layout->set_layouts.list[set];
    auto& last_bound = lastBound[ConvertToVvlBindPoint(pipelineBindPoint)];
    auto& push_descriptor_set = last_bound.push_descriptor_set;
    // If we are disturbing the current push_desriptor_set clear it
    if (!push_descriptor_set || !last_bound.IsBoundSetCompatible(set, *pipeline_layout)) {
        last_bound.UnbindAndResetPushDescriptorSet(dev_data.CreatePushDescriptorSet(dsl));
    }

    UpdateLastBoundDescriptorSets(pipelineBindPoint, pipeline_layout, set, 1, nullptr, push_descriptor_set, 0, nullptr, loc);

    // Now that we have either the new or extant push_descriptor set ... do the write updates against it
    push_descriptor_set->PerformPushDescriptorsUpdate(*this, descriptorWriteCount, pDescriptorWrites);
}

// Generic function to handle state update for all CmdDraw* type functions
void CommandBuffer::RecordDraw(const Location& loc) {
    RecordCommand(loc);
    LastBound& last_bound = lastBound[vvl::BindPointGraphics];
    for (auto& item : sub_states_) {
        item.second->RecordActionCommand(last_bound, loc);
    }
}

// Generic function to handle state update for all CmdDispatch* type functions
void CommandBuffer::RecordDispatch(const Location& loc) {
    RecordCommand(loc);
    LastBound& last_bound = lastBound[vvl::BindPointCompute];
    for (auto& item : sub_states_) {
        item.second->RecordActionCommand(last_bound, loc);
    }
}

// Generic function to handle state update for all CmdTraceRay* type functions
void CommandBuffer::RecordTraceRay(const Location& loc) {
    RecordCommand(loc);
    LastBound& last_bound = lastBound[vvl::BindPointRayTracing];
    for (auto& item : sub_states_) {
        item.second->RecordActionCommand(last_bound, loc);
    }
}

void CommandBuffer::RecordExecuteGeneratedCommands(const VkGeneratedCommandsInfoEXT& info, const Location& loc) {
    const VkPipelineBindPoint bind_point = ConvertStageToBindPoint(info.shaderStages);
    if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) {
        RecordDraw(loc);
    } else if (bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) {
        RecordDispatch(loc);
    } else if (bind_point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR) {
        RecordTraceRay(loc);
    }

    for (auto& item : sub_states_) {
        item.second->RecordExecuteGeneratedCommands(info, bind_point, loc);
    }
}

void CommandBuffer::RecordBindPipeline(VkPipelineBindPoint bind_point, vvl::Pipeline& pipeline) {
    auto& last_bound = lastBound[ConvertToVvlBindPoint(bind_point)];
    last_bound.BindPipeline(&pipeline);

    for (auto& item : sub_states_) {
        item.second->RecordBindPipeline(bind_point, pipeline);
    }

    if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) {
        dynamic_state_status.pipeline.reset();

        // Make a copy and then xor the new change
        // This gives us which state has been invalidated, allows us to save time for most cases where nothing changes
        CBDynamicFlags invalidated_state = dynamic_state_status.cb;

        // Spec: "[dynamic state] made invalid by another pipeline bind with that state specified as static"
        // So unset the bitmask for the command buffer lifetime tracking (unless ignored, keep set)
        dynamic_state_status.cb &= (pipeline.dynamic_state | pipeline.ignored_dynamic_state);

        invalidated_state ^= dynamic_state_status.cb;
        if (invalidated_state.any()) {
            // Reset dynamic state values
            dynamic_state_value.reset(invalidated_state);

            for (int index = 1; index < CB_DYNAMIC_STATE_STATUS_NUM; ++index) {
                CBDynamicState status = static_cast<CBDynamicState>(index);
                if (invalidated_state[status]) {
                    invalidated_state_pipe[index] = pipeline.VkHandle();
                }
            }
        }

        if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT) &&
            !pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE) && pipeline.vertex_input_state) {
            for (const auto& [binding_index, binding_state] : pipeline.vertex_input_state->bindings) {
                current_vertex_buffer_binding_info[binding_index].stride = binding_state.desc.stride;
            }
        }

        if (!dev_data.enabled_features.variableMultisampleRate) {
            if (const auto* multisample_state = pipeline.MultisampleState(); multisample_state) {
                if (const auto& render_pass = active_render_pass) {
                    const uint32_t subpass = GetActiveSubpass();
                    // if render pass uses no attachment, all bound pipelines in the same subpass must have the same
                    // pMultisampleState->rasterizationSamples. To check that, record pMultisampleState->rasterizationSamples of the
                    // first bound pipeline.
                    if (render_pass->UsesNoAttachment(subpass)) {
                        if (std::optional<VkSampleCountFlagBits> subpass_rasterization_samples =
                                GetActiveSubpassRasterizationSampleCount();
                            !subpass_rasterization_samples) {
                            SetActiveSubpassRasterizationSampleCount(multisample_state->rasterizationSamples);
                        }
                    }
                }
            }
        }

    } else if (bind_point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR) {
        dynamic_state_status.rtx_stack_size_pipeline = false;
        if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_RAY_TRACING_PIPELINE_STACK_SIZE_KHR)) {
            dynamic_state_status.rtx_stack_size_cb = false;  // invalidated
        }
    }

    dirty_static_state = false;
}

void CommandBuffer::RecordBindShaderObject(VkShaderStageFlagBits shader_stage,
                                           const std::shared_ptr<ShaderObject>& shader_object_state) {
    auto& last_bound = lastBound[ConvertStageToVvlBindPoint(shader_stage)];
    last_bound.BindShaderObject(shader_stage, shader_object_state);
}

// Helper for descriptor set (and buffer) updates.
static bool PushDescriptorCleanup(LastBound& last_bound, uint32_t set_idx) {
    // All uses are from loops over ds_slots, but just in case..
    assert(set_idx < last_bound.ds_slots.size());

    auto descriptor_set = last_bound.ds_slots[set_idx].ds_state.get();
    if (descriptor_set && descriptor_set->IsPushDescriptor()) {
        assert(descriptor_set == last_bound.push_descriptor_set.get());
        last_bound.push_descriptor_set = nullptr;
        return true;
    }
    return true;
}

// Update pipeline_layout bind points applying the "Pipeline Layout Compatibility" rules.
// One of pDescriptorSets or push_descriptor_set should be nullptr, indicating whether this
// is called for CmdBindDescriptorSets or CmdPushDescriptorSet.
void CommandBuffer::UpdateLastBoundDescriptorSets(VkPipelineBindPoint pipeline_bind_point,
                                                  std::shared_ptr<const vvl::PipelineLayout> pipeline_layout, uint32_t first_set,
                                                  uint32_t set_count, const VkDescriptorSet* pDescriptorSets,
                                                  std::shared_ptr<vvl::DescriptorSet>& push_descriptor_set,
                                                  uint32_t dynamic_offset_count, const uint32_t* p_dynamic_offsets,
                                                  const Location& loc) {
    ASSERT_AND_RETURN((pDescriptorSets == nullptr) ^ (push_descriptor_set == nullptr));

    uint32_t required_size = first_set + set_count;
    const uint32_t last_binding_index = required_size - 1;
    ASSERT_AND_RETURN(last_binding_index < pipeline_layout->set_compat_ids.size());

    auto& last_bound = lastBound[ConvertToVvlBindPoint(pipeline_bind_point)];
    last_bound.desc_set_pipeline_layout = pipeline_layout;
    last_bound.desc_set_bound_command = loc.function;
    last_bound.SetDescriptorMode(DescriptorModeClassic, loc.function);
    auto& pipe_compat_ids = pipeline_layout->set_compat_ids;
    // Resize binding arrays
    if (last_binding_index >= last_bound.ds_slots.size()) {
        last_bound.ds_slots.resize(required_size);
    }
    const uint32_t current_size = static_cast<uint32_t>(last_bound.ds_slots.size());

    // Clean up the "disturbed" before and after the range to be set
    if (required_size < current_size) {
        if (last_bound.ds_slots[last_binding_index].compat_id_for_set != pipe_compat_ids[last_binding_index]) {
            // We're disturbing those after last, we'll shrink below, but first need to check for and cleanup the push_descriptor
            for (auto set_idx = required_size; set_idx < current_size; ++set_idx) {
                if (PushDescriptorCleanup(last_bound, set_idx)) {
                    break;
                }
            }
        } else {
            // We're not disturbing past last, so leave the upper binding data alone.
            required_size = current_size;
        }
    }

    // We resize if we need more set entries or if those past "last" are disturbed
    if (required_size != current_size) {
        last_bound.ds_slots.resize(required_size);
    }

    // For any previously bound sets, need to set them to "invalid" if they were disturbed by this update
    for (uint32_t set_idx = 0; set_idx < first_set; ++set_idx) {
        auto& ds_slot = last_bound.ds_slots[set_idx];
        if (ds_slot.compat_id_for_set != pipe_compat_ids[set_idx]) {
            PushDescriptorCleanup(last_bound, set_idx);
            ds_slot.Reset();
            ds_slot.compat_id_for_set = pipe_compat_ids[set_idx];
        }
    }

    // Now update the bound sets with the input sets
    const uint32_t* input_dynamic_offsets = p_dynamic_offsets;  // "read" pointer for dynamic offset data
    for (uint32_t input_idx = 0; input_idx < set_count; input_idx++) {
        auto set_idx = input_idx + first_set;  // set_idx is index within layout, input_idx is index within input descriptor sets
        auto& ds_slot = last_bound.ds_slots[set_idx];
        auto descriptor_set =
            push_descriptor_set ? push_descriptor_set : dev_data.Get<vvl::DescriptorSet>(pDescriptorSets[input_idx]);

        ds_slot.Reset();
        // Record binding (or push)
        if (descriptor_set != last_bound.push_descriptor_set) {
            // Only cleanup the push descriptors if they aren't the currently used set.
            PushDescriptorCleanup(last_bound, set_idx);
        }
        ds_slot.ds_state = descriptor_set;
        ds_slot.compat_id_for_set = pipe_compat_ids[set_idx];  // compat ids are canonical *per* set index

        if (descriptor_set) {
            auto set_dynamic_descriptor_count = descriptor_set->GetDynamicDescriptorCount();
            // TODO: Add logic for tracking push_descriptor offsets (here or in caller)
            if (set_dynamic_descriptor_count && input_dynamic_offsets) {
                const uint32_t* end_offset = input_dynamic_offsets + set_dynamic_descriptor_count;
                ds_slot.dynamic_offsets = std::vector<uint32_t>(input_dynamic_offsets, end_offset);
                input_dynamic_offsets = end_offset;
                assert(input_dynamic_offsets <= (p_dynamic_offsets + dynamic_offset_count));
            } else {
                ds_slot.dynamic_offsets.clear();
            }
        }
    }

    for (auto& item : sub_states_) {
        item.second->UpdateLastBoundDescriptorSets(pipeline_bind_point, loc);
    }
}

void CommandBuffer::UpdateLastBoundDescriptorBuffers(LastBound& last_bound,
                                                     std::shared_ptr<const vvl::PipelineLayout> pipeline_layout, uint32_t first_set,
                                                     uint32_t set_count, const uint32_t* buffer_indicies,
                                                     const VkDeviceSize* buffer_offsets) {
    uint32_t required_size = first_set + set_count;
    const uint32_t last_binding_index = required_size - 1;
    assert(last_binding_index < pipeline_layout->set_compat_ids.size());

    last_bound.desc_set_pipeline_layout = pipeline_layout;
    auto& pipe_compat_ids = pipeline_layout->set_compat_ids;
    // Resize binding arrays
    if (last_binding_index >= last_bound.ds_slots.size()) {
        last_bound.ds_slots.resize(required_size);
    }
    const uint32_t current_size = static_cast<uint32_t>(last_bound.ds_slots.size());

    // Clean up the "disturbed" before and after the range to be set
    if (required_size < current_size) {
        if (last_bound.ds_slots[last_binding_index].compat_id_for_set != pipe_compat_ids[last_binding_index]) {
            // We're disturbing those after last, we'll shrink below, but first need to check for and cleanup the push_descriptor
            for (auto set_idx = required_size; set_idx < current_size; ++set_idx) {
                if (PushDescriptorCleanup(last_bound, set_idx)) {
                    break;
                }
            }
        } else {
            // We're not disturbing past last, so leave the upper binding data alone.
            required_size = current_size;
        }
    }

    // We resize if we need more set entries or if those past "last" are disturbed
    if (required_size != current_size) {
        last_bound.ds_slots.resize(required_size);
    }

    // For any previously bound push descriptor sets, need to set them to "invalid" if they were disturbed by this update
    for (uint32_t set_idx = 0; set_idx < first_set; ++set_idx) {
        PushDescriptorCleanup(last_bound, set_idx);
    }

    // Now update the bound sets with the input sets
    for (uint32_t input_idx = 0; input_idx < set_count; input_idx++) {
        auto set_idx = input_idx + first_set;  // set_idx is index within layout, input_idx is index within input descriptor sets
        auto& ds_slot = last_bound.ds_slots[set_idx];
        ds_slot.Reset();

        // Record binding
        ds_slot.descriptor_buffer_binding = {vvl::kNoIndex32, buffer_indicies[input_idx], buffer_offsets[input_idx]};
        ds_slot.compat_id_for_set = pipe_compat_ids[set_idx];  // compat ids are canonical *per* set index
    }
}

// Make assumption that there is no reason to ONLY have embedded samplers set and that CmdSetDescriptorBufferOffsetsEXT will be
// called
void CommandBuffer::UpdateLastBoundDescriptorBuffersEmbedded(LastBound& last_bound,
                                                             std::shared_ptr<const vvl::PipelineLayout> pipeline_layout,
                                                             uint32_t set) {
    auto& pipe_compat_ids = pipeline_layout->set_compat_ids;

    if (set >= last_bound.ds_slots.size()) {
        last_bound.ds_slots.resize(set + 1);
    }

    auto& ds_slot = last_bound.ds_slots[set];
    ds_slot.Reset();

    ds_slot.descriptor_buffer_binding = {set, 0, 0};
    ds_slot.compat_id_for_set = pipe_compat_ids[set];
}

// Set image layout for given subresource range
void CommandBuffer::SetImageLayout(const vvl::Image& image_state, const VkImageSubresourceRange& normalized_subresource_range,
                                   VkImageLayout layout, VkImageLayout expected_layout) {
    if (auto image_layout_map = GetOrCreateImageLayoutMap(image_state)) {
        if (image_state.subresource_encoder.InRange(normalized_subresource_range)) {
            RangeGenerator range_gen(image_state.subresource_encoder, normalized_subresource_range);
            if (UpdateCurrentLayout(*image_layout_map, std::move(range_gen), layout, expected_layout,
                                    normalized_subresource_range.aspectMask)) {
                image_layout_change_count++;  // Change the version of this data to force revalidation
            }
        }
    }
}

void CommandBuffer::TrackImageViewFirstLayout(const vvl::ImageView& view_state, VkImageLayout layout,
                                              const char* submit_time_layout_mismatch_vuid) {
    if (auto image_layout_map = GetOrCreateImageLayoutMap(*view_state.image_state.get())) {
        RangeGenerator range_gen(view_state.range_generator);
        TrackFirstLayout(*image_layout_map, std::move(range_gen), layout, view_state.normalized_subresource_range.aspectMask,
                         submit_time_layout_mismatch_vuid);
    }
}

void CommandBuffer::TrackDepthAttachmentFirstLayout(const vvl::ImageView& view_state, VkImageLayout layout,
                                                    const char* submit_time_layout_mismatch_vuid) {
    if (auto image_layout_map = GetOrCreateImageLayoutMap(*view_state.image_state.get())) {
        // According to the spec for dynamic rendering depth attachment, we must ignore
        // the aspect used to create the image view and use the DEPTH aspect instead
        VkImageSubresourceRange image_layout_range = view_state.GetRangeGeneratorRange(dev_data.extensions);
        image_layout_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
        RangeGenerator range_gen(view_state.image_state->subresource_encoder, image_layout_range);

        TrackFirstLayout(*image_layout_map, std::move(range_gen), layout, VK_IMAGE_ASPECT_DEPTH_BIT,
                         submit_time_layout_mismatch_vuid);
    }
}

void CommandBuffer::TrackStencilAttachmentFirstLayout(const vvl::ImageView& view_state, VkImageLayout layout,
                                                      const char* submit_time_layout_mismatch_vuid) {
    if (auto image_layout_map = GetOrCreateImageLayoutMap(*view_state.image_state.get())) {
        // According to the spec for dynamic rendering stencil attachment, we must ignore
        // the aspect used to create the image view and use the STENCIL aspect instead
        VkImageSubresourceRange image_layout_range = view_state.GetRangeGeneratorRange(dev_data.extensions);
        image_layout_range.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
        RangeGenerator range_gen(view_state.image_state->subresource_encoder, image_layout_range);

        TrackFirstLayout(*image_layout_map, std::move(range_gen), layout, VK_IMAGE_ASPECT_STENCIL_BIT,
                         submit_time_layout_mismatch_vuid);
    }
}

void CommandBuffer::TrackImageFirstLayout(const vvl::Image& image_state, const VkImageSubresourceRange& subresource_range,
                                          int32_t depth_offset, uint32_t depth_extent, VkImageLayout layout) {
    if (auto image_layout_map = GetOrCreateImageLayoutMap(image_state)) {
        VkImageSubresourceRange normalized_subresource_range = image_state.NormalizeSubresourceRange(subresource_range);

        if (depth_extent != 0 && CanTransitionDepthSlices(dev_data.extensions, image_state.GetImageType(), image_state.create_flags)) {
            normalized_subresource_range.baseArrayLayer = (uint32_t)depth_offset;
            normalized_subresource_range.layerCount = depth_extent;
        }
        if (image_state.subresource_encoder.InRange(normalized_subresource_range)) {
            RangeGenerator range_gen(image_state.subresource_encoder, normalized_subresource_range);
            TrackFirstLayout(*image_layout_map, std::move(range_gen), layout, normalized_subresource_range.aspectMask, nullptr);
        }
    }
}

void CommandBuffer::SetImageViewLayout(const vvl::ImageView& view_state, VkImageLayout layout, VkImageLayout layoutStencil) {
    const vvl::Image* image_state = view_state.image_state.get();

    VkImageSubresourceRange sub_range = view_state.GetRangeGeneratorRange(dev_data.extensions);

    if (sub_range.aspectMask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) && layoutStencil != kInvalidLayout) {
        sub_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
        SetImageLayout(*image_state, sub_range, layout);
        sub_range.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
        SetImageLayout(*image_state, sub_range, layoutStencil);
    } else {
        // If layoutStencil is kInvalidLayout (meaning no separate depth/stencil layout), image view format has both depth and
        // stencil aspects, and subresource has only one of aspect out of depth or stencil, then the missing aspect will also be
        // transitioned and thus must be included explicitly
        if (const VkFormat format = view_state.create_info.format; vkuFormatIsDepthAndStencil(format)) {
            if (layoutStencil == kInvalidLayout &&
                (sub_range.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT))) {
                sub_range.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
            }
        }
        SetImageLayout(*image_state, sub_range, layout);
    }
}

void CommandBuffer::RecordStateCmd(CBDynamicState state) {
    // NOTE: this can be extended to use RecordCommand for state commands if needed (currently not needed)
    command_count++;
    RecordDynamicState(state);

    vvl::Pipeline* pipeline = GetLastBoundGraphics().pipeline_state;
    if (pipeline && !pipeline->IsDynamic(state)) {
        dirty_static_state = true;
    }
}

void CommandBuffer::RecordDynamicState(CBDynamicState state) {
    dynamic_state_status.cb.set(state);
    dynamic_state_status.pipeline.set(state);
    dynamic_state_status.history.set(state);
}

void CommandBuffer::RecordSetViewport(uint32_t first_viewport, uint32_t viewport_count, const VkViewport* viewports) {
    RecordStateCmd(CB_DYNAMIC_STATE_VIEWPORT);
    if (dynamic_state_value.viewports.size() < first_viewport + viewport_count) {
        dynamic_state_value.viewports.resize(first_viewport + viewport_count);
    }
    for (size_t i = 0; i < viewport_count; ++i) {
        dynamic_state_value.viewports[first_viewport + i] = viewports[i];
    }
    for (auto& item : sub_states_) {
        item.second->RecordSetViewport(first_viewport, viewport_count);
    }
}

void CommandBuffer::RecordSetViewportWithCount(uint32_t viewport_count, const VkViewport* viewports) {
    RecordStateCmd(CB_DYNAMIC_STATE_VIEWPORT_WITH_COUNT);
    dynamic_state_value.viewport_count = viewport_count;
    dynamic_state_value.viewports.resize(viewport_count);
    for (size_t i = 0; i < viewport_count; ++i) {
        dynamic_state_value.viewports[i] = viewports[i];
    }

    for (auto& item : sub_states_) {
        item.second->RecordSetViewportWithCount(viewport_count);
    }
}

void CommandBuffer::RecordSetScissor(uint32_t first_scissor, uint32_t scissor_count) {
    RecordStateCmd(CB_DYNAMIC_STATE_SCISSOR);
    for (auto& item : sub_states_) {
        item.second->RecordSetScissor(first_scissor, scissor_count);
    }
}

void CommandBuffer::RecordSetScissorWithCount(uint32_t scissor_count) {
    RecordStateCmd(CB_DYNAMIC_STATE_SCISSOR_WITH_COUNT);
    dynamic_state_value.scissor_count = scissor_count;
    for (auto& item : sub_states_) {
        item.second->RecordSetScissorWithCount(scissor_count);
    }
}

void CommandBuffer::RecordSetDepthCompareOp(VkCompareOp depth_compare_op) {
    RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_COMPARE_OP);
    for (auto& item : sub_states_) {
        item.second->RecordSetDepthCompareOp(depth_compare_op);
    }
}
void CommandBuffer::RecordSetDepthTestEnable(VkBool32 depth_test_enable) {
    RecordStateCmd(CB_DYNAMIC_STATE_DEPTH_TEST_ENABLE);
    dynamic_state_value.depth_test_enable = depth_test_enable;
    for (auto& item : sub_states_) {
        item.second->RecordSetDepthTestEnable(depth_test_enable);
    }
}

void CommandBuffer::RecordCopyBuffer(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count,
                                     const VkBufferCopy* regions, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyBuffer(src_buffer_state, dst_buffer_state, region_count, regions, loc);
    }
}

void CommandBuffer::RecordCopyBuffer2(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count,
                                      const VkBufferCopy2* regions, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyBuffer2(src_buffer_state, dst_buffer_state, region_count, regions, loc);
    }
}

void CommandBuffer::RecordBindIndexbuffer(const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordBindIndexbuffer();
    }
}

void CommandBuffer::RecordSetPrimitiveRestartIndex(uint32_t primitive_restart_index, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordSetPrimitiveRestartIndex(primitive_restart_index);
    }
}

void CommandBuffer::RecordCopyImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout src_image_layout,
                                    VkImageLayout dst_image_layout, uint32_t region_count, const VkImageCopy* regions,
                                    const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyImage(src_image_state, dst_image_state, src_image_layout, dst_image_layout, region_count, regions,
                                     loc);
    }
}

void CommandBuffer::RecordCopyImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout src_image_layout,
                                     VkImageLayout dst_image_layout, uint32_t region_count, const VkImageCopy2* regions,
                                     const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyImage2(src_image_state, dst_image_state, src_image_layout, dst_image_layout, region_count, regions,
                                      loc);
    }
}

void CommandBuffer::RecordCopyBufferToImage(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state,
                                            VkImageLayout dst_image_layout, uint32_t region_count, const VkBufferImageCopy* regions,
                                            const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyBufferToImage(src_buffer_state, dst_image_state, dst_image_layout, region_count, regions, loc);
    }
}

void CommandBuffer::RecordCopyBufferToImage2(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state,
                                             VkImageLayout dst_image_layout, uint32_t region_count,
                                             const VkBufferImageCopy2* regions, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyBufferToImage2(src_buffer_state, dst_image_state, dst_image_layout, region_count, regions, loc);
    }
}

void CommandBuffer::RecordCopyImageToBuffer(vvl::Image& src_image_state, vvl::Buffer& dst_buffer_state,
                                            VkImageLayout src_image_layout, uint32_t region_count, const VkBufferImageCopy* regions,
                                            const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyImageToBuffer(src_image_state, dst_buffer_state, src_image_layout, region_count, regions, loc);
    }
}

void CommandBuffer::RecordCopyImageToBuffer2(vvl::Image& src_image_state, vvl::Buffer& dst_buffer_state,
                                             VkImageLayout src_image_layout, uint32_t region_count,
                                             const VkBufferImageCopy2* regions, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyImageToBuffer2(src_image_state, dst_buffer_state, src_image_layout, region_count, regions, loc);
    }
}

void CommandBuffer::RecordCopyImageToMemory(vvl::Image& src_image_state, uint32_t region_count,
                                            const VkDeviceMemoryImageCopyKHR* regions, const Location& loc) {
    RecordCommand(loc);

    for (auto& item : sub_states_) {
        item.second->RecordCopyImageToMemory(src_image_state, region_count, regions, loc);
    }
}

void CommandBuffer::RecordCopyMemoryToImage(vvl::Image& dst_image_state, uint32_t region_count,
                                            const VkDeviceMemoryImageCopyKHR* regions, const Location& loc) {
    RecordCommand(loc);

    for (auto& item : sub_states_) {
        item.second->RecordCopyMemoryToImage(dst_image_state, region_count, regions, loc);
    }
}

void CommandBuffer::RecordCopyMemory(uint32_t region_count, const VkDeviceMemoryCopyKHR* regions, const Location& loc) {
    RecordCommand(loc);
}

void CommandBuffer::RecordBlitImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout src_image_layout,
                                    VkImageLayout dst_image_layout, uint32_t region_count, const VkImageBlit* regions,
                                    const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordBlitImage(src_image_state, dst_image_state, src_image_layout, dst_image_layout, region_count, regions,
                                     loc);
    }
}

void CommandBuffer::RecordCopyMemoryIndirect(const VkCopyMemoryIndirectInfoKHR& info, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyMemoryIndirect(info, loc);
    }
}

void CommandBuffer::RecordCopyMemoryToImageIndirect(const VkCopyMemoryToImageIndirectInfoKHR& info, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordCopyMemoryToImageIndirect(info, loc);
    }
}

void CommandBuffer::RecordBlitImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout src_image_layout,
                                     VkImageLayout dst_image_layout, uint32_t region_count, const VkImageBlit2* regions,
                                     const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordBlitImage2(src_image_state, dst_image_state, src_image_layout, dst_image_layout, region_count, regions,
                                      loc);
    }
}

void CommandBuffer::RecordResolveImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count,
                                       const VkImageResolve* regions, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordResolveImage(src_image_state, dst_image_state, region_count, regions, loc);
    }
}

void CommandBuffer::RecordResolveImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count,
                                        const VkImageResolve2* regions, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordResolveImage2(src_image_state, dst_image_state, region_count, regions, loc);
    }
}

void CommandBuffer::RecordClearColorImage(vvl::Image& image_state, VkImageLayout image_layout,
                                          const VkClearColorValue* color_values, uint32_t range_count,
                                          const VkImageSubresourceRange* ranges, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordClearColorImage(image_state, image_layout, color_values, range_count, ranges, loc);
    }
}

void CommandBuffer::RecordClearDepthStencilImage(vvl::Image& image_state, VkImageLayout image_layout,
                                                 const VkClearDepthStencilValue* depth_stencil_values, uint32_t range_count,
                                                 const VkImageSubresourceRange* ranges, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordClearDepthStencilImage(image_state, image_layout, depth_stencil_values, range_count, ranges, loc);
    }
}

void CommandBuffer::RecordClearAttachments(uint32_t attachment_count, const VkClearAttachment* pAttachments, uint32_t rect_count,
                                           const VkClearRect* pRects, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordClearAttachments(attachment_count, pAttachments, rect_count, pRects, loc);
    }
}

void CommandBuffer::RecordFillBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordFillBuffer(buffer_state, offset, size, loc);
    }
}

void CommandBuffer::RecordUpdateBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordUpdateBuffer(buffer_state, offset, size, loc);
    }
}

void CommandBuffer::RecordFillMemory(VkDeviceAddressRangeKHR range, const Location& loc) {
    RecordCommand(loc);
    // TODO - Sync val will want the range
    (void)range;
}

void CommandBuffer::RecordUpdateMemory(VkDeviceAddressRangeKHR range, const Location& loc) {
    RecordCommand(loc);
    // TODO - Sync val will want the range
    (void)range;
}

void CommandBuffer::RecordSetEvent(VkEvent event, VkPipelineStageFlags stage_mask, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordSetEvent(event, stage_mask);
    }
    if (!dev_data.disabled[command_buffer_state]) {
        if (auto event_state = dev_data.Get<vvl::Event>(event)) {
            AddChild(event_state);
        }
    }
    events.push_back(event);
}

void CommandBuffer::RecordSetEvent2(VkEvent event, const VkDependencyInfo& dependency_info, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordSetEvent2(event, dependency_info, loc);
    }
    if (!dev_data.disabled[command_buffer_state]) {
        if (auto event_state = dev_data.Get<vvl::Event>(event)) {
            AddChild(event_state);
        }
    }
    events.push_back(event);
}

void CommandBuffer::RecordResetEvent(VkEvent event, VkPipelineStageFlags2 stage_mask, const Location& loc) {
    RecordCommand(loc);
    for (auto& item : sub_states_) {
        item.second->RecordResetEvent(event, stage_mask, loc);
    }
    if (!dev_data.disabled[command_buffer_state]) {
        if (auto event_state = dev_data.Get<vvl::Event>(event)) {
            AddChild(event_state);
        }
    }
    events.push_back(event);
}

void CommandBuffer::RecordWaitEvents(vvl::span<const VkEvent> events, VkPipelineStageFlags src_stage_mask,
                                     VkPipelineStageFlags dst_stage_mask, const Location& loc) {
    for (auto& item : sub_states_) {
        item.second->RecordWaitEvents(events, src_stage_mask, dst_stage_mask, loc);
    }
    for (const VkEvent event : events) {
        if (!dev_data.disabled[command_buffer_state]) {
            if (auto event_state = dev_data.Get<vvl::Event>(event)) {
                AddChild(event_state);
            }
        }
        this->events.push_back(event);
    }
}

void CommandBuffer::RecordWaitEvent2(VkEvent event, const VkDependencyInfo& dependency_info, const Location& dep_info_loc) {
    for (auto& item : sub_states_) {
        item.second->RecordWaitEvent2(event, dependency_info, dep_info_loc);
    }
    if (!dev_data.disabled[command_buffer_state]) {
        if (auto event_state = dev_data.Get<vvl::Event>(event)) {
            AddChild(event_state);
        }
    }
    events.push_back(event);
}

void CommandBuffer::RecordBarrierObjects(uint32_t buffer_barrier_count, const VkBufferMemoryBarrier* buffer_barriers,
                                         uint32_t image_barrier_count, const VkImageMemoryBarrier* image_barriers,
                                         VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
                                         const Location& loc) {
    if (!dev_data.disabled[command_buffer_state]) {
        for (uint32_t i = 0; i < buffer_barrier_count; i++) {
            if (auto buffer_state = dev_data.Get<vvl::Buffer>(buffer_barriers[i].buffer)) {
                AddChild(buffer_state);
            }
        }
        for (uint32_t i = 0; i < image_barrier_count; i++) {
            if (auto image_state = dev_data.Get<vvl::Image>(image_barriers[i].image)) {
                AddChild(image_state);
            }
        }
    }

    for (auto& item : sub_states_) {
        item.second->RecordBarriers(buffer_barrier_count, buffer_barriers, image_barrier_count, image_barriers, src_stage_mask,
                                    dst_stage_mask, loc);
    }
}

void CommandBuffer::RecordBarrierObjects(const VkDependencyInfo& dep_info, const Location& loc) {
    if (!dev_data.disabled[command_buffer_state]) {
        for (uint32_t i = 0; i < dep_info.bufferMemoryBarrierCount; i++) {
            if (auto buffer_state = dev_data.Get<vvl::Buffer>(dep_info.pBufferMemoryBarriers[i].buffer)) {
                AddChild(buffer_state);
            }
        }
        for (uint32_t i = 0; i < dep_info.imageMemoryBarrierCount; i++) {
            if (auto image_state = dev_data.Get<vvl::Image>(dep_info.pImageMemoryBarriers[i].image)) {
                AddChild(image_state);
            }
        }
    }

    // TODO - When moving here, these were not in CoreCheck, need to understand if we want SetEvents or not to validate the same
    // things as WaitEvent/PipelineBarriers
    if (loc.function != vvl::Func::vkCmdSetEvent2 && loc.function != vvl::Func::vkCmdSetEvent2KHR) {
        for (auto& item : sub_states_) {
            item.second->RecordBarriers2(dep_info, loc);
        }
    }
}

void CommandBuffer::RecordPushConstants(const vvl::PipelineLayout& pipeline_layout_state, VkShaderStageFlags stage_flags,
                                        uint32_t offset, uint32_t size, const void* values) {
    // Discussed in details in https://github.com/KhronosGroup/Vulkan-Docs/issues/1081
    // Internal discussion and CTS were written to prove that this is not called after an incompatible vkCmdBindPipeline
    // "Binding a pipeline with a layout that is not compatible with the push constant layout does not disturb the push constant
    // values"
    //
    // vkCmdBindDescriptorSet has nothing to do with push constants and don't need to call this after neither
    //
    // Part of this assumes apps at draw/dispatch/traceRays/etc time will have it properly compatible or else other VU will be
    // triggered
    if (push_constant_ranges_layout != pipeline_layout_state.push_constant_ranges_layout) {
        push_constant_ranges_layout = pipeline_layout_state.push_constant_ranges_layout;
        for (auto& item : sub_states_) {
            item.second->ClearPushConstants();
        }
    }

    for (auto& item : sub_states_) {
        item.second->RecordPushConstants(pipeline_layout_state.VkHandle(), stage_flags, offset, size, values);

        // vkspec.html#descriptorheaps-invalidate-sets
        item.second->ClearPushData();
        push_data_dword_mask.clear();
    }
}

void CommandBuffer::RecordPushData(const VkPushDataInfoEXT& push_data_info, const Location& loc) {
    RecordCommand(loc);

    if (push_data_info.data.size > 0) {
        const size_t begin_byte = push_data_info.offset;
        const size_t end_byte = push_data_info.offset + push_data_info.data.size;
        // offset/size required to be multiple of 4
        const size_t begin_dword = begin_byte / 4;
        const size_t end_dword = end_byte / 4;
        if (push_data_dword_mask.size() < end_dword) {
            push_data_dword_mask.resize(end_dword, false);
        }
        for (size_t i = begin_dword; i < end_dword; ++i) {
            push_data_dword_mask[i] = true;
        }
    }

    for (auto& item : sub_states_) {
        item.second->RecordPushData(push_data_info);
        item.second->ClearPushConstants();  // vkspec.html#descriptorheaps-invalidate-sets
    }
}

bool CommandBuffer::VerifyPushData(uint32_t offset_byte, uint32_t size_byte) const {
    // no other reason we should need this function
    assert(size_byte == 4 || size_byte == 8);
    // offset required to be a multiple of 4
    const uint32_t dwords_used = (offset_byte + size_byte) / 4;
    if (dwords_used > push_data_dword_mask.size()) {
        return false;
    }
    uint32_t dword_offset = offset_byte / 4;
    if (!push_data_dword_mask[dword_offset]) {
        return false;
    }
    if (size_byte == 8 && !push_data_dword_mask[dword_offset + 1]) {
        return false;
    }
    return true;
}

void CommandBuffer::RecordBindDescriptorBuffer(vvl::DeviceState& device_state, uint32_t buffer_count,
                                               const VkDescriptorBufferBindingInfoEXT* binding_infos, const Location& loc) {
    descriptor_buffer.ever_bound = true;

    descriptor_buffer.binding_info.resize(buffer_count);
    for (uint32_t i = 0; i < buffer_count; i++) {
        const VkDescriptorBufferBindingInfoEXT& binding_info = binding_infos[i];
        VkBufferUsageFlags2 buffer_usage = binding_info.usage;
        if (const auto usage_flags2 = vku::FindStructInPNextChain<VkBufferUsageFlags2CreateInfo>(binding_info.pNext)) {
            buffer_usage = usage_flags2->usage;
        }

        descriptor_buffer.binding_info[i] = {binding_info.address, buffer_usage};
    }
}

void CommandBuffer::RecordBindResourceHeap(vvl::DeviceState& device_state, const VkBindHeapInfoEXT& bind_info,
                                           const Location& loc) {
    RecordCommand(loc);

    vvl::range<VkDeviceAddress> old_reserved = descriptor_heap.resource_reserved;

    // need to do first as the substate expects this to be set
    descriptor_heap.Update(bind_info, false);

    if (descriptor_heap.resource_reserved != old_reserved) {
        device_state.UpdateCommandBufferHeapReservedAddressMap(this, descriptor_heap.resource_reserved, false);
    }

    SetDescriptorMode(DescriptorModeHeap, loc.function);
    device_state.TrackDeviceAddressRange(*this, descriptor_heap.resource_range, VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT);

    for (auto& item : sub_states_) {
        item.second->RecordBindResourceHeap();
    }
}

void CommandBuffer::RecordBindSamplerHeap(vvl::DeviceState& device_state, const VkBindHeapInfoEXT& bind_info, const Location& loc) {
    RecordCommand(loc);

    vvl::range<VkDeviceAddress> old_reserved = descriptor_heap.sampler_reserved;

    // need to do first as the substate expects this to be set
    descriptor_heap.Update(bind_info, true);

    if (descriptor_heap.sampler_reserved != old_reserved) {
        device_state.UpdateCommandBufferHeapReservedAddressMap(this, descriptor_heap.sampler_reserved, true);
    }

    SetDescriptorMode(DescriptorModeHeap, loc.function);
    device_state.TrackDeviceAddressRange(*this, descriptor_heap.sampler_range, VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT);

    for (auto& item : sub_states_) {
        item.second->RecordBindSamplerHeap();
    }
}

void CommandBuffer::RecordBeginConditionalRendering(const Location& loc) {
    RecordCommand(loc);
    conditional_rendering_active = true;
    conditional_rendering_inside_render_pass = active_render_pass != nullptr;
    conditional_rendering_subpass = GetActiveSubpass();
}

void CommandBuffer::RecordEndConditionalRendering(const Location& loc) {
    RecordCommand(loc);
    conditional_rendering_active = false;
    conditional_rendering_inside_render_pass = false;
    conditional_rendering_subpass = 0;
}

void CommandBuffer::RecordSetRenderingAttachmentLocations(const VkRenderingAttachmentLocationInfo* pLocationInfo,
                                                          const Location& loc) {
    RecordCommand(loc);
    rendering_attachments.set_color_locations = true;
    SetRenderingAttachmentLocations(rendering_attachments, pLocationInfo);
}

void CommandBuffer::RecordSetRenderingInputAttachmentIndices(const VkRenderingInputAttachmentIndexInfo* pLocationInfo,
                                                             const Location& loc) {
    RecordCommand(loc);
    rendering_attachments.set_color_indexes = true;
    SetRenderingInputAttachmentIndices(rendering_attachments, pLocationInfo);
}

void CommandBuffer::SubmitTimeValidate(Queue& queue_state, uint32_t perf_submit_pass, const Location& loc) {
    for (const auto& it : video_session_updates) {
        auto video_session_state = dev_data.Get<vvl::VideoSession>(it.first);
        auto device_state = video_session_state->DeviceStateWrite();
        for (const auto& function : it.second) {
            function(video_session_state.get(), *device_state, /*do_validate*/ false);
        }
    }

    for (auto& item : sub_states_) {
        item.second->Submit(queue_state, perf_submit_pass, loc);
    }
}

uint32_t CommandBuffer::GetDynamicRenderingColorAttachmentCount() const {
    return active_render_pass ? active_render_pass->dynamic_rendering_color_attachment_count : 0;
}

uint32_t CommandBuffer::GetDynamicRenderingAttachmentIndex(AttachmentInfo::Type type) const {
    // The first indexes are the color attachments, multiply by 2 as each has a resolve attachment index
    const uint32_t color_offset = 2 * GetDynamicRenderingColorAttachmentCount();
    switch (type) {
        case AttachmentInfo::Type::Depth:
            return color_offset;
        case AttachmentInfo::Type::DepthResolve:
            return color_offset + 1;
        case AttachmentInfo::Type::Stencil:
            return color_offset + 2;
        case AttachmentInfo::Type::StencilResolve:
            return color_offset + 3;
        case AttachmentInfo::Type::FragmentDensityMap:
            return color_offset + 4;
        case AttachmentInfo::Type::FragmentShadingRate:
            return color_offset + 5;
        default:
            assert(false);
    }
    return 0;
}

uint32_t CommandBuffer::GetViewMask() const {
    if (!active_render_pass) {
        return 0;
    } else if (active_render_pass->UsesDynamicRendering()) {
        return active_render_pass->GetDynamicRenderingViewMask();
    } else {
        return active_render_pass->create_info.pSubpasses[GetActiveSubpass()].viewMask;
    }
}

bool CommandBuffer::HasExternalFormatResolveAttachment() const {
    if (active_render_pass && active_render_pass->use_dynamic_rendering &&
        active_render_pass->dynamic_rendering_begin_rendering_info.colorAttachmentCount > 0) {
        return active_render_pass->dynamic_rendering_begin_rendering_info.pColorAttachments->resolveMode ==
               VK_RESOLVE_MODE_EXTERNAL_FORMAT_DOWNSAMPLE_BIT_ANDROID;
    }
    return false;
}

// Only called for Graphics and during Multiview
// "When multiview is enabled, at the beginning of each subpass all non-render pass state is undefined."
void CommandBuffer::UnbindResources() {
    // Vertex and index buffers
    index_buffer_binding = {};
    current_vertex_buffer_binding_info.clear();

    // Push constants
    push_constant_ranges_layout.reset();

    // Reset status of graphics cb to force rebinding of all resources
    dynamic_state_status.cb.reset();
    dynamic_state_status.pipeline.reset();
    dynamic_state_status.history.reset();

    // Pipeline and descriptor sets
    lastBound[vvl::BindPointGraphics].Reset();
}

LogObjectList CommandBuffer::GetObjectList(VkShaderStageFlagBits stage) const {
    LogObjectList objlist(handle_);
    const auto& last_bound = lastBound[ConvertStageToVvlBindPoint(stage)];
    const auto* pipeline_state = last_bound.pipeline_state;

    if (pipeline_state) {
        objlist.add(pipeline_state->Handle());
    } else if (VkShaderEXT shader = last_bound.GetShaderObject(VkShaderStageToShaderObjectStage(stage))) {
        objlist.add(shader);
    }
    return objlist;
}

LogObjectList CommandBuffer::GetObjectList(VkPipelineBindPoint pipeline_bind_point) const {
    LogObjectList objlist(handle_);

    const auto& last_bound = lastBound[ConvertToVvlBindPoint(pipeline_bind_point)];
    const auto* pipeline_state = last_bound.pipeline_state;

    if (pipeline_state) {
        objlist.add(pipeline_state->Handle());
    } else if (pipeline_bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) {
        if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::COMPUTE)) {
            objlist.add(shader);
        }
    } else if (pipeline_bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) {
        // If using non-compute, need to check all graphics stages
        if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::VERTEX)) {
            objlist.add(shader);
        }
        if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::TESSELLATION_CONTROL)) {
            objlist.add(shader);
        }
        if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::TESSELLATION_EVALUATION)) {
            objlist.add(shader);
        }
        if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::GEOMETRY)) {
            objlist.add(shader);
        }
        if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::FRAGMENT)) {
            objlist.add(shader);
        }
        if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::MESH)) {
            objlist.add(shader);
        }
        if (VkShaderEXT shader = last_bound.GetShaderObject(ShaderObjectStage::TASK)) {
            objlist.add(shader);
        }
    }

    // If using dynamic rendering, will just not add anything
    if (pipeline_bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS && active_render_pass) {
        objlist.add(active_render_pass->Handle());
    }

    return objlist;
}

void CommandBuffer::BeginLabel(const char* label_name) {
    ++label_stack_depth_;
    label_commands_.emplace_back(LabelCommand{true, label_name});
}

void CommandBuffer::EndLabel() {
    --label_stack_depth_;
    label_commands_.emplace_back(LabelCommand{false, std::string()});
}

void CommandBuffer::ReplayLabelCommands(const vvl::span<const LabelCommand>& label_commands,
                                        std::vector<std::string>& label_stack) {
    for (const LabelCommand& command : label_commands) {
        if (command.begin) {
            label_stack.emplace_back(command.label_name.empty() ? "(empty label)" : command.label_name);
        } else if (!label_stack.empty()) {
            // The above condition is needed for several reasons. On the primary command buffer level
            // the labels are not necessary balanced. And if the empty stack is detected in the context
            // where it is an error, then it will be reported by the core validation, but we still need
            // a safety check.
            label_stack.pop_back();
        }
    }
}

std::string CommandBuffer::GetDebugRegionName(const std::vector<LabelCommand>& label_commands, uint32_t label_command_index,
                                              const std::vector<std::string>& initial_label_stack) {
    if (label_command_index >= label_commands.size()) {
        // Can happen due to core validation error when in-use command buffer was re-recorded.
        // It's a bug if this happens in a valid vulkan program.
        return {};
    }
    auto label_commands_to_replay = vvl::make_span(label_commands.data(), label_command_index + 1);
    auto label_stack = initial_label_stack;
    vvl::CommandBuffer::ReplayLabelCommands(label_commands_to_replay, label_stack);

    // Build up complete debug region name from all enclosing regions
    std::string debug_region;
    for (const std::string& label_name : label_stack) {
        if (!debug_region.empty()) {
            debug_region += "::";
        }
        debug_region += label_name;
    }
    return debug_region;
}

std::string CommandBuffer::DescribeInvalidatedState(CBDynamicState dynamic_state) const {
    std::ostringstream ss;
    if (dynamic_state_status.history[dynamic_state] && !dynamic_state_status.cb[dynamic_state]) {
        ss << " (There was a call to vkCmdBindPipeline";
        if (auto pipeline = dev_data.Get<vvl::Pipeline>(invalidated_state_pipe[dynamic_state])) {
            ss << " with " << dev_data.FormatHandle(*pipeline);
        }
        ss << " that didn't have " << DynamicStateToString(dynamic_state) << " and invalidated the prior "
           << DescribeDynamicStateCommand(dynamic_state) << " call)";
    }
    if (GetActiveSubpass() != 0 && active_render_pass->has_multiview_enabled) {
        ss << " (When multiview is enabled, vkCmdNextSubpass will invalidate all dynamic state)";
    }
    return ss.str();
}

void CommandBuffer::RemoveOwnedSubState(LayerObjectTypeId id) {
    for (auto& last_bound : lastBound) {
        if (last_bound.push_descriptor_set) {
            last_bound.push_descriptor_set->RemoveSubState(id);
        }
    }
}

VulkanTypedHandle CommandBufferSubState::Handle() const { return base.Handle(); }
VkCommandBuffer CommandBufferSubState::VkHandle() const { return base.VkHandle(); }

}  // namespace vvl
