/* 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.
 * Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved.
 *
 * 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 "last_bound_state.h"
#include <vulkan/vulkan_core.h>
#include <cassert>
#include <string>
#include "containers/container_utils.h"
#include "state_tracker/descriptor_mode.h"
#include "state_tracker/pipeline_state.h"
#include "generated/dynamic_state_helper.h"
#include "state_tracker/descriptor_sets.h"
#include "state_tracker/cmd_buffer_state.h"
#include "state_tracker/shader_object_state.h"
#include "chassis/chassis_modification_state.h"
#include "utils/vk_api_utils.h"

void LastBound::UnbindAndResetPushDescriptorSet(std::shared_ptr<vvl::DescriptorSet>&& ds) {
    if (push_descriptor_set) {
        for (auto& ds_slot : ds_slots) {
            if (ds_slot.ds_state == push_descriptor_set) {
                cb_state.RemoveChild(ds_slot.ds_state);
                ds_slot.ds_state.reset();
            }
        }
    }
    cb_state.AddChild(ds);
    push_descriptor_set = std::move(ds);
}

bool LastBound::IsDynamic(const CBDynamicState state) const { return !pipeline_state || pipeline_state->IsDynamic(state); }

small_vector<const ShaderStageState*, 3> LastBound::GetStages() const {
    small_vector<const ShaderStageState*, 3> stages;
    if (pipeline_state) {
        for (const ShaderStageState& stage : pipeline_state->stage_states) {
            stages.emplace_back(&stage);
        }
    } else {
        for (const auto& shader_object : shader_object_states) {
            if (shader_object) {
                stages.emplace_back(&shader_object->stage);
            }
        }
    }
    return stages;
}

void LastBound::BindPipeline(vvl::Pipeline* pipe_state) {
    pipeline_state = pipe_state;

    // Mark any previous shader object binds as invalidated vkspec.html#shaders-objects-pipeline-interaction
    for (uint32_t i = 0; i < kShaderObjectStageCount; ++i) {
        shader_object_bound[i] = false;
        shader_object_states[i] = nullptr;
    }
}

void LastBound::BindShaderObject(VkShaderStageFlagBits shader_stage,
                                 const std::shared_ptr<vvl::ShaderObject>& shader_object_state) {
    const auto stage_index = static_cast<uint32_t>(VkShaderStageToShaderObjectStage(shader_stage));
    shader_object_bound[stage_index] = true;
    shader_object_states[stage_index] = shader_object_state;

    // We use this to mark any previous pipeline bounds are invalidated now
    // vkspec.html#shaders-objects-pipeline-interaction
    pipeline_state = nullptr;
}

void LastBound::Reset() {
    pipeline_state = nullptr;
    for (uint32_t i = 0; i < kShaderObjectStageCount; ++i) {
        shader_object_bound[i] = false;
        shader_object_states[i] = nullptr;
    }
    desc_set_pipeline_layout.reset();
    if (push_descriptor_set) {
        cb_state.RemoveChild(push_descriptor_set);
        push_descriptor_set->Destroy();
    }
    push_descriptor_set.reset();
    ds_slots.clear();
    descriptor_mode = vvl::DescriptorModeUnknown;
}

bool LastBound::IsDepthTestEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_DEPTH_TEST_ENABLE)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_DEPTH_TEST_ENABLE)) {
            return cb_state.dynamic_state_value.depth_test_enable;
        }
    } else {
        if (const auto ds_state = pipeline_state->DepthStencilState()) {
            return ds_state->depthTestEnable;
        }
    }
    return false;
}

bool LastBound::IsDepthBoundTestEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE)) {
            return cb_state.dynamic_state_value.depth_bounds_test_enable;
        }
    } else {
        if (const auto ds_state = pipeline_state->DepthStencilState()) {
            return ds_state->depthBoundsTestEnable;
        }
    }
    return false;
}

bool LastBound::IsDepthWriteEnable() const {
    // "Depth writes are always disabled when depthTestEnable is VK_FALSE"
    if (!IsDepthTestEnable()) {
        return false;
    }
    if (IsDynamic(CB_DYNAMIC_STATE_DEPTH_WRITE_ENABLE)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_DEPTH_WRITE_ENABLE)) {
            return cb_state.dynamic_state_value.depth_write_enable;
        }
    } else {
        if (const auto ds_state = pipeline_state->DepthStencilState()) {
            return ds_state->depthWriteEnable;
        }
    }
    return false;
}

bool LastBound::IsDepthBiasEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_DEPTH_BIAS_ENABLE)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_DEPTH_BIAS_ENABLE)) {
            return cb_state.dynamic_state_value.depth_bias_enable;
        }
    } else {
        if (const auto raster_state = pipeline_state->RasterizationState()) {
            return raster_state->depthBiasEnable;
        }
    }
    return false;
}

bool LastBound::IsDepthClampEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT)) {
            return cb_state.dynamic_state_value.depth_clamp_enable;
        }
    } else {
        if (const auto raster_state = pipeline_state->RasterizationState()) {
            return raster_state->depthClampEnable;
        }
    }
    return false;
}

bool LastBound::IsStencilTestEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_STENCIL_TEST_ENABLE)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_STENCIL_TEST_ENABLE)) {
            return cb_state.dynamic_state_value.stencil_test_enable;
        }
    } else {
        if (const auto ds_state = pipeline_state->DepthStencilState()) {
            return ds_state->stencilTestEnable;
        }
    }
    return false;
}

VkStencilOpState LastBound::GetStencilOpStateFront() const {
    VkStencilOpState front = {};
    if (pipeline_state && pipeline_state->DepthStencilState()) {
        front = pipeline_state->DepthStencilState()->front;
    }
    if (IsDynamic(CB_DYNAMIC_STATE_STENCIL_WRITE_MASK)) {
        front.writeMask = cb_state.dynamic_state_value.write_mask_front;
    }
    if (IsDynamic(CB_DYNAMIC_STATE_STENCIL_OP)) {
        front.failOp = cb_state.dynamic_state_value.fail_op_front;
        front.passOp = cb_state.dynamic_state_value.pass_op_front;
        front.depthFailOp = cb_state.dynamic_state_value.depth_fail_op_front;
    }
    return front;
}

VkStencilOpState LastBound::GetStencilOpStateBack() const {
    VkStencilOpState back = {};
    if (pipeline_state && pipeline_state->DepthStencilState()) {
        back = pipeline_state->DepthStencilState()->back;
    }
    if (IsDynamic(CB_DYNAMIC_STATE_STENCIL_WRITE_MASK)) {
        back.writeMask = cb_state.dynamic_state_value.write_mask_back;
    }
    if (IsDynamic(CB_DYNAMIC_STATE_STENCIL_OP)) {
        back.failOp = cb_state.dynamic_state_value.fail_op_back;
        back.passOp = cb_state.dynamic_state_value.pass_op_back;
        back.depthFailOp = cb_state.dynamic_state_value.depth_fail_op_back;
    }
    return back;
}

VkSampleCountFlagBits LastBound::GetRasterizationSamples() const {
    // For given pipeline, return number of MSAA samples, or one if MSAA disabled
    VkSampleCountFlagBits rasterization_samples = VK_SAMPLE_COUNT_1_BIT;
    if (IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT)) {
        rasterization_samples = cb_state.dynamic_state_value.rasterization_samples;
    } else {
        if (auto ms_state = pipeline_state->MultisampleState()) {
            rasterization_samples = ms_state->rasterizationSamples;
        }
    }
    return rasterization_samples;
}

bool LastBound::IsRasterizationDisabled() const {
    if (IsDynamic(CB_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE)) {
            return cb_state.dynamic_state_value.rasterizer_discard_enable;
        }
    } else {
        return pipeline_state->RasterizationDisabled();
    }
    return false;
}

std::string LastBound::DescribeRasterizationDisabled() const {
    if (IsDynamic(CB_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE)) {
        return "the last call to vkCmdSetRasterizerDiscardEnableEXT";
    }
    return "VkPipelineRasterizationStateCreateInfo::rasterizerDiscardEnable";
}

bool LastBound::IsLogicOpEnabled() const {
    if (IsDynamic(CB_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT)) {
            return cb_state.dynamic_state_value.logic_op_enable;
        }
    } else {
        if (const auto& color_blend = pipeline_state->ColorBlendState()) {
            return color_blend->logicOpEnable;
        }
    }
    return false;
}

VkColorComponentFlags LastBound::GetColorWriteMask(uint32_t i) const {
    if (IsDynamic(CB_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT)) {
        if (i < cb_state.dynamic_state_value.color_write_masks.size()) {
            return cb_state.dynamic_state_value.color_write_masks[i];
        }
    } else {
        if (const auto& color_blend = pipeline_state->ColorBlendState()) {
            if (i < color_blend->attachmentCount) {
                return color_blend->pAttachments[i].colorWriteMask;
            }
        }
    }
    return (VkColorComponentFlags)0u;
}

bool LastBound::IsColorWriteEnabled(uint32_t i) const {
    if (IsDynamic(CB_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT)) {
            return cb_state.dynamic_state_value.color_write_enabled[i];
        }
    } else {
        if (const auto& color_blend = pipeline_state->ColorBlendState()) {
            auto color_write = vku::FindStructInPNextChain<VkPipelineColorWriteCreateInfoEXT>(color_blend->pNext);
            if (color_write && i < color_write->attachmentCount) {
                return color_write->pColorWriteEnables[i];
            }
        }
    }
    return true;
}

bool LastBound::IsColorBlendEnabled(uint32_t i) const {
    if (IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT)) {
            return cb_state.dynamic_state_value.color_blend_enabled[i];
        }
    } else {
        if (const auto& color_blend = pipeline_state->ColorBlendState()) {
            if (i < color_blend->attachmentCount) {
                return color_blend->pAttachments[i].blendEnable;
            }
        }
    }
    return true;
}

std::string LastBound::DescribeColorBlendEnabled(uint32_t i) const {
    std::ostringstream ss;
    if (IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT)) {
            ss << "vkCmdSetColorBlendEnableEXT::pColorBlendEnables[" << i << "] is ";
            ss << (cb_state.dynamic_state_value.color_blend_enabled[i] ? "VK_TRUE" : "VK_FALSE");
        }
    } else {
        if (const auto& color_blend = pipeline_state->ColorBlendState()) {
            if (i < color_blend->attachmentCount) {
                ss << "VkPipelineColorBlendStateCreateInfo::pAttachments[" << i << "].blendEnable is ";
                ss << (color_blend->pAttachments[i].blendEnable ? "VK_TRUE" : "VK_FALSE");
            }
        }
    }
    return ss.str();
}

bool LastBound::IsBlendConstantsEnabled(uint32_t i) const {
    static constexpr std::array<VkBlendFactor, 4> const_factors = {
        VK_BLEND_FACTOR_CONSTANT_COLOR, VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR, VK_BLEND_FACTOR_CONSTANT_ALPHA,
        VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA};
    if (IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT)) {
            if (i >= cb_state.dynamic_state_value.color_blend_equations.size()) {
                return false;  // this color attachment was set with vkCmdSetColorBlendAdvancedEXT instead
            }
            const VkColorBlendEquationEXT& eq = cb_state.dynamic_state_value.color_blend_equations[i];
            return IsValueIn(eq.srcColorBlendFactor, const_factors) || IsValueIn(eq.dstColorBlendFactor, const_factors) ||
                   IsValueIn(eq.srcAlphaBlendFactor, const_factors) || IsValueIn(eq.dstAlphaBlendFactor, const_factors);
        }
    } else {
        if (const auto& color_blend = pipeline_state->ColorBlendState()) {
            if (i < color_blend->attachmentCount) {
                const VkPipelineColorBlendAttachmentState& eq = color_blend->pAttachments[i];
                return IsValueIn(eq.srcColorBlendFactor, const_factors) || IsValueIn(eq.dstColorBlendFactor, const_factors) ||
                       IsValueIn(eq.srcAlphaBlendFactor, const_factors) || IsValueIn(eq.dstAlphaBlendFactor, const_factors);
            }
        }
    }
    return false;
}

bool LastBound::IsDualBlending(uint32_t i) const {
    if (IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT)) {
            if (i >= cb_state.dynamic_state_value.color_blend_equations.size()) {
                return false;  // this color attachment was set with vkCmdSetColorBlendAdvancedEXT instead
            }
            const VkColorBlendEquationEXT& eq = cb_state.dynamic_state_value.color_blend_equations[i];
            return IsSecondaryColorInputBlendFactor(eq.srcColorBlendFactor) ||
                   IsSecondaryColorInputBlendFactor(eq.dstColorBlendFactor) ||
                   IsSecondaryColorInputBlendFactor(eq.srcAlphaBlendFactor) ||
                   IsSecondaryColorInputBlendFactor(eq.dstAlphaBlendFactor);
        }
    } else {
        if (const auto& color_blend = pipeline_state->ColorBlendState()) {
            if (i < color_blend->attachmentCount) {
                const VkPipelineColorBlendAttachmentState& eq = color_blend->pAttachments[i];
                return IsSecondaryColorInputBlendFactor(eq.srcColorBlendFactor) ||
                       IsSecondaryColorInputBlendFactor(eq.dstColorBlendFactor) ||
                       IsSecondaryColorInputBlendFactor(eq.srcAlphaBlendFactor) ||
                       IsSecondaryColorInputBlendFactor(eq.dstAlphaBlendFactor);
            }
        }
    }
    return false;
}

std::string LastBound::DescribeBlendFactorEquation(uint32_t i) const {
    std::ostringstream ss;
    if (IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT)) {
            const VkColorBlendEquationEXT& eq = cb_state.dynamic_state_value.color_blend_equations[i];
            ss << "The following are set by vkCmdSetColorBlendEquationEXT::pColorBlendEquations[" << i << "]\n";
            ss << "  srcColorBlendFactor = " << string_VkBlendFactor(eq.srcColorBlendFactor) << "\n";
            ss << "  dstColorBlendFactor = " << string_VkBlendFactor(eq.dstColorBlendFactor) << "\n";
            ss << "  srcAlphaBlendFactor = " << string_VkBlendFactor(eq.srcAlphaBlendFactor) << "\n";
            ss << "  dstAlphaBlendFactor = " << string_VkBlendFactor(eq.dstAlphaBlendFactor) << "\n";
        }
    } else {
        if (const auto& color_blend = pipeline_state->ColorBlendState()) {
            if (i < color_blend->attachmentCount) {
                const VkPipelineColorBlendAttachmentState& eq = color_blend->pAttachments[i];
                ss << "The following are set by VkPipelineColorBlendStateCreateInfo::pAttachments[" << i << "]\n";
                ss << "  srcColorBlendFactor = " << string_VkBlendFactor(eq.srcColorBlendFactor) << "\n";
                ss << "  dstColorBlendFactor = " << string_VkBlendFactor(eq.dstColorBlendFactor) << "\n";
                ss << "  srcAlphaBlendFactor = " << string_VkBlendFactor(eq.srcAlphaBlendFactor) << "\n";
                ss << "  dstAlphaBlendFactor = " << string_VkBlendFactor(eq.dstAlphaBlendFactor) << "\n";
            }
        }
    }
    return ss.str();
}

VkCullModeFlags LastBound::GetCullMode() const {
    if (IsDynamic(CB_DYNAMIC_STATE_CULL_MODE)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_CULL_MODE)) {
            return cb_state.dynamic_state_value.cull_mode;
        }
    } else {
        if (auto raster_state = pipeline_state->RasterizationState()) {
            return raster_state->cullMode;
        }
    }
    return VK_CULL_MODE_NONE;
}

VkConservativeRasterizationModeEXT LastBound::GetConservativeRasterizationMode() const {
    if (IsDynamic(CB_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT)) {
            return cb_state.dynamic_state_value.conservative_rasterization_mode;
        }
    } else {
        if (const auto rasterization_conservative_state_ci =
                vku::FindStructInPNextChain<VkPipelineRasterizationConservativeStateCreateInfoEXT>(
                    pipeline_state->RasterizationStatePNext())) {
            return rasterization_conservative_state_ci->conservativeRasterizationMode;
        }
    }
    return VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT;
}

bool LastBound::IsSampleLocationsEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT)) {
            return cb_state.dynamic_state_value.sample_locations_enable;
        }
    } else {
        if (auto ms_state = pipeline_state->MultisampleState()) {
            if (const auto* sample_location_state =
                    vku::FindStructInPNextChain<VkPipelineSampleLocationsStateCreateInfoEXT>(ms_state->pNext)) {
                return sample_location_state->sampleLocationsEnable;
            }
        }
    }
    return false;
}

bool LastBound::IsExclusiveScissorEnabled() const {
    if (IsDynamic(CB_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_ENABLE_NV)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_ENABLE_NV)) {
            for (uint32_t i = 0; i < cb_state.dynamic_state_value.exclusive_scissor_enable_count; ++i) {
                if (cb_state.dynamic_state_value
                        .exclusive_scissor_enables[cb_state.dynamic_state_value.exclusive_scissor_enable_first + i]) {
                    return true;
                }
            }
        }
    } else {
        return true;  // no pipeline state, but if not dynamic, defaults to being enabled
    }
    return false;
}

bool LastBound::IsCoverageToColorEnabled() const {
    if (IsDynamic(CB_DYNAMIC_STATE_COVERAGE_TO_COLOR_ENABLE_NV)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_COVERAGE_TO_COLOR_ENABLE_NV)) {
            return cb_state.dynamic_state_value.coverage_to_color_enable;
        }
    } else {
        if (auto ms_state = pipeline_state->MultisampleState()) {
            if (const auto* coverage_to_color_state =
                    vku::FindStructInPNextChain<VkPipelineCoverageToColorStateCreateInfoNV>(ms_state->pNext)) {
                return coverage_to_color_state->coverageToColorEnable;
            }
        }
    }
    return false;
}

bool LastBound::IsCoverageModulationTableEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_ENABLE_NV)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_ENABLE_NV)) {
            return cb_state.dynamic_state_value.coverage_modulation_table_enable;
        }
    } else {
        if (auto ms_state = pipeline_state->MultisampleState()) {
            if (const auto* coverage_modulation_state =
                    vku::FindStructInPNextChain<VkPipelineCoverageModulationStateCreateInfoNV>(ms_state->pNext)) {
                return coverage_modulation_state->coverageModulationTableEnable;
            }
        }
    }
    return false;
}

bool LastBound::IsStippledLineEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT)) {
            return cb_state.dynamic_state_value.stippled_line_enable;
        }
    } else {
        if (const auto line_state_ci = vku::FindStructInPNextChain<VkPipelineRasterizationLineStateCreateInfo>(
                pipeline_state->RasterizationStatePNext())) {
            return line_state_ci->stippledLineEnable;
        }
    }
    return false;
}

bool LastBound::IsDiscardRectangleEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_DISCARD_RECTANGLE_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_DISCARD_RECTANGLE_ENABLE_EXT)) {
            return cb_state.dynamic_state_value.discard_rectangle_enable;
        }
    } else {
        // VK_EXT_discard_rectangles had a special v2 added right away to give it dynamic state
        // "If the VK_DYNAMIC_STATE_DISCARD_RECTANGLE_ENABLE_EXT dynamic state is not enabled for the pipeline the presence of this
        // structure in the VkGraphicsPipelineCreateInfo chain, and a discardRectangleCount greater than zero, implicitly enables
        // discard rectangles in the pipeline"
        const void* pipeline_pnext = pipeline_state->GetCreateInfoPNext();
        if (const auto* discard_rectangle_state =
                vku::FindStructInPNextChain<VkPipelineDiscardRectangleStateCreateInfoEXT>(pipeline_pnext)) {
            return discard_rectangle_state->discardRectangleCount > 0;
        }
    }
    return false;
}

bool LastBound::IsShadingRateImageEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_SHADING_RATE_IMAGE_ENABLE_NV)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_SHADING_RATE_IMAGE_ENABLE_NV)) {
            return cb_state.dynamic_state_value.shading_rate_image_enable;
        }
    } else {
        if (auto viewport_state = pipeline_state->ViewportState()) {
            if (const auto* shading_rate_image_state =
                    vku::FindStructInPNextChain<VkPipelineViewportShadingRateImageStateCreateInfoNV>(viewport_state->pNext)) {
                return shading_rate_image_state->shadingRateImageEnable;
            }
        }
    }
    return false;
}

bool LastBound::IsViewportWScalingEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_VIEWPORT_W_SCALING_ENABLE_NV)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_VIEWPORT_W_SCALING_ENABLE_NV)) {
            return cb_state.dynamic_state_value.viewport_w_scaling_enable;
        }
    } else {
        if (auto viewport_state = pipeline_state->ViewportState()) {
            if (const auto* viewport_w_scaling_state =
                    vku::FindStructInPNextChain<VkPipelineViewportWScalingStateCreateInfoNV>(viewport_state->pNext)) {
                return viewport_w_scaling_state->viewportWScalingEnable;
            }
        }
    }
    return false;
}

bool LastBound::IsPrimitiveRestartEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE)) {
            return cb_state.dynamic_state_value.primitive_restart_enable;
        }
    } else {
        if (auto ia_state = pipeline_state->InputAssemblyState()) {
            return ia_state->primitiveRestartEnable == VK_TRUE;
        }
    }
    return false;
}

bool LastBound::IsAlphaToCoverageEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT)) {
            return cb_state.dynamic_state_value.alpha_to_coverage_enable;
        }
    } else {
        if (auto ms_state = pipeline_state->MultisampleState()) {
            return ms_state->alphaToCoverageEnable == VK_TRUE;
        }
    }
    return false;
}

bool LastBound::IsAlphaToOneEnable() const {
    if (IsDynamic(CB_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT)) {
            return cb_state.dynamic_state_value.alpha_to_one_enable;
        }
    } else {
        if (auto ms_state = pipeline_state->MultisampleState()) {
            return ms_state->alphaToOneEnable == VK_TRUE;
        }
    }
    return false;
}

VkCoverageModulationModeNV LastBound::GetCoverageModulationMode() const {
    if (IsDynamic(CB_DYNAMIC_STATE_COVERAGE_MODULATION_MODE_NV)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_COVERAGE_MODULATION_MODE_NV)) {
            return cb_state.dynamic_state_value.coverage_modulation_mode;
        }
    } else {
        if (auto ms_state = pipeline_state->MultisampleState()) {
            if (const auto* coverage_modulation_state =
                    vku::FindStructInPNextChain<VkPipelineCoverageModulationStateCreateInfoNV>(ms_state->pNext)) {
                return coverage_modulation_state->coverageModulationMode;
            }
        }
    }
    return VK_COVERAGE_MODULATION_MODE_NONE_NV;
}

uint32_t LastBound::GetViewportSwizzleCount() const {
    if (IsDynamic(CB_DYNAMIC_STATE_VIEWPORT_SWIZZLE_NV)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_VIEWPORT_SWIZZLE_NV)) {
            return cb_state.dynamic_state_value.viewport_swizzle_count;
        }
    } else {
        if (auto viewport_state = pipeline_state->ViewportState()) {
            if (const auto* viewport_swizzle_state =
                    vku::FindStructInPNextChain<VkPipelineViewportSwizzleStateCreateInfoNV>(viewport_state->pNext)) {
                return viewport_swizzle_state->viewportCount;
            }
        }
    }
    return 0;
}

VkPolygonMode LastBound::GetPolygonMode() const {
    if (IsDynamic(CB_DYNAMIC_STATE_POLYGON_MODE_EXT)) {
        if (cb_state.IsDynamicStateSet(CB_DYNAMIC_STATE_POLYGON_MODE_EXT)) {
            return cb_state.dynamic_state_value.polygon_mode;
        }
    } else {
        if (pipeline_state->RasterizationState()) {
            return pipeline_state->RasterizationState()->polygonMode;
        }
    }
    return VK_POLYGON_MODE_MAX_ENUM;
}

// vkspec.html#drawing-vertex-input-assembler-topology
// When calling, we don't have to worry about Mesh shading because either VUs like 07065/07066 prevent these dynamic state being set
// and the VkPipelineInputAssemblyStateCreateInfo is ignored
VkPrimitiveTopology LastBound::GetVertexInputAssemblerTopology() const {
    if (IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY)) {
        return cb_state.dynamic_state_value.primitive_topology;
    } else {
        if (auto ia_state = pipeline_state->InputAssemblyState()) {
            return ia_state->topology;
        }
    }
    return VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
}

std::string LastBound::DescribeVertexInputAssemblerTopology() const {
    if (IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY)) {
        return "the last call to vkCmdSetPrimitiveTopology";
    }
    return "VkPipelineInputAssemblyStateCreateInfo::topology";
}

// vkspec.html#drawing-clip-space-topology
VkPrimitiveTopology LastBound::ClipSpaceTopology() const {
    VkShaderStageFlags bound_stages = GetAllActiveBoundStages();
    const bool geom_shader_bound = (bound_stages & VK_SHADER_STAGE_GEOMETRY_BIT) != 0;
    const bool tesc_shader_bound = (bound_stages & VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT) != 0;
    const bool tese_shader_bound = (bound_stages & VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) != 0;
    const bool mesh_shader_bound = (bound_stages & VK_SHADER_STAGE_MESH_BIT_EXT) != 0;
    if (!geom_shader_bound && !tesc_shader_bound && !tese_shader_bound && !mesh_shader_bound) {
        return GetVertexInputAssemblerTopology();  // vertex is the last pre-rasterization stage
    }

    if (pipeline_state) {
        if (mesh_shader_bound || geom_shader_bound) {
            // Can only have either a mesh or geometry stage, so can search both at once
            assert(!mesh_shader_bound || !geom_shader_bound);
            for (const ShaderStageState& shader_stage_state : pipeline_state->stage_states) {
                const VkShaderStageFlagBits stage = shader_stage_state.GetStage();
                if (stage == VK_SHADER_STAGE_MESH_BIT_EXT || stage == VK_SHADER_STAGE_GEOMETRY_BIT) {
                    if (shader_stage_state.HasSpirv()) {
                        return shader_stage_state.entrypoint->execution_mode.GetGeometryMeshOutputTopology();
                    }
                }
            }
        } else if (tesc_shader_bound || tese_shader_bound) {
            VkPrimitiveTopology tess_output_topology = VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
            for (const ShaderStageState& shader_stage_state : pipeline_state->stage_states) {
                const VkShaderStageFlagBits stage = shader_stage_state.GetStage();
                if (stage == VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT || stage == VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT) {
                    if (shader_stage_state.HasSpirv()) {
                        // In tessellation shaders, PointMode is separate and trumps the tessellation topology.
                        // Can be found in both tessellation shaders
                        if (shader_stage_state.entrypoint->execution_mode.Has(spirv::ExecutionModeSet::point_mode_bit)) {
                            return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
                        } else if (stage == VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) {
                            tess_output_topology =
                                shader_stage_state.entrypoint->execution_mode.GetTessellationEvalOutputTopology();
                        }
                    }
                }
            }

            return tess_output_topology;
        }
    } else {  // shader object
        if (mesh_shader_bound) {
            vvl::ShaderObject* mesh_shader = GetShaderObjectState(ShaderObjectStage::MESH);
            if (mesh_shader && mesh_shader->stage.entrypoint) {
                return mesh_shader->stage.entrypoint->execution_mode.GetGeometryMeshOutputTopology();
            }
        } else if (geom_shader_bound) {
            vvl::ShaderObject* geom_shader = GetShaderObjectState(ShaderObjectStage::GEOMETRY);
            if (geom_shader && geom_shader->stage.entrypoint) {
                return geom_shader->stage.entrypoint->execution_mode.GetGeometryMeshOutputTopology();
            }
        } else if (tesc_shader_bound || tese_shader_bound) {
            VkPrimitiveTopology tess_output_topology = VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
            vvl::ShaderObject* tesc_shader = GetShaderObjectState(ShaderObjectStage::TESSELLATION_CONTROL);
            if (tesc_shader && tesc_shader->stage.entrypoint) {
                if (tesc_shader->stage.entrypoint->execution_mode.Has(spirv::ExecutionModeSet::point_mode_bit)) {
                    return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
                }
            }

            vvl::ShaderObject* tese_shader = GetShaderObjectState(ShaderObjectStage::TESSELLATION_EVALUATION);
            if (tese_shader && tese_shader->stage.entrypoint) {
                if (tese_shader->stage.entrypoint->execution_mode.Has(spirv::ExecutionModeSet::point_mode_bit)) {
                    return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
                } else {
                    tess_output_topology = tese_shader->stage.entrypoint->execution_mode.GetTessellationEvalOutputTopology();
                }
            }

            return tess_output_topology;
        }
    }

    // can happen when dealing with things like VK_SHADER_CODE_TYPE_SPIRV_EXT
    return VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
}

// vkspec.html#drawing-rasterization-input-topology
// The Topology can alter from Vertex/Mesh before going to rasterization
// This is the "final" topology which most VUs care about
// For additional info https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/primitive_topology.adoc
VkPrimitiveTopology LastBound::GetRasterizationInputTopology() const {
    VkPrimitiveTopology topology = ClipSpaceTopology();
    VkPolygonMode polygon_mode = GetPolygonMode();

    if (polygon_mode == VK_POLYGON_MODE_POINT && (IsLineTopology(topology) || IsTriangleTopology(topology))) {
        topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
    } else if (polygon_mode == VK_POLYGON_MODE_LINE && IsTriangleTopology(topology)) {
        topology = TriangleToLineTopology(topology);
    }

    return topology;
}

bool LastBound::IsSampleShadingEnabled() const {
    // There is no dynamic state for sampleShadingEnable (or minSampleShading) and instead it can be implicitly set in one of 3 ways
    // as described in https://godbolt.org/z/7KdqafWrj
    auto fragment_entry_point = GetFragmentEntryPoint();
    if (!fragment_entry_point) {
        return false;  // if no fragment shader, no sample shading
    }

    for (const auto& variable : fragment_entry_point->stage_interface_variables) {
        if (variable.storage_class != spv::StorageClassInput) {
            continue;
        }
        if (variable.decorations.Has(spirv::DecorationSet::sample_bit) || variable.decorations.built_in == spv::BuiltInSampleId ||
            variable.decorations.built_in == spv::BuiltInSamplePosition) {
            return true;
        }
    }

    // Need to check implicit first as it override the explicit values
    if (pipeline_state) {
        if (auto ms_state = pipeline_state->MultisampleState()) {
            return ms_state->sampleShadingEnable;  // explicitly enabled
        }
    }

    return false;
}

float LastBound::GetMinSampleShading() const {
    // assumes sample shading is enabled, the minSampleShading can have 2 values
    // 1. If explicitly enabled, read minSampleShading from pipeline state
    // 2. If implicitly enabled, it is always going to be 1.0
    if (pipeline_state) {
        if (auto ms_state = pipeline_state->MultisampleState()) {
            return ms_state->minSampleShading;
        }
    }
    return 1.0;
}

std::string LastBound::DescribeSampleShading() const {
    std::ostringstream ss;
    ss << "Sample Shading was enabled ";

    bool is_implicit = false;
    if (auto fragment_entry_point = GetFragmentEntryPoint()) {
        for (const auto& variable : fragment_entry_point->stage_interface_variables) {
            if (variable.storage_class != spv::StorageClassInput) {
                continue;
            }
            if (variable.decorations.Has(spirv::DecorationSet::sample_bit)) {
                ss << "implicitly in the fragment shader because there is a Sample decorated input variable.";
                is_implicit = true;
                break;
            } else if (variable.decorations.built_in == spv::BuiltInSampleId) {
                ss << "implicitly in the fragment shader because there is a SampleId BuiltIn decorated input variable. "
                      "(gl_SampleID)";
                is_implicit = true;
                break;
            } else if (variable.decorations.built_in == spv::BuiltInSamplePosition) {
                ss << "implicitly in the fragment shader because there is a SamplePosition BuiltIn decorated input variable. "
                      "(gl_SamplePosition)";
                is_implicit = true;
                break;
            }
        }
    }
    if (is_implicit) {
        ss << "\nminSampleShading is always 1.0 when sample shading is implicitly enabled";
    } else if (pipeline_state && pipeline_state->MultisampleState()) {
        ss << "explicitly from VkPipelineMultisampleStateCreateInfo::sampleShadingEnable set to "
              "VK_TRUE.\nVkPipelineMultisampleStateCreateInfo::minSampleShading = "
           << pipeline_state->MultisampleState()->minSampleShading;
    } else {
        assert(false);
    }
    return ss.str();
}

VkShaderEXT LastBound::GetShaderObject(ShaderObjectStage stage) const {
    if (!IsValidShaderObjectBound(stage) || GetShaderObjectState(stage) == nullptr) {
        return VK_NULL_HANDLE;
    }
    return shader_object_states[static_cast<uint32_t>(stage)]->VkHandle();
}

vvl::ShaderObject* LastBound::GetShaderObjectState(ShaderObjectStage stage) const {
    return shader_object_states[static_cast<uint32_t>(stage)].get();
}

const vvl::ShaderObject* LastBound::GetShaderObjectStateIfValid(ShaderObjectStage stage) const {
    if (!shader_object_bound[static_cast<uint32_t>(stage)]) {
        return nullptr;
    }
    return shader_object_states[static_cast<uint32_t>(stage)].get();
}

const vvl::ShaderObject* LastBound::GetFirstShader() const {
    if (bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) {
        return GetShaderObjectStateIfValid(ShaderObjectStage::COMPUTE);
    } else if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) {
        if (const vvl::ShaderObject* vs = GetShaderObjectStateIfValid(ShaderObjectStage::VERTEX)) {
            return vs;
        }

        if (const vvl::ShaderObject* ms = GetShaderObjectStateIfValid(ShaderObjectStage::MESH)) {
            return ms;
        }
    }

    return nullptr;
}

bool LastBound::HasShaderObjects() const {
    for (uint32_t i = 0; i < kShaderObjectStageCount; ++i) {
        if (GetShaderObject(static_cast<ShaderObjectStage>(i)) != VK_NULL_HANDLE) {
            return true;
        }
    }
    return false;
}

bool LastBound::IsValidShaderObjectBound(ShaderObjectStage stage) const { return GetShaderObjectStateIfValid(stage) != nullptr; }

bool LastBound::IsValidShaderObjectOrNullBound(ShaderObjectStage stage) const {
    return shader_object_bound[static_cast<uint32_t>(stage)];
}

std::vector<vvl::ShaderObject*> LastBound::GetAllBoundGraphicsShaderObjects() {
    std::vector<vvl::ShaderObject*> shaders;

    if (IsValidShaderObjectBound(ShaderObjectStage::VERTEX)) {
        shaders.emplace_back(shader_object_states[static_cast<uint32_t>(ShaderObjectStage::VERTEX)].get());
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::TESSELLATION_CONTROL)) {
        shaders.emplace_back(shader_object_states[static_cast<uint32_t>(ShaderObjectStage::TESSELLATION_CONTROL)].get());
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::TESSELLATION_EVALUATION)) {
        shaders.emplace_back(shader_object_states[static_cast<uint32_t>(ShaderObjectStage::TESSELLATION_EVALUATION)].get());
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::GEOMETRY)) {
        shaders.emplace_back(shader_object_states[static_cast<uint32_t>(ShaderObjectStage::GEOMETRY)].get());
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::FRAGMENT)) {
        shaders.emplace_back(shader_object_states[static_cast<uint32_t>(ShaderObjectStage::FRAGMENT)].get());
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::TASK)) {
        shaders.emplace_back(shader_object_states[static_cast<uint32_t>(ShaderObjectStage::TASK)].get());
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::MESH)) {
        shaders.emplace_back(shader_object_states[static_cast<uint32_t>(ShaderObjectStage::MESH)].get());
    }

    return shaders;
}

bool LastBound::IsStageBound(VkShaderStageFlagBits stage) const {
    if (pipeline_state) {
        return (pipeline_state->active_shaders & stage) != 0;
    } else {
        const ShaderObjectStage shader_object_stage = VkShaderStageToShaderObjectStage(stage);
        return GetShaderObjectStateIfValid(shader_object_stage) != nullptr;
    }
}

bool LastBound::IsAnyGraphicsStageBound() const {
    if (pipeline_state) {
        return (pipeline_state->active_shaders & kShaderStageAllGraphics) != 0;
    } else {
        return IsValidShaderObjectBound(ShaderObjectStage::VERTEX) ||
               IsValidShaderObjectBound(ShaderObjectStage::TESSELLATION_CONTROL) ||
               IsValidShaderObjectBound(ShaderObjectStage::TESSELLATION_EVALUATION) ||
               IsValidShaderObjectBound(ShaderObjectStage::GEOMETRY) || IsValidShaderObjectBound(ShaderObjectStage::FRAGMENT) ||
               IsValidShaderObjectBound(ShaderObjectStage::TASK) || IsValidShaderObjectBound(ShaderObjectStage::MESH);
    }
}

VkShaderStageFlags LastBound::GetAllActiveBoundStages() const {
    if (pipeline_state) {
        return pipeline_state->active_shaders;
    }
    // else shader object
    VkShaderStageFlags stages = 0;
    if (IsValidShaderObjectBound(ShaderObjectStage::VERTEX)) {
        stages |= VK_SHADER_STAGE_VERTEX_BIT;
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::TESSELLATION_CONTROL)) {
        stages |= VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::TESSELLATION_EVALUATION)) {
        stages |= VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::GEOMETRY)) {
        stages |= VK_SHADER_STAGE_GEOMETRY_BIT;
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::FRAGMENT)) {
        stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::COMPUTE)) {
        stages |= VK_SHADER_STAGE_COMPUTE_BIT;
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::TASK)) {
        stages |= VK_SHADER_STAGE_TASK_BIT_EXT;
    }
    if (IsValidShaderObjectBound(ShaderObjectStage::MESH)) {
        stages |= VK_SHADER_STAGE_MESH_BIT_EXT;
    }
    return stages;
}

bool LastBound::IsBoundSetCompatible(uint32_t set, const vvl::PipelineLayout& pipeline_layout) const {
    if ((set >= ds_slots.size()) || (set >= pipeline_layout.set_compat_ids.size())) {
        return false;
    }
    // (because I always forget) Checked in PipelineLayoutCompatDef::operator==
    return (*(ds_slots[set].compat_id_for_set) == *(pipeline_layout.set_compat_ids[set]));
}

bool LastBound::IsBoundSetCompatible(uint32_t set, const vvl::ShaderObject& shader_object_state) const {
    if ((set >= ds_slots.size()) || (set >= shader_object_state.set_compat_ids.size())) {
        return false;
    }
    // (because I always forget) Checked in PipelineLayoutCompatDef::operator==
    return (*(ds_slots[set].compat_id_for_set) == *(shader_object_state.set_compat_ids[set]));
};

std::string LastBound::DescribeNonCompatibleSet(uint32_t set, const vvl::PipelineLayout& pipeline_layout) const {
    std::ostringstream ss;
    if (set >= ds_slots.size()) {
        ss << "The set (" << set << ") is out of bounds for the number of sets bound (" << ds_slots.size()
           << ")\nHint: Make sure the previous calls to " << String(desc_set_bound_command)
           << " has all sets bound that are needed.";
    } else if (set >= pipeline_layout.set_compat_ids.size()) {
        ss << "The set (" << set << ") is out of bounds for the number of sets in the non-compatible VkPipelineLayout ("
           << pipeline_layout.set_compat_ids.size() << ")\n";
    } else {
        return ds_slots[set].compat_id_for_set->DescribeDifference(*(pipeline_layout.set_compat_ids[set]));
    }
    return ss.str();
}

std::string LastBound::DescribeNonCompatibleSet(uint32_t set, const vvl::ShaderObject& shader_object_state) const {
    std::ostringstream ss;
    if (set >= ds_slots.size()) {
        ss << "The bound shader VkShaderCreateInfoEXT::pSetLayouts[" << set << "] is out of bounds for the number of sets bound ("
           << ds_slots.size() << ") in the previous call to " << String(desc_set_bound_command);
    } else if (set >= shader_object_state.set_compat_ids.size()) {
        ss << "The set (" << set << ") is out of bounds for the number of sets in the non-compatible VkDescriptorSetLayout ("
           << shader_object_state.set_compat_ids.size() << ")\n";
    } else {
        return ds_slots[set].compat_id_for_set->DescribeDifference(*(shader_object_state.set_compat_ids[set]));
    }
    return ss.str();
}

const spirv::EntryPoint* LastBound::GetVertexEntryPoint() const {
    if (pipeline_state) {
        const ShaderStageState* stage_state = pipeline_state->GetShaderStageState(VK_SHADER_STAGE_VERTEX_BIT);
        if (stage_state) {
            return stage_state->entrypoint.get();
        }
    } else if (const auto* shader_object = GetShaderObjectState(ShaderObjectStage::VERTEX)) {
        return shader_object->stage.entrypoint.get();
    }
    return nullptr;
}

const spirv::EntryPoint* LastBound::GetFragmentEntryPoint() const {
    if (pipeline_state && pipeline_state->fragment_shader_state) {
        return pipeline_state->fragment_shader_state->fragment_entry_point.get();
    } else if (const auto* shader_object = GetShaderObjectState(ShaderObjectStage::FRAGMENT)) {
        return shader_object->stage.entrypoint.get();
    }
    return nullptr;
}

vvl::DescriptorMode LastBound::GetActionDescriptorMode() const {
    if (descriptor_mode != vvl::DescriptorModeUnknown) {
        return descriptor_mode;  // Most common case
    }

    // This is only needed at draw/dispatch time when there is a chance there is not bound descriptor, but can still find from a
    // pipeline/layout
    if (pipeline_state) {
        if (pipeline_state->descriptor_buffer_mode) {
            return vvl::DescriptorModeBuffer;
        } else if (pipeline_state->descriptor_heap_mode) {
            return vvl::DescriptorModeHeap;
        } else {
            return vvl::DescriptorModeClassic;
        }
    } else {
        // Shader Object
        if (GetFirstShader() && GetFirstShader()->descriptor_heap_mode) {
            return vvl::DescriptorModeHeap;
        }
        if (desc_set_pipeline_layout) {
            if (desc_set_pipeline_layout->has_descriptor_buffer) {
                return vvl::DescriptorModeBuffer;
            } else {
                return vvl::DescriptorModeClassic;
            }
        }
    }
    // Not sure how to find it if in this situation, so resort to a safe choice
    return vvl::DescriptorModeClassic;
}

std::string LastBound::DescribeInvalidDescriptorMode() const {
    std::stringstream ss;

    // If it is unknown, the user has just never set any descriptors
    if (previous_descriptor_mode != vvl::DescriptorModeUnknown) {
        ss << "\nThe command buffer is currently in ";

        if (descriptor_mode == vvl::DescriptorModeClassic) {
            ss << "'Classic Descriptor'";
        } else if (descriptor_mode == vvl::DescriptorModeBuffer) {
            ss << "'Descriptor Buffer'";
        } else if (descriptor_mode == vvl::DescriptorModeHeap) {
            ss << "'Descriptor Heap'";
        }
        ss << " mode from previous call to " << String(set_descriptor_mode) << " which invalidated the previous ";

        if (previous_descriptor_mode == vvl::DescriptorModeClassic) {
            ss << "'Classic Descriptor'";
        } else if (previous_descriptor_mode == vvl::DescriptorModeBuffer) {
            ss << "'Descriptor Buffer'";
        } else if (previous_descriptor_mode == vvl::DescriptorModeHeap) {
            ss << "'Descriptor Heap'";
        }
        ss << " mode that was set with " << String(previous_set_descriptor_mode);
    }

    return ss.str();
}