/* Copyright (c) 2015-2023 The Khronos Group Inc.
 * Copyright (c) 2015-2023 Valve Corporation
 * Copyright (c) 2015-2023 LunarG, Inc.
 * Copyright (C) 2015-2023 Google Inc.
 * Modifications Copyright (C) 2020 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"

COMMAND_POOL_STATE::COMMAND_POOL_STATE(ValidationStateTracker *dev, VkCommandPool cp, const VkCommandPoolCreateInfo *pCreateInfo,
                                       VkQueueFlags flags)
    : BASE_NODE(cp, kVulkanObjectTypeCommandPool),
      dev_data(dev),
      createFlags(pCreateInfo->flags),
      queueFamilyIndex(pCreateInfo->queueFamilyIndex),
      queue_flags(flags),
      unprotected((pCreateInfo->flags & VK_COMMAND_POOL_CREATE_PROTECTED_BIT) == 0) {}

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

void COMMAND_POOL_STATE::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<CMD_BUFFER_STATE>(iter->first);
            commandBuffers.erase(iter);
        }
    }
}

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

void COMMAND_POOL_STATE::Destroy() {
    for (auto &entry : commandBuffers) {
        dev_data->Destroy<CMD_BUFFER_STATE>(entry.first);
    }
    commandBuffers.clear();
    BASE_NODE::Destroy();
}

const char *CommandTypeString(CMD_TYPE type) {
    // Autogenerated as part of the command_validation.h codegen
    return kGeneratedCommandNameList[type];
}

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

CMD_BUFFER_STATE::CMD_BUFFER_STATE(ValidationStateTracker *dev, VkCommandBuffer cb, const VkCommandBufferAllocateInfo *pCreateInfo,
                                   const COMMAND_POOL_STATE *pool)
    : REFCOUNTED_NODE(cb, kVulkanObjectTypeCommandBuffer),
      createInfo(*pCreateInfo),
      command_pool(pool),
      dev_data(dev),
      unprotected(pool->unprotected),
      lastBound({*this, *this, *this}) {
    ResetCBState();
}

// Get the image viewstate for a given framebuffer attachment
IMAGE_VIEW_STATE *CMD_BUFFER_STATE::GetActiveAttachmentImageViewState(uint32_t index) {
    assert(active_attachments && index != VK_ATTACHMENT_UNUSED && (index < active_attachments->size()));
    return active_attachments->at(index);
}

// Get the image viewstate for a given framebuffer attachment
const IMAGE_VIEW_STATE *CMD_BUFFER_STATE::GetActiveAttachmentImageViewState(uint32_t index) const {
    if (!active_attachments || index == VK_ATTACHMENT_UNUSED || (index >= active_attachments->size())) {
        return nullptr;
    }
    return active_attachments->at(index);
}

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

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

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

    // Reset CB state (note that createInfo is not cleared)
    memset(&beginInfo, 0, sizeof(VkCommandBufferBeginInfo));
    memset(&inheritanceInfo, 0, sizeof(VkCommandBufferInheritanceInfo));
    has_draw_cmd = false;
    has_dispatch_cmd = false;
    has_trace_rays_cmd = false;
    has_build_as_cmd = false;
    hasRenderPassInstance = false;
    suspendsRenderPassInstance = false;
    resumesRenderPassInstance = false;
    state = CB_NEW;
    command_count = 0;
    submitCount = 0;
    image_layout_change_count = 1;  // Start at 1. 0 is insert value for validation cache versions, s.t. new == dirty
    status.reset();
    static_status.reset();
    dynamic_state_value.reset();
    inheritedViewportDepths.clear();
    usedViewportScissorCount = 0;
    pipelineStaticViewportCount = 0;
    pipelineStaticScissorCount = 0;
    viewportMask = 0;
    viewportWithCountMask = 0;
    viewportWithCountCount = 0;
    scissorMask = 0;
    scissorWithCountMask = 0;
    scissorWithCountCount = 0;
    trashedViewportMask = 0;
    trashedScissorMask = 0;
    trashedViewportCount = false;
    trashedScissorCount = false;
    usedDynamicViewportCount = false;
    usedDynamicScissorCount = false;
    dynamicColorWriteEnableAttachmentCount = 0;

    activeRenderPassBeginInfo = safe_VkRenderPassBeginInfo();
    activeRenderPass = nullptr;
    active_attachments = nullptr;
    active_subpasses = nullptr;
    active_color_attachments_index.clear();
    attachments_view_states.clear();
    activeSubpassContents = VK_SUBPASS_CONTENTS_INLINE;
    SetActiveSubpass(0);
    waitedEvents.clear();
    events.clear();
    writeEventsBeforeWait.clear();
    activeQueries.clear();
    startedQueries.clear();
    image_layout_map.clear();
    aliased_image_layout_map.clear();
    descriptorset_cache.clear();
    current_vertex_buffer_binding_info.vertex_buffer_bindings.clear();
    vertex_buffer_used = false;
    primaryCommandBuffer = VK_NULL_HANDLE;
    linkedCommandBuffers.clear();
    queue_submit_functions.clear();
    queue_submit_functions_after_render_pass.clear();
    cmd_execute_commands_functions.clear();
    eventUpdates.clear();
    queryUpdates.clear();

    for (auto &item : lastBound) {
        item.Reset();
    }
    activeFramebuffer = VK_NULL_HANDLE;
    index_buffer_binding.reset();

    qfo_transfer_image_barriers.Reset();
    qfo_transfer_buffer_barriers.Reset();

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

    // Clean up the label data
    debug_label.Reset();
    validate_descriptorsets_in_queuesubmit.clear();

    // Best practices info
    small_indexed_draw_call_count = 0;

    transform_feedback_active = false;

    // Clean up the label data
    ResetCmdDebugUtilsLabel(dev_data->report_data, commandBuffer());
}

void CMD_BUFFER_STATE::Reset() {
    ResetCBState();
    // Remove reverse command buffer links.
    Invalidate(true);
}

// Track which resources are in-flight by atomically incrementing their "in_use" count
void CMD_BUFFER_STATE::IncrementResources() {
    submitCount++;

    // TODO : We should be able to remove the NULL look-up checks from the code below as long as
    //  all the corresponding cases are verified to cause CB_INVALID state and the CB_INVALID state
    //  should then be flagged prior to calling this function
    for (auto event : writeEventsBeforeWait) {
        auto event_state = dev_data->Get<EVENT_STATE>(event);
        if (event_state) event_state->write_in_use++;
    }
}

// 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/dispath/traceRays/etc time will have it properly compatabile or else other VU will be triggered
void CMD_BUFFER_STATE::ResetPushConstantDataIfIncompatible(const PIPELINE_LAYOUT_STATE *pipeline_layout_state) {
    if (pipeline_layout_state == nullptr) {
        return;
    }
    if (push_constant_data_ranges == pipeline_layout_state->push_constant_ranges) {
        return;
    }

    push_constant_data_ranges = pipeline_layout_state->push_constant_ranges;
    push_constant_data.clear();
    push_constant_data_update.clear();
    uint32_t size_needed = 0;
    for (const auto &push_constant_range : *push_constant_data_ranges) {
        auto size = push_constant_range.offset + push_constant_range.size;
        size_needed = std::max(size_needed, size);

        auto stage_flags = push_constant_range.stageFlags;
        uint32_t bit_shift = 0;
        while (stage_flags) {
            if (stage_flags & 1) {
                VkShaderStageFlagBits flag = static_cast<VkShaderStageFlagBits>(1 << bit_shift);
                const auto it = push_constant_data_update.find(flag);

                if (it != push_constant_data_update.end()) {
                    if (it->second.size() < push_constant_range.offset) {
                        it->second.resize(push_constant_range.offset, PC_Byte_Not_Set);
                    }
                    if (it->second.size() < size) {
                        it->second.resize(size, PC_Byte_Not_Updated);
                    }
                } else {
                    std::vector<uint8_t> bytes;
                    bytes.resize(push_constant_range.offset, PC_Byte_Not_Set);
                    bytes.resize(size, PC_Byte_Not_Updated);
                    push_constant_data_update[flag] = bytes;
                }
            }
            stage_flags = stage_flags >> 1;
            ++bit_shift;
        }
    }
    push_constant_data.resize(size_needed, 0);
}

void CMD_BUFFER_STATE::Destroy() {
    // Remove the cb debug labels
    EraseCmdDebugUtilsLabel(dev_data->report_data, commandBuffer());
    {
        auto guard = WriteLock();
        ResetCBState();
    }
    BASE_NODE::Destroy();
}

void CMD_BUFFER_STATE::NotifyInvalidate(const BASE_NODE::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 CMD_BUFFER_STATE and BASE_NODE::parent_nodes locking.
            if (object_bindings.erase(obj)) {
                obj->RemoveParent(this);
                found_invalid = true;
            }
            switch (obj->Type()) {
                case kVulkanObjectTypeCommandBuffer:
                    if (unlink) {
                        linkedCommandBuffers.erase(static_cast<CMD_BUFFER_STATE *>(obj.get()));
                    }
                    break;
                case kVulkanObjectTypeImage:
                    if (unlink) {
                        image_layout_map.erase(static_cast<IMAGE_STATE *>(obj.get()));
                    }
                    break;
                default:
                    break;
            }
        }
        if (found_invalid) {
            if (state == CB_RECORDING) {
                state = CB_INVALID_INCOMPLETE;
            } else if (state == CB_RECORDED) {
                state = CB_INVALID_COMPLETE;
            }
            broken_bindings.emplace(invalid_nodes[0]->Handle(), log_list);
        }
    }
    BASE_NODE::NotifyInvalidate(invalid_nodes, unlink);
}

const CommandBufferImageLayoutMap &CMD_BUFFER_STATE::GetImageSubresourceLayoutMap() const { return image_layout_map; }

// The const variant only need the image as it is the key for the map
const ImageSubresourceLayoutMap *CMD_BUFFER_STATE::GetImageSubresourceLayoutMap(const IMAGE_STATE &image_state) const {
    auto it = image_layout_map.find(&image_state);
    if (it == image_layout_map.cend()) {
        return nullptr;
    }
    return it->second.get();
}

// The non-const variant only needs the image state, as the factory requires it to construct a new entry
ImageSubresourceLayoutMap *CMD_BUFFER_STATE::GetImageSubresourceLayoutMap(const IMAGE_STATE &image_state) {
    auto &layout_map = image_layout_map[&image_state];
    if (!layout_map) {
        // Make sure we don't create a nullptr keyed entry for a zombie Image
        if (image_state.Destroyed() || !image_state.layout_range_map) {
            return nullptr;
        }
        // Was an empty slot... fill it in.
        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 *global_layout_map = image_state.layout_range_map.get();
            auto iter = aliased_image_layout_map.find(global_layout_map);
            if (iter != aliased_image_layout_map.end()) {
                layout_map = iter->second;
            } else {
                layout_map = std::make_shared<ImageSubresourceLayoutMap>(image_state);
                // 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(global_layout_map, layout_map);
            }

        } else {
            layout_map = std::make_shared<ImageSubresourceLayoutMap>(image_state);
        }
    }
    return layout_map.get();
}

static bool SetQueryState(const QueryObject &object, QueryState value, QueryMap *localQueryToStateMap) {
    (*localQueryToStateMap)[object] = value;
    return false;
}

void CMD_BUFFER_STATE::BeginQuery(const QueryObject &query_obj) {
    activeQueries.insert(query_obj);
    startedQueries.insert(query_obj);
    queryUpdates.emplace_back([query_obj](CMD_BUFFER_STATE &cb_state_arg, bool do_validate, VkQueryPool &firstPerfQueryPool,
                                          uint32_t perfQueryPass, QueryMap *localQueryToStateMap) {
        SetQueryState(QueryObject(query_obj, perfQueryPass), QUERYSTATE_RUNNING, localQueryToStateMap);
        return false;
    });
    updatedQueries.insert(query_obj);
}

void CMD_BUFFER_STATE::EndQuery(const QueryObject &query_obj) {
    activeQueries.erase(query_obj);
    queryUpdates.emplace_back([query_obj](CMD_BUFFER_STATE &cb_state_arg, bool do_validate, VkQueryPool &firstPerfQueryPool,
                                          uint32_t perfQueryPass, QueryMap *localQueryToStateMap) {
        return SetQueryState(QueryObject(query_obj, perfQueryPass), QUERYSTATE_ENDED, localQueryToStateMap);
    });
    updatedQueries.insert(query_obj);
}

bool CMD_BUFFER_STATE::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 : linkedCommandBuffers) {
        auto guard = sub_cb->ReadLock();
        if (sub_cb->updatedQueries.find(key) != sub_cb->updatedQueries.end()) {
            return true;
        }
    }
    return updatedQueries.find(key) != updatedQueries.end();
}

static bool SetQueryStateMulti(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, uint32_t perfPass, QueryState value,
                               QueryMap *localQueryToStateMap) {
    for (uint32_t i = 0; i < queryCount; i++) {
        QueryObject object = QueryObject(QueryObject(queryPool, firstQuery + i), perfPass);
        (*localQueryToStateMap)[object] = value;
    }
    return false;
}

void CMD_BUFFER_STATE::EndQueries(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount) {
    for (uint32_t slot = firstQuery; slot < (firstQuery + queryCount); slot++) {
        QueryObject query = {queryPool, slot};
        activeQueries.erase(query);
        updatedQueries.insert(query);
    }
    queryUpdates.emplace_back([queryPool, firstQuery, queryCount](CMD_BUFFER_STATE &cb_state_arg, bool do_validate,
                                                                  VkQueryPool &firstPerfQueryPool, uint32_t perfQueryPass,
                                                                  QueryMap *localQueryToStateMap) {
        return SetQueryStateMulti(queryPool, firstQuery, queryCount, perfQueryPass, QUERYSTATE_ENDED, localQueryToStateMap);
    });
}

void CMD_BUFFER_STATE::ResetQueryPool(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount) {
    for (uint32_t slot = firstQuery; slot < (firstQuery + queryCount); slot++) {
        QueryObject query = {queryPool, slot};
        resetQueries.insert(query);
        updatedQueries.insert(query);
    }

    queryUpdates.emplace_back([queryPool, firstQuery, queryCount](CMD_BUFFER_STATE &cb_state_arg, bool do_validate,
                                                                  VkQueryPool &firstPerfQueryPool, uint32_t perfQueryPass,
                                                                  QueryMap *localQueryToStateMap) {
        return SetQueryStateMulti(queryPool, firstQuery, queryCount, perfQueryPass, QUERYSTATE_RESET, localQueryToStateMap);
    });
}

void CMD_BUFFER_STATE::UpdateSubpassAttachments(const safe_VkSubpassDescription2 &subpass, std::vector<SUBPASS_INFO> &subpasses) {
    for (uint32_t index = 0; index < subpass.inputAttachmentCount; ++index) {
        const uint32_t attachment_index = subpass.pInputAttachments[index].attachment;
        if (attachment_index != VK_ATTACHMENT_UNUSED) {
            subpasses[attachment_index].used = true;
            subpasses[attachment_index].usage = VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
            subpasses[attachment_index].layout = subpass.pInputAttachments[index].layout;
        }
    }

    for (uint32_t index = 0; index < subpass.colorAttachmentCount; ++index) {
        const uint32_t attachment_index = subpass.pColorAttachments[index].attachment;
        if (attachment_index != VK_ATTACHMENT_UNUSED) {
            subpasses[attachment_index].used = true;
            subpasses[attachment_index].usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
            subpasses[attachment_index].layout = subpass.pColorAttachments[index].layout;
            active_color_attachments_index.insert(index);
        }
        if (subpass.pResolveAttachments) {
            const uint32_t attachment_index2 = subpass.pResolveAttachments[index].attachment;
            if (attachment_index2 != VK_ATTACHMENT_UNUSED) {
                subpasses[attachment_index2].used = true;
                subpasses[attachment_index2].usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
                subpasses[attachment_index2].layout = subpass.pResolveAttachments[index].layout;
            }
        }
    }

    if (subpass.pDepthStencilAttachment) {
        const uint32_t attachment_index = subpass.pDepthStencilAttachment->attachment;
        if (attachment_index != VK_ATTACHMENT_UNUSED) {
            subpasses[attachment_index].used = true;
            subpasses[attachment_index].usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
            subpasses[attachment_index].layout = subpass.pDepthStencilAttachment->layout;
        }
    }
}

void CMD_BUFFER_STATE::UpdateAttachmentsView(const VkRenderPassBeginInfo *pRenderPassBegin) {
    auto &attachments = *(active_attachments.get());
    const bool imageless = (activeFramebuffer->createInfo.flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) != 0;
    const VkRenderPassAttachmentBeginInfo *attachment_info_struct = nullptr;
    if (pRenderPassBegin) attachment_info_struct = LvlFindInChain<VkRenderPassAttachmentBeginInfo>(pRenderPassBegin->pNext);

    for (uint32_t i = 0; i < attachments.size(); ++i) {
        if (imageless) {
            if (attachment_info_struct && i < attachment_info_struct->attachmentCount) {
                auto res = attachments_view_states.insert(dev_data->Get<IMAGE_VIEW_STATE>(attachment_info_struct->pAttachments[i]));
                attachments[i] = res.first->get();
            }
        } else {
            auto res = attachments_view_states.insert(activeFramebuffer->attachments_view_state[i]);
            attachments[i] = res.first->get();
        }
    }
}

void CMD_BUFFER_STATE::BeginRenderPass(CMD_TYPE cmd_type, const VkRenderPassBeginInfo *pRenderPassBegin,
                                       const VkSubpassContents contents) {
    RecordCmd(cmd_type);
    activeFramebuffer = dev_data->Get<FRAMEBUFFER_STATE>(pRenderPassBegin->framebuffer);
    activeRenderPass = dev_data->Get<RENDER_PASS_STATE>(pRenderPassBegin->renderPass);
    activeRenderPassBeginInfo = safe_VkRenderPassBeginInfo(pRenderPassBegin);
    SetActiveSubpass(0);
    activeSubpassContents = contents;

    if (activeRenderPass) {
        // Connect this RP to cmdBuffer
        if (!dev_data->disabled[command_buffer_state]) {
            AddChild(activeRenderPass);
        }

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

    auto chained_device_group_struct = LvlFindInChain<VkDeviceGroupRenderPassBeginInfo>(pRenderPassBegin->pNext);
    if (chained_device_group_struct) {
        active_render_pass_device_mask = chained_device_group_struct->deviceMask;
    } else {
        active_render_pass_device_mask = initial_device_mask;
    }

    active_subpasses = nullptr;
    active_attachments = nullptr;

    if (activeFramebuffer) {
        // Set cb_state->active_subpasses
        active_subpasses = std::make_shared<std::vector<SUBPASS_INFO>>(activeFramebuffer->createInfo.attachmentCount);
        const auto &subpass = activeRenderPass->createInfo.pSubpasses[GetActiveSubpass()];
        UpdateSubpassAttachments(subpass, *active_subpasses);

        // Set cb_state->active_attachments & cb_state->attachments_view_states
        active_attachments = std::make_shared<std::vector<IMAGE_VIEW_STATE *>>(activeFramebuffer->createInfo.attachmentCount);
        UpdateAttachmentsView(pRenderPassBegin);

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

void CMD_BUFFER_STATE::NextSubpass(CMD_TYPE cmd_type, VkSubpassContents contents) {
    RecordCmd(cmd_type);
    SetActiveSubpass(GetActiveSubpass() + 1);
    activeSubpassContents = contents;

    // Update cb_state->active_subpasses
    if (activeRenderPass) {
        if (activeFramebuffer) {
            active_subpasses = nullptr;
            active_subpasses = std::make_shared<std::vector<SUBPASS_INFO>>(activeFramebuffer->createInfo.attachmentCount);

            if (GetActiveSubpass() < activeRenderPass->createInfo.subpassCount) {
                const auto &subpass = activeRenderPass->createInfo.pSubpasses[GetActiveSubpass()];
                UpdateSubpassAttachments(subpass, *active_subpasses);
            }
        }

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

void CMD_BUFFER_STATE::EndRenderPass(CMD_TYPE cmd_type) {
    RecordCmd(cmd_type);
    activeRenderPass = nullptr;
    active_attachments = nullptr;
    active_subpasses = nullptr;
    active_color_attachments_index.clear();
    SetActiveSubpass(0);
    activeFramebuffer = VK_NULL_HANDLE;
}

void CMD_BUFFER_STATE::BeginRendering(CMD_TYPE cmd_type, const VkRenderingInfo *pRenderingInfo) {
    RecordCmd(cmd_type);
    begin_rendering_func_name = CommandTypeString(cmd_type);
    activeRenderPass = std::make_shared<RENDER_PASS_STATE>(pRenderingInfo);

    auto chained_device_group_struct = LvlFindInChain<VkDeviceGroupRenderPassBeginInfo>(pRenderingInfo->pNext);
    if (chained_device_group_struct) {
        active_render_pass_device_mask = chained_device_group_struct->deviceMask;
    } else {
        active_render_pass_device_mask = initial_device_mask;
    }

    activeSubpassContents = ((pRenderingInfo->flags & VK_RENDERING_CONTENTS_SECONDARY_COMMAND_BUFFERS_BIT_KHR)
                                 ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS
                                 : VK_SUBPASS_CONTENTS_INLINE);

    // Handle flags for dynamic rendering
    if (!hasRenderPassInstance && pRenderingInfo->flags & VK_RENDERING_RESUMING_BIT) {
        resumesRenderPassInstance = true;
    }
    suspendsRenderPassInstance = (pRenderingInfo->flags & VK_RENDERING_SUSPENDING_BIT) > 0;
    hasRenderPassInstance = true;

    active_attachments = nullptr;
    active_color_attachments_index.clear();
    uint32_t attachment_count = (pRenderingInfo->colorAttachmentCount + 2) * 2;

    // Set cb_state->active_attachments & cb_state->attachments_view_states
    active_attachments = std::make_shared<std::vector<IMAGE_VIEW_STATE *>>(attachment_count);
    auto &attachments = *(active_attachments.get());

    for (uint32_t i = 0; i < pRenderingInfo->colorAttachmentCount; ++i) {
        active_color_attachments_index.insert(i);
        auto &colorAttachment = attachments[GetDynamicColorAttachmentImageIndex(i)];
        auto &colorResolveAttachment = attachments[GetDynamicColorResolveAttachmentImageIndex(i)];
        colorAttachment = nullptr;
        colorResolveAttachment = nullptr;

        if (pRenderingInfo->pColorAttachments[i].imageView != VK_NULL_HANDLE) {
            auto res =
                attachments_view_states.insert(dev_data->Get<IMAGE_VIEW_STATE>(pRenderingInfo->pColorAttachments[i].imageView));
            colorAttachment = res.first->get();
            if (pRenderingInfo->pColorAttachments[i].resolveMode != VK_RESOLVE_MODE_NONE &&
                pRenderingInfo->pColorAttachments[i].resolveImageView != VK_NULL_HANDLE) {
                colorResolveAttachment = res.first->get();
            }
        }
    }

    if (pRenderingInfo->pDepthAttachment && pRenderingInfo->pDepthAttachment->imageView != VK_NULL_HANDLE) {
        auto &depthAttachment = attachments[GetDynamicDepthAttachmentImageIndex()];
        auto &depthResolveAttachment = attachments[GetDynamicDepthResolveAttachmentImageIndex()];
        depthAttachment = nullptr;
        depthResolveAttachment = nullptr;

        auto res = attachments_view_states.insert(dev_data->Get<IMAGE_VIEW_STATE>(pRenderingInfo->pDepthAttachment->imageView));
        depthAttachment = res.first->get();
        if (pRenderingInfo->pDepthAttachment->resolveMode != VK_RESOLVE_MODE_NONE &&
            pRenderingInfo->pDepthAttachment->resolveImageView != VK_NULL_HANDLE) {
            depthResolveAttachment = res.first->get();
        }
    }

    if (pRenderingInfo->pStencilAttachment && pRenderingInfo->pStencilAttachment->imageView != VK_NULL_HANDLE) {
        auto &stencilAttachment = attachments[GetDynamicStencilAttachmentImageIndex()];
        auto &stencilResolveAttachment = attachments[GetDynamicStencilResolveAttachmentImageIndex()];
        stencilAttachment = nullptr;
        stencilResolveAttachment = nullptr;

        auto res = attachments_view_states.insert(dev_data->Get<IMAGE_VIEW_STATE>(pRenderingInfo->pStencilAttachment->imageView));
        stencilAttachment = res.first->get();
        if (pRenderingInfo->pStencilAttachment->resolveMode != VK_RESOLVE_MODE_NONE &&
            pRenderingInfo->pStencilAttachment->resolveImageView != VK_NULL_HANDLE) {
            stencilResolveAttachment = res.first->get();
        }
    }
}

void CMD_BUFFER_STATE::BeginVideoCoding(const VkVideoBeginCodingInfoKHR *pBeginInfo) {
    RecordCmd(CMD_BEGINVIDEOCODINGKHR);
    bound_video_session = dev_data->Get<VIDEO_SESSION_STATE>(pBeginInfo->videoSession);
    bound_video_session_parameters = dev_data->Get<VIDEO_SESSION_PARAMETERS_STATE>(pBeginInfo->videoSessionParameters);

    if (bound_video_session) {
        // 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);
        }
    }

    if (pBeginInfo && pBeginInfo->pReferenceSlots) {
        std::vector<VideoReferenceSlot> expected_slots{};
        expected_slots.reserve(pBeginInfo->referenceSlotCount);

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

            if (pBeginInfo->pReferenceSlots[i].slotIndex >= 0) {
                expected_slots.emplace_back(dev_data, *bound_video_session->profile, pBeginInfo->pReferenceSlots[i], false);
            }
        }

        // Enqueue submission time validation
        video_session_updates[bound_video_session->videoSession()].emplace_back(
            [expected_slots](const ValidationStateTracker *dev_data, const VIDEO_SESSION_STATE *vs_state,
                             VideoSessionDeviceState &dev_state, bool do_validate) {
                bool skip = false;

                if (do_validate) {
                    for (const auto &slot : expected_slots) {
                        if (!dev_state.IsSlotActive(slot.index)) {
                            skip |= dev_data->LogError(vs_state->Handle(), "VUID-vkCmdBeginVideoCodingKHR-slotIndex-07239",
                                                       "DPB slot index %d is not active in %s", slot.index,
                                                       dev_data->report_data->FormatHandle(vs_state->Handle()).c_str());
                        } else if (slot.resource && !dev_state.IsSlotPicture(slot.index, slot.resource)) {
                            skip |= dev_data->LogError(
                                vs_state->Handle(), "VUID-vkCmdBeginVideoCodingKHR-pPictureResource-07265",
                                "DPB slot index %d of %s is not currently associated with the specified "
                                "video picture resource: %s, layer %u, offset (%u,%u), extent (%u,%u)",
                                slot.index, dev_data->report_data->FormatHandle(vs_state->Handle()).c_str(),
                                dev_data->report_data->FormatHandle(slot.resource.image_state->Handle()).c_str(),
                                slot.resource.range.baseArrayLayer, slot.resource.coded_offset.x, slot.resource.coded_offset.y,
                                slot.resource.coded_extent.width, slot.resource.coded_extent.height);
                        }
                    }
                }

                for (const auto &slot : expected_slots) {
                    if (!slot.resource) {
                        dev_state.Deactivate(slot.index);
                    }
                }

                return skip;
            });
    }
}

void CMD_BUFFER_STATE::EndVideoCoding(const VkVideoEndCodingInfoKHR *pEndCodingInfo) {
    RecordCmd(CMD_ENDVIDEOCODINGKHR);
    bound_video_session = nullptr;
    bound_video_session_parameters = nullptr;
    bound_video_picture_resources.clear();
}

void CMD_BUFFER_STATE::ControlVideoCoding(const VkVideoCodingControlInfoKHR *pControlInfo) {
    RecordCmd(CMD_CONTROLVIDEOCODINGKHR);

    if (pControlInfo && bound_video_session) {
        auto control_flags = pControlInfo->flags;

        if (control_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 validation and device state changes
        video_session_updates[bound_video_session->videoSession()].emplace_back(
            [control_flags](const ValidationStateTracker *dev_data, const VIDEO_SESSION_STATE *vs_state,
                            VideoSessionDeviceState &dev_state, bool do_validate) {
                bool skip = false;
                bool reset_session = control_flags & VK_VIDEO_CODING_CONTROL_RESET_BIT_KHR;
                if (do_validate) {
                    if (!reset_session && !dev_state.IsInitialized()) {
                        skip |= dev_data->LogError(vs_state->Handle(), "VUID-vkCmdControlVideoCodingKHR-flags-07017",
                                                   "Bound video session %s is uninitialized",
                                                   dev_data->report_data->FormatHandle(vs_state->Handle()).c_str());
                    }
                }

                // Reset video session at submission time, if requested
                if (reset_session) {
                    dev_state.Reset();
                }

                return skip;
            });
    }
}

void CMD_BUFFER_STATE::DecodeVideo(const VkVideoDecodeInfoKHR *pDecodeInfo) {
    RecordCmd(CMD_DECODEVIDEOKHR);

    if (bound_video_session && pDecodeInfo) {
        VideoReferenceSlot setup_slot{};
        if (pDecodeInfo->pSetupReferenceSlot && pDecodeInfo->pSetupReferenceSlot->pPictureResource) {
            setup_slot = VideoReferenceSlot(dev_data, *bound_video_session->profile, *pDecodeInfo->pSetupReferenceSlot);
            // Update bound video picture resource DPB slot index association
            bound_video_picture_resources[setup_slot.resource] = setup_slot.index;
        }

        // Need to also validate the picture kind (frame, top field, bottom field) for H.264
        bool need_reference_slot_validation = (bound_video_session->GetCodecOp() == VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR);

        std::vector<VideoReferenceSlot> reference_slots{};
        if (need_reference_slot_validation) {
            reference_slots.reserve(pDecodeInfo->referenceSlotCount);

            for (uint32_t i = 0; i < pDecodeInfo->referenceSlotCount; ++i) {
                reference_slots.emplace_back(dev_data, *bound_video_session->profile, pDecodeInfo->pReferenceSlots[i]);
            }
        }

        // Enqueue submission time validation and device state changes
        video_session_updates[bound_video_session->videoSession()].emplace_back(
            [setup_slot, reference_slots](const ValidationStateTracker *dev_data, const VIDEO_SESSION_STATE *vs_state,
                                          VideoSessionDeviceState &dev_state, bool do_validate) {
                bool skip = false;
                if (do_validate) {
                    if (!dev_state.IsInitialized()) {
                        skip |= dev_data->LogError(vs_state->Handle(), "VUID-vkCmdDecodeVideoKHR-None-07011", "%s is uninitialized",
                                                   dev_data->report_data->FormatHandle(vs_state->Handle()).c_str());
                    }

                    const auto log_picture_kind_error = [&](const VideoReferenceSlot &slot, const char *vuid,
                                                            const char *picture_kind) -> bool {
                        return dev_data->LogError(
                            vs_state->Handle(), vuid,
                            "DPB slot index %d of %s does not currently contain a %s with the specified "
                            "video picture resource: %s, layer %u, offset (%u,%u), extent (%u,%u)",
                            slot.index, dev_data->report_data->FormatHandle(vs_state->Handle()).c_str(), picture_kind,
                            dev_data->report_data->FormatHandle(slot.resource.image_state->Handle()).c_str(),
                            slot.resource.range.baseArrayLayer, slot.resource.coded_offset.x, slot.resource.coded_offset.y,
                            slot.resource.coded_extent.width, slot.resource.coded_extent.height);
                    };

                    for (const auto &slot : reference_slots) {
                        if (slot.picture_id.IsFrame() &&
                            !dev_state.IsSlotPicture(slot.index, VideoPictureID::Frame(), slot.resource)) {
                            skip |= log_picture_kind_error(slot, "VUID-vkCmdDecodeVideoKHR-pDecodeInfo-07266", "frame");
                        }
                        if (slot.picture_id.ContainsTopField() &&
                            !dev_state.IsSlotPicture(slot.index, VideoPictureID::TopField(), slot.resource)) {
                            skip |= log_picture_kind_error(slot, "VUID-vkCmdDecodeVideoKHR-pDecodeInfo-07267", "top field");
                        }
                        if (slot.picture_id.ContainsBottomField() &&
                            !dev_state.IsSlotPicture(slot.index, VideoPictureID::BottomField(), slot.resource)) {
                            skip |= log_picture_kind_error(slot, "VUID-vkCmdDecodeVideoKHR-pDecodeInfo-07268", "bottom field");
                        }
                    }
                }

                // Set up reference slot at submission time, if requested
                if (setup_slot) {
                    dev_state.Activate(setup_slot.index, setup_slot.picture_id, setup_slot.resource);
                }

                return skip;
            });

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

void CMD_BUFFER_STATE::Begin(const VkCommandBufferBeginInfo *pBeginInfo) {
    if (CB_RECORDED == state || CB_INVALID_COMPLETE == state) {
        Reset();
    }

    descriptorset_cache.clear();

    // Set updated state here in case implicit reset occurs above
    state = CB_RECORDING;
    beginInfo = *pBeginInfo;
    if (beginInfo.pInheritanceInfo && (createInfo.level == VK_COMMAND_BUFFER_LEVEL_SECONDARY)) {
        inheritanceInfo = *(beginInfo.pInheritanceInfo);
        beginInfo.pInheritanceInfo = &inheritanceInfo;
        // If we are a secondary command-buffer and inheriting.  Update the items we should inherit.
        if ((createInfo.level != VK_COMMAND_BUFFER_LEVEL_PRIMARY) &&
            (beginInfo.flags & VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT)) {
            if (beginInfo.pInheritanceInfo->renderPass) {
                activeRenderPass = dev_data->Get<RENDER_PASS_STATE>(beginInfo.pInheritanceInfo->renderPass);
                SetActiveSubpass(beginInfo.pInheritanceInfo->subpass);

                if (beginInfo.pInheritanceInfo->framebuffer) {
                    activeFramebuffer = dev_data->Get<FRAMEBUFFER_STATE>(beginInfo.pInheritanceInfo->framebuffer);
                    active_subpasses = nullptr;
                    active_attachments = nullptr;

                    if (activeFramebuffer) {
                        // Set active_subpasses
                        active_subpasses =
                            std::make_shared<std::vector<SUBPASS_INFO>>(activeFramebuffer->createInfo.attachmentCount);
                        const auto &subpass = activeRenderPass->createInfo.pSubpasses[GetActiveSubpass()];
                        UpdateSubpassAttachments(subpass, *active_subpasses);

                        // Set active_attachments & attachments_view_states
                        active_attachments =
                            std::make_shared<std::vector<IMAGE_VIEW_STATE *>>(activeFramebuffer->createInfo.attachmentCount);
                        UpdateAttachmentsView(nullptr);

                        // Connect this framebuffer and its children to this cmdBuffer
                        if (!dev_data->disabled[command_buffer_state]) {
                            AddChild(activeFramebuffer);
                        }
                    }
                }
            } else {
                auto inheritance_rendering_info =
                    LvlFindInChain<VkCommandBufferInheritanceRenderingInfo>(beginInfo.pInheritanceInfo->pNext);
                if (inheritance_rendering_info) {
                    activeRenderPass = std::make_shared<RENDER_PASS_STATE>(inheritance_rendering_info);
                }
            }

            // Check for VkCommandBufferInheritanceViewportScissorInfoNV (VK_NV_inherited_viewport_scissor)
            auto p_inherited_viewport_scissor_info =
                LvlFindInChain<VkCommandBufferInheritanceViewportScissorInfoNV>(beginInfo.pInheritanceInfo->pNext);
            if (p_inherited_viewport_scissor_info != nullptr && p_inherited_viewport_scissor_info->viewportScissor2D) {
                auto pViewportDepths = p_inherited_viewport_scissor_info->pViewportDepths;
                inheritedViewportDepths.assign(pViewportDepths,
                                               pViewportDepths + p_inherited_viewport_scissor_info->viewportDepthCount);
            }
        }
    }

    auto chained_device_group_struct = LvlFindInChain<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;
    updatedQueries.clear();
}

void CMD_BUFFER_STATE::End(VkResult result) {
    // Cached validation is specific to a specific recording of a specific command buffer.
    descriptorset_cache.clear();
    if (VK_SUCCESS == result) {
        state = CB_RECORDED;
    }
}

void CMD_BUFFER_STATE::ExecuteCommands(vvl::span<const VkCommandBuffer> secondary_command_buffers) {
    RecordCmd(CMD_EXECUTECOMMANDS);
    for (const VkCommandBuffer sub_command_buffer : secondary_command_buffers) {
        auto sub_cb_state = dev_data->GetWrite<CMD_BUFFER_STATE>(sub_command_buffer);
        assert(sub_cb_state);
        if (!(sub_cb_state->beginInfo.flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT)) {
            if (beginInfo.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
                beginInfo.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
        // ValidationStateTracker 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 &sub_layout_map_entry : sub_cb_state->image_layout_map) {
            const auto *image_state = sub_layout_map_entry.first;

            auto *cb_subres_map = GetImageSubresourceLayoutMap(*image_state);
            if (cb_subres_map) {
                const auto &sub_cb_subres_map = sub_layout_map_entry.second;
                cb_subres_map->UpdateFrom(*sub_cb_subres_map);
            }
        }

        sub_cb_state->primaryCommandBuffer = commandBuffer();
        linkedCommandBuffers.insert(sub_cb_state.get());
        AddChild(sub_cb_state);
        // Add a query update that runs all the query updates that happen in the sub command buffer.
        // This avoids locking ambiguity because primary command buffers are locked when these
        // callbacks run, but secondary command buffers are not.
        queryUpdates.emplace_back([sub_command_buffer](CMD_BUFFER_STATE &cb_state_arg, bool do_validate,
                                                       VkQueryPool &firstPerfQueryPool, uint32_t perfQueryPass,
                                                       QueryMap *localQueryToStateMap) {
            bool skip = false;
            auto sub_cb_state_arg = cb_state_arg.dev_data->GetWrite<CMD_BUFFER_STATE>(sub_command_buffer);
            for (auto &function : sub_cb_state_arg->queryUpdates) {
                skip |= function(*sub_cb_state_arg, do_validate, firstPerfQueryPool, perfQueryPass, localQueryToStateMap);
            }
            return skip;
        });
        for (auto &function : sub_cb_state->eventUpdates) {
            eventUpdates.push_back(function);
        }
        for (auto &event : sub_cb_state->events) {
            events.push_back(event);
        }
        for (auto &function : sub_cb_state->queue_submit_functions) {
            queue_submit_functions.push_back(function);
        }

        // State is trashed after executing secondary command buffers.
        // Importantly, this function runs after CoreChecks::PreCallValidateCmdExecuteCommands.
        trashedViewportMask = vvl::MaxTypeValue(trashedViewportMask);
        trashedScissorMask = vvl::MaxTypeValue(trashedScissorMask);
        trashedViewportCount = true;
        trashedScissorCount = true;

        // Pass along if any commands are used in the secondary command buffer
        has_draw_cmd |= sub_cb_state->has_draw_cmd;
        has_dispatch_cmd |= sub_cb_state->has_dispatch_cmd;
        has_trace_rays_cmd |= sub_cb_state->has_trace_rays_cmd;
        has_build_as_cmd |= sub_cb_state->has_build_as_cmd;

        // Handle secondary command buffer updates for dynamic rendering
        if (!hasRenderPassInstance) {
            resumesRenderPassInstance = sub_cb_state->resumesRenderPassInstance;
        }
        if (!sub_cb_state->activeRenderPass) {
            suspendsRenderPassInstance = sub_cb_state->suspendsRenderPassInstance;
            hasRenderPassInstance |= sub_cb_state->hasRenderPassInstance;
        }
    }
}

void CMD_BUFFER_STATE::PushDescriptorSetState(VkPipelineBindPoint pipelineBindPoint, const PIPELINE_LAYOUT_STATE &pipeline_layout,
                                              uint32_t set, uint32_t descriptorWriteCount,
                                              const VkWriteDescriptorSet *pDescriptorWrites) {
    // Short circuit invalid updates
    if ((set >= pipeline_layout.set_layouts.size()) || !pipeline_layout.set_layouts[set] ||
        !pipeline_layout.set_layouts[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[set];
    const auto lv_bind_point = ConvertToLvlBindPoint(pipelineBindPoint);
    auto &last_bound = lastBound[lv_bind_point];
    auto &push_descriptor_set = last_bound.push_descriptor_set;
    // If we are disturbing the current push_desriptor_set clear it
    if (!push_descriptor_set || !IsBoundSetCompat(set, last_bound, pipeline_layout)) {
        last_bound.UnbindAndResetPushDescriptorSet(
            std::make_shared<cvdescriptorset::DescriptorSet>(VK_NULL_HANDLE, nullptr, dsl, 0, dev_data));
    }

    UpdateLastBoundDescriptorSets(pipelineBindPoint, pipeline_layout, set, 1, nullptr, push_descriptor_set, 0, nullptr);
    last_bound.pipeline_layout = pipeline_layout.layout();

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

// Generic function to handle state update for all CmdDraw* type functions
void CMD_BUFFER_STATE::UpdateDrawCmd(CMD_TYPE cmd_type) {
    has_draw_cmd = true;
    UpdatePipelineState(cmd_type, VK_PIPELINE_BIND_POINT_GRAPHICS);

    // Update the consumed viewport/scissor count.
    uint32_t &used = usedViewportScissorCount;
    used = std::max(used, pipelineStaticViewportCount);
    used = std::max(used, pipelineStaticScissorCount);
    usedDynamicViewportCount |= dynamic_status[CB_DYNAMIC_VIEWPORT_WITH_COUNT_SET];
    usedDynamicScissorCount |= dynamic_status[CB_DYNAMIC_SCISSOR_WITH_COUNT_SET];
}

// Generic function to handle state update for all CmdDispatch* type functions
void CMD_BUFFER_STATE::UpdateDispatchCmd(CMD_TYPE cmd_type) {
    has_dispatch_cmd = true;
    UpdatePipelineState(cmd_type, VK_PIPELINE_BIND_POINT_COMPUTE);
}

// Generic function to handle state update for all CmdTraceRay* type functions
void CMD_BUFFER_STATE::UpdateTraceRayCmd(CMD_TYPE cmd_type) {
    has_trace_rays_cmd = true;
    UpdatePipelineState(cmd_type, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR);
}

// Generic function to handle state update for all Provoking functions calls (draw/dispatch/traceray/etc)
void CMD_BUFFER_STATE::UpdatePipelineState(CMD_TYPE cmd_type, const VkPipelineBindPoint bind_point) {
    RecordCmd(cmd_type);

    const auto lv_bind_point = ConvertToLvlBindPoint(bind_point);
    auto &last_bound = lastBound[lv_bind_point];
    PIPELINE_STATE *pipe = last_bound.pipeline_state;
    if (VK_NULL_HANDLE != last_bound.pipeline_layout) {
        for (const auto &set_binding_pair : pipe->active_slots) {
            uint32_t set_index = set_binding_pair.first;
            if (set_index >= last_bound.per_set.size()) {
                continue;
            }
            auto &set_info = last_bound.per_set[set_index];
            // Pull the set node
            auto &descriptor_set = set_info.bound_descriptor_set;
            if (!descriptor_set) {
                continue;
            }

            // For the "bindless" style resource usage with many descriptors, need to optimize command <-> descriptor binding

            // TODO: If recreating the reduced_map here shows up in profilinging, need to find a way of sharing with the
            // Validate pass.  Though in the case of "many" descriptors, typically the descriptor count >> binding count
            cvdescriptorset::PrefilterBindRequestMap reduced_map(*descriptor_set, set_binding_pair.second);
            const auto &binding_req_map = reduced_map.FilteredMap(*this, *pipe);

            if (reduced_map.IsManyDescriptors()) {
                // Only update validate binding tags if we meet the "many" criteria in the Prefilter class
                descriptor_set->UpdateValidationCache(*this, *pipe, binding_req_map);
            }

            // We can skip updating the state if "nothing" has changed since the last validation.
            // See CoreChecks::ValidateCmdBufDrawState for more details.
            const bool descriptor_set_changed = !reduced_map.IsManyDescriptors() ||
                                                // Update if descriptor set (or contents) has changed
                                                set_info.validated_set != descriptor_set.get() ||
                                                set_info.validated_set_change_count != descriptor_set->GetChangeCount() ||
                                                (!dev_data->disabled[image_layout_validation] &&
                                                 set_info.validated_set_image_layout_change_count != image_layout_change_count);
            const bool need_update =
                descriptor_set_changed ||
                // Update if previous bindingReqMap doesn't include new bindingReqMap
                !std::includes(set_info.validated_set_binding_req_map.begin(), set_info.validated_set_binding_req_map.end(),
                               binding_req_map.begin(), binding_req_map.end());

            if (need_update) {
                if (!dev_data->disabled[command_buffer_state] && !descriptor_set->IsPushDescriptor()) {
                    AddChild(descriptor_set);
                }

                // Bind this set and its active descriptor resources to the command buffer
                if (!descriptor_set_changed && reduced_map.IsManyDescriptors()) {
                    // Only record the bindings that haven't already been recorded
                    BindingReqMap delta_reqs;
                    std::set_difference(binding_req_map.begin(), binding_req_map.end(),
                                        set_info.validated_set_binding_req_map.begin(),
                                        set_info.validated_set_binding_req_map.end(),
                                        vvl::insert_iterator<BindingReqMap>(delta_reqs, delta_reqs.begin()));
                    descriptor_set->UpdateDrawState(dev_data, this, cmd_type, pipe, delta_reqs);
                } else {
                    descriptor_set->UpdateDrawState(dev_data, this, cmd_type, pipe, binding_req_map);
                }

                set_info.validated_set = descriptor_set.get();
                set_info.validated_set_change_count = descriptor_set->GetChangeCount();
                set_info.validated_set_image_layout_change_count = image_layout_change_count;
                if (reduced_map.IsManyDescriptors()) {
                    // Check whether old == new before assigning, the equality check is much cheaper than
                    // freeing and reallocating the map.
                    if (set_info.validated_set_binding_req_map != set_binding_pair.second) {
                        set_info.validated_set_binding_req_map = set_binding_pair.second;
                    }
                } else {
                    set_info.validated_set_binding_req_map = BindingReqMap();
                }
            }
        }
    }
    if (pipe && pipe->vertex_input_state && !pipe->vertex_input_state->binding_descriptions.empty()) {
        vertex_buffer_used = true;
    }
}

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

    auto ds = last_bound.per_set[set_idx].bound_descriptor_set.get();
    if (ds && ds->IsPushDescriptor()) {
        assert(ds == 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 CMD_BUFFER_STATE::UpdateLastBoundDescriptorSets(VkPipelineBindPoint pipeline_bind_point,
                                                     const PIPELINE_LAYOUT_STATE &pipeline_layout, uint32_t first_set,
                                                     uint32_t set_count, const VkDescriptorSet *pDescriptorSets,
                                                     std::shared_ptr<cvdescriptorset::DescriptorSet> &push_descriptor_set,
                                                     uint32_t dynamic_offset_count, const uint32_t *p_dynamic_offsets) {
    assert((pDescriptorSets == nullptr) ^ (push_descriptor_set == nullptr));

    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());

    // Some useful shorthand
    const auto lv_bind_point = ConvertToLvlBindPoint(pipeline_bind_point);
    auto &last_bound = lastBound[lv_bind_point];
    last_bound.pipeline_layout = pipeline_layout.layout();
    auto &pipe_compat_ids = pipeline_layout.set_compat_ids;
    // Resize binding arrays
    if (last_binding_index >= last_bound.per_set.size()) {
        last_bound.per_set.resize(required_size);
    }
    const uint32_t current_size = static_cast<uint32_t>(last_bound.per_set.size());

    // Clean up the "disturbed" before and after the range to be set
    if (required_size < current_size) {
        if (last_bound.per_set[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.per_set.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 &set_info = last_bound.per_set[set_idx];
        if (set_info.compat_id_for_set != pipe_compat_ids[set_idx]) {
            PushDescriptorCleanup(last_bound, set_idx);
            set_info.Reset();
            set_info.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 &set_info = last_bound.per_set[set_idx];
        auto descriptor_set =
            push_descriptor_set ? push_descriptor_set : dev_data->Get<cvdescriptorset::DescriptorSet>(pDescriptorSets[input_idx]);

        set_info.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);
        }
        set_info.bound_descriptor_set = descriptor_set;
        set_info.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;
                set_info.dynamicOffsets = 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 {
                set_info.dynamicOffsets.clear();
            }
        }
    }
}

void CMD_BUFFER_STATE::UpdateLastBoundDescriptorBuffers(VkPipelineBindPoint pipeline_bind_point,
                                                        const PIPELINE_LAYOUT_STATE &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());

    // Some useful shorthand
    const auto lv_bind_point = ConvertToLvlBindPoint(pipeline_bind_point);
    auto &last_bound = lastBound[lv_bind_point];
    last_bound.pipeline_layout = pipeline_layout.layout();
    auto &pipe_compat_ids = pipeline_layout.set_compat_ids;
    // Resize binding arrays
    if (last_binding_index >= last_bound.per_set.size()) {
        last_bound.per_set.resize(required_size);
    }
    const uint32_t current_size = static_cast<uint32_t>(last_bound.per_set.size());

    // Clean up the "disturbed" before and after the range to be set
    if (required_size < current_size) {
        if (last_bound.per_set[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.per_set.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) {
        PushDescriptorCleanup(last_bound, set_idx);
        last_bound.per_set[set_idx].Reset();
    }

    // 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 &set_info = last_bound.per_set[set_idx];
        set_info.Reset();

        // Record binding
        set_info.bound_descriptor_buffer = {buffer_indicies[input_idx], buffer_offsets[input_idx]};
        set_info.compat_id_for_set = pipe_compat_ids[set_idx];  // compat ids are canonical *per* set index
    }
}

// Set image layout for given VkImageSubresourceRange struct
void CMD_BUFFER_STATE::SetImageLayout(const IMAGE_STATE &image_state, const VkImageSubresourceRange &image_subresource_range,
                                      VkImageLayout layout, VkImageLayout expected_layout) {
    auto *subresource_map = GetImageSubresourceLayoutMap(image_state);
    if (subresource_map && subresource_map->SetSubresourceRangeLayout(*this, image_subresource_range, layout, expected_layout)) {
        image_layout_change_count++;  // Change the version of this data to force revalidation
    }
}

// Set the initial image layout for all slices of an image view
void CMD_BUFFER_STATE::SetImageViewInitialLayout(const IMAGE_VIEW_STATE &view_state, VkImageLayout layout) {
    if (dev_data->disabled[image_layout_validation]) {
        return;
    }
    IMAGE_STATE *image_state = view_state.image_state.get();
    auto *subresource_map = GetImageSubresourceLayoutMap(*image_state);
    if (subresource_map) {
        subresource_map->SetSubresourceRangeInitialLayout(*this, layout, view_state);
    }
}

// Set the initial image layout for a passed non-normalized subresource range
void CMD_BUFFER_STATE::SetImageInitialLayout(const IMAGE_STATE &image_state, const VkImageSubresourceRange &range,
                                             VkImageLayout layout) {
    auto *subresource_map = GetImageSubresourceLayoutMap(image_state);
    if (subresource_map) {
        subresource_map->SetSubresourceRangeInitialLayout(*this, image_state.NormalizeSubresourceRange(range), layout);
    }
}

void CMD_BUFFER_STATE::SetImageInitialLayout(VkImage image, const VkImageSubresourceRange &range, VkImageLayout layout) {
    auto image_state = dev_data->Get<IMAGE_STATE>(image);
    if (!image_state) return;
    SetImageInitialLayout(*image_state, range, layout);
}

void CMD_BUFFER_STATE::SetImageInitialLayout(const IMAGE_STATE &image_state, const VkImageSubresourceLayers &layers,
                                             VkImageLayout layout) {
    SetImageInitialLayout(image_state, RangeFromLayers(layers), layout);
}

// Set image layout for all slices of an image view
void CMD_BUFFER_STATE::SetImageViewLayout(const IMAGE_VIEW_STATE &view_state, VkImageLayout layout, VkImageLayout layoutStencil) {
    const IMAGE_STATE *image_state = view_state.image_state.get();

    VkImageSubresourceRange sub_range = view_state.normalized_subresource_range;

    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 {
        SetImageLayout(*image_state, sub_range, layout);
    }
}

void CMD_BUFFER_STATE::RecordCmd(CMD_TYPE cmd_type) { command_count++; }

void CMD_BUFFER_STATE::RecordStateCmd(CMD_TYPE cmd_type, CBDynamicStatus state) {
    CBDynamicFlags state_bits;
    state_bits.set(state);
    RecordStateCmd(cmd_type, state_bits);
}

void CMD_BUFFER_STATE::RecordStateCmd(CMD_TYPE cmd_type, CBDynamicFlags const &state_bits) {
    RecordCmd(cmd_type);
    status |= state_bits;
    static_status &= ~state_bits;
}

void CMD_BUFFER_STATE::RecordColorWriteEnableStateCmd(CMD_TYPE cmd_type, CBDynamicStatus state, uint32_t attachment_count) {
    RecordStateCmd(cmd_type, state);
    dynamicColorWriteEnableAttachmentCount = std::max(dynamicColorWriteEnableAttachmentCount, attachment_count);
}

void CMD_BUFFER_STATE::RecordTransferCmd(CMD_TYPE cmd_type, std::shared_ptr<BINDABLE> &&buf1, std::shared_ptr<BINDABLE> &&buf2) {
    RecordCmd(cmd_type);
    if (buf1) {
        AddChild(buf1);
    }
    if (buf2) {
        AddChild(buf2);
    }
}

static bool SetEventStageMask(VkEvent event, VkPipelineStageFlags2KHR stageMask, EventToStageMap *localEventToStageMap) {
    (*localEventToStageMap)[event] = stageMask;
    return false;
}

void CMD_BUFFER_STATE::RecordSetEvent(CMD_TYPE cmd_type, VkEvent event, VkPipelineStageFlags2KHR stageMask) {
    RecordCmd(cmd_type);
    if (!dev_data->disabled[command_buffer_state]) {
        auto event_state = dev_data->Get<EVENT_STATE>(event);
        if (event_state) {
            AddChild(event_state);
        }
    }
    events.push_back(event);
    if (!waitedEvents.count(event)) {
        writeEventsBeforeWait.push_back(event);
    }
    eventUpdates.emplace_back([event, stageMask](CMD_BUFFER_STATE &, bool do_validate, EventToStageMap *localEventToStageMap) {
        return SetEventStageMask(event, stageMask, localEventToStageMap);
    });
}

void CMD_BUFFER_STATE::RecordResetEvent(CMD_TYPE cmd_type, VkEvent event, VkPipelineStageFlags2KHR stageMask) {
    RecordCmd(cmd_type);
    if (!dev_data->disabled[command_buffer_state]) {
        auto event_state = dev_data->Get<EVENT_STATE>(event);
        if (event_state) {
            AddChild(event_state);
        }
    }
    events.push_back(event);
    if (!waitedEvents.count(event)) {
        writeEventsBeforeWait.push_back(event);
    }

    eventUpdates.emplace_back([event](CMD_BUFFER_STATE &, bool do_validate, EventToStageMap *localEventToStageMap) {
        return SetEventStageMask(event, VkPipelineStageFlags2KHR(0), localEventToStageMap);
    });
}

void CMD_BUFFER_STATE::RecordWaitEvents(CMD_TYPE cmd_type, uint32_t eventCount, const VkEvent *pEvents,
                                        VkPipelineStageFlags2KHR src_stage_mask) {
    RecordCmd(cmd_type);
    for (uint32_t i = 0; i < eventCount; ++i) {
        if (!dev_data->disabled[command_buffer_state]) {
            auto event_state = dev_data->Get<EVENT_STATE>(pEvents[i]);
            if (event_state) {
                AddChild(event_state);
            }
        }
        waitedEvents.insert(pEvents[i]);
        events.push_back(pEvents[i]);
    }
}

void CMD_BUFFER_STATE::RecordBarriers(uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers,
                                      uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier *pBufferMemoryBarriers,
                                      uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier *pImageMemoryBarriers) {
    if (dev_data->disabled[command_buffer_state]) return;

    for (uint32_t i = 0; i < bufferMemoryBarrierCount; i++) {
        auto buffer_state = dev_data->Get<BUFFER_STATE>(pBufferMemoryBarriers[i].buffer);
        if (buffer_state) {
            AddChild(buffer_state);
        }
    }
    for (uint32_t i = 0; i < imageMemoryBarrierCount; i++) {
        auto image_state = dev_data->Get<IMAGE_STATE>(pImageMemoryBarriers[i].image);
        if (image_state) {
            AddChild(image_state);
        }
    }
}

void CMD_BUFFER_STATE::RecordBarriers(const VkDependencyInfoKHR &dep_info) {
    if (dev_data->disabled[command_buffer_state]) return;

    for (uint32_t i = 0; i < dep_info.bufferMemoryBarrierCount; i++) {
        auto buffer_state = dev_data->Get<BUFFER_STATE>(dep_info.pBufferMemoryBarriers[i].buffer);
        if (buffer_state) {
            AddChild(buffer_state);
        }
    }
    for (uint32_t i = 0; i < dep_info.imageMemoryBarrierCount; i++) {
        auto image_state = dev_data->Get<IMAGE_STATE>(dep_info.pImageMemoryBarriers[i].image);
        if (image_state) {
            AddChild(image_state);
        }
    }
}

void CMD_BUFFER_STATE::RecordWriteTimestamp(CMD_TYPE cmd_type, VkPipelineStageFlags2KHR pipelineStage, VkQueryPool queryPool,
                                            uint32_t slot) {
    RecordCmd(cmd_type);
    if (dev_data->disabled[query_validation]) return;

    if (!dev_data->disabled[command_buffer_state]) {
        auto pool_state = dev_data->Get<QUERY_POOL_STATE>(queryPool);
        AddChild(pool_state);
    }
    QueryObject query = {queryPool, slot};
    EndQuery(query);
}

void CMD_BUFFER_STATE::Submit(uint32_t perf_submit_pass) {
    VkQueryPool first_pool = VK_NULL_HANDLE;
    EventToStageMap local_event_to_stage_map;
    QueryMap local_query_to_state_map;
    for (auto &function : queryUpdates) {
        function(*this, /*do_validate*/ false, first_pool, perf_submit_pass, &local_query_to_state_map);
    }

    for (const auto &query_state_pair : local_query_to_state_map) {
        auto query_pool_state = dev_data->Get<QUERY_POOL_STATE>(query_state_pair.first.pool);
        query_pool_state->SetQueryState(query_state_pair.first.query, query_state_pair.first.perf_pass, query_state_pair.second);
    }

    for (const auto &function : eventUpdates) {
        function(*this, /*do_validate*/ false, &local_event_to_stage_map);
    }

    for (const auto &eventStagePair : local_event_to_stage_map) {
        auto event_state = dev_data->Get<EVENT_STATE>(eventStagePair.first);
        event_state->stageMask = eventStagePair.second;
    }

    for (const auto &it : video_session_updates) {
        auto video_session_state = dev_data->Get<VIDEO_SESSION_STATE>(it.first);
        auto device_state = video_session_state->DeviceStateWrite();
        for (const auto &function : it.second) {
            function(nullptr, video_session_state.get(), *device_state, /*do_validate*/ false);
        }
    }
}

void CMD_BUFFER_STATE::Retire(uint32_t perf_submit_pass, const std::function<bool(const QueryObject &)> &is_query_updated_after) {
    // First perform decrement on general case bound objects
    for (auto event : writeEventsBeforeWait) {
        auto event_state = dev_data->Get<EVENT_STATE>(event);
        if (event_state) {
            event_state->write_in_use--;
        }
    }
    QueryMap local_query_to_state_map;
    VkQueryPool first_pool = VK_NULL_HANDLE;
    for (auto &function : queryUpdates) {
        function(*this, /*do_validate*/ false, first_pool, perf_submit_pass, &local_query_to_state_map);
    }

    for (const auto &query_state_pair : local_query_to_state_map) {
        if (query_state_pair.second == QUERYSTATE_ENDED && !is_query_updated_after(query_state_pair.first)) {
            auto query_pool_state = dev_data->Get<QUERY_POOL_STATE>(query_state_pair.first.pool);
            if (query_pool_state) {
                query_pool_state->SetQueryState(query_state_pair.first.query, query_state_pair.first.perf_pass,
                                                QUERYSTATE_AVAILABLE);
            }
        }
    }
}

void CMD_BUFFER_STATE::UnbindResources() {
    // Vertex and index buffers
    index_buffer_binding.reset();
    vertex_buffer_used = false;
    current_vertex_buffer_binding_info.vertex_buffer_bindings.clear();

    // Push constants
    push_constant_data.clear();
    push_constant_data_ranges.reset();
    push_constant_data_update.clear();

    // Reset status of cb to force rebinding of all resources
    // Index buffer included
    status.reset();

    // Pipeline and descriptor sets
    lastBound[BindPoint_Graphics].Reset();
}

// Need to think about dynamic state when grabbing state
bool CMD_BUFFER_STATE::RasterizationDisabled() const {
    auto pipeline = lastBound[BindPoint_Graphics].pipeline_state;
    if (pipeline) {
        if (pipeline->IsDynamic(VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT)) {
            return rasterization_disabled;
        } else {
            return pipeline->RasterizationDisabled();
        }
    }

    return false;
}
