/* 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-2022,2025-2026 Advanced Micro Devices, Inc. All rights reserved.
 * Modifications Copyright (C) 2022 RasterGrid Kft.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <sstream>
#include <string>
#include <vector>

#include <vulkan/vk_enum_string_helper.h>
#include <vulkan/vulkan_core.h>
#include "containers/container_utils.h"
#include "containers/custom_containers.h"
#include "core_checks/cc_state_tracker.h"
#include "error_message/error_location.h"
#include "error_message/logging.h"
#include "state_tracker/last_bound_state.h"
#include "utils/assert_utils.h"
#include "utils/math_utils.h"
#include "utils/vk_struct_compare.h"
#include "utils/action_command_utils.h"
#include "utils/image_utils.h"
#include "core_validation.h"
#include "generated/enum_flag_bits.h"
#include "generated/dispatch_functions.h"
#include "drawdispatch/drawdispatch_vuids.h"
#include "chassis/chassis_modification_state.h"
#include "state_tracker/image_state.h"
#include "state_tracker/descriptor_sets.h"
#include "state_tracker/render_pass_state.h"
#include "state_tracker/cmd_buffer_state.h"
#include "state_tracker/pipeline_state.h"
#include "error_message/error_strings.h"

bool CoreChecks::PreCallValidateCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count,
                                                        const VkGraphicsPipelineCreateInfo* pCreateInfos,
                                                        const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines,
                                                        const ErrorObject& error_obj, PipelineStates& pipeline_states,
                                                        chassis::CreateGraphicsPipelines& chassis_state) const {
    bool skip = false;

    skip |= ValidateDeviceQueueSupport(error_obj.location);
    for (uint32_t i = 0; i < count; i++) {
        const Location create_info_loc = error_obj.location.dot(Field::pCreateInfos, i);
        skip |= ValidateGraphicsPipeline(*pipeline_states[i].get(), pCreateInfos[i].pNext, create_info_loc);
        skip |= ValidateGraphicsPipelineDerivatives(pipeline_states, i, create_info_loc);

        // From dumping traces, we found almost all apps only create one pipeline at a time. To greatly simplify the logic, only
        // check the stateless validation in the pNext chain for the first pipeline. (The core issue is because we parse the SPIR-V
        // at state tracking time, and we state track pipelines first)
        if (i == 0) {
            uint32_t stage_count = std::min(pCreateInfos[0].stageCount, kCommonMaxGraphicsShaderStages);
            for (uint32_t stage = 0; stage < stage_count; stage++) {
                if (chassis_state.stateless_data[stage].pipeline_pnext_module) {
                    skip |= stateless_spirv_validator.Validate(
                        *chassis_state.stateless_data[stage].pipeline_pnext_module, chassis_state.stateless_data[stage],
                        create_info_loc.dot(Field::pStages, stage).pNext(Struct::VkShaderModuleCreateInfo, Field::pCode));
                }
            }
        }
    }
    return skip;
}

bool CoreChecks::ValidateGraphicsPipeline(const vvl::Pipeline& pipeline, const void* pipeline_ci_pnext,
                                          const Location& create_info_loc) const {
    bool skip = false;
    // It would be ideal to split all pipeline checks into Dynamic and Non-Dynamic renderpasses, but with GPL it gets a bit tricky.
    // Also you might be deep in a function and it is easier to do a if/else check if it is dynamic rendering or not there.
    vku::safe_VkSubpassDescription2* subpass_desc = nullptr;
    const auto rp_state = pipeline.RenderPassState();

    if (pipeline.descriptor_heap_embedded_samplers_count > 0) {
        skip |= ValidateEmbeddedSamplersCount(pipeline.descriptor_heap_embedded_samplers_count, create_info_loc);
    }

    if (rp_state && rp_state->UsesDynamicRendering()) {
        skip |= ValidateGraphicsPipelineExternalFormatResolveDynamicRendering(pipeline, create_info_loc);
        skip |= ValidateGraphicsPipelineDynamicRendering(pipeline, create_info_loc);
    } else if (rp_state && !rp_state->UsesDynamicRendering()) {
        const uint32_t subpass = pipeline.Subpass();
        if (subpass >= rp_state->create_info.subpassCount) {
            skip |=
                LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-06046", rp_state->Handle(),
                         create_info_loc.dot(Field::subpass), "(%" PRIu32 ") is out of range for this renderpass (0..%" PRIu32 ").",
                         subpass, rp_state->create_info.subpassCount - 1);
            subpass_desc = nullptr;
        } else {
            subpass_desc = &rp_state->create_info.pSubpasses[subpass];
            skip |= ValidateGraphicsPipelineExternalFormatResolve(pipeline, *rp_state, *subpass_desc, create_info_loc);
            skip |= ValidateGraphicsPipelineMultisampleState(pipeline, *rp_state, *subpass_desc, create_info_loc);
            skip |= ValidateGraphicsPipelineRenderPassRasterization(pipeline, *rp_state, *subpass_desc, create_info_loc);

            if (subpass_desc->viewMask != 0) {
                skip |= ValidateMultiViewShaders(pipeline, create_info_loc.dot(Field::pSubpasses, subpass).dot(Field::viewMask),
                                                 subpass_desc->viewMask, false);
            }
        }

        // Check for portability errors
        // Issue raised in https://gitlab.khronos.org/vulkan/vulkan/-/issues/3436
        // The combination of GPL/DynamicRendering and Portability has spec issues that need to be clarified
        if (IsExtEnabled(extensions.vk_khr_portability_subset) && !pipeline.IsGraphicsLibrary()) {
            skip |= ValidateGraphicsPipelinePortability(pipeline, create_info_loc);
        }
    }

    // Special case that doesn't fit into dynamic vs non-dynamic rendering because how it loops ALL subpass viewMasks
    if (enabled_features.multiviewPerViewViewports && rp_state && rp_state->has_multiview_enabled) {
        skip |= ValidateMultiviewPerViewViewports(pipeline, *rp_state, create_info_loc);
    }

    if (pipeline.OwnsLibState(pipeline.pre_raster_state) || pipeline.OwnsLibState(pipeline.fragment_shader_state)) {
        vvl::unordered_map<VkShaderStageFlags, uint32_t> unique_stage_map;
        uint32_t index = 0;
        for (const auto& stage_ci : pipeline.shader_stages_ci) {
            auto it = unique_stage_map.find(stage_ci.stage);
            if (it != unique_stage_map.end()) {
                skip |=
                    LogError("VUID-VkGraphicsPipelineCreateInfo-stage-06897", device, create_info_loc.dot(Field::pStages, index),
                             "and pStages[%" PRIu32 "] both have %s", it->second, string_VkShaderStageFlagBits(stage_ci.stage));
                return skip;  // If we are here, you could have 2 vertex shaders and interface code will blow up
            }
            unique_stage_map[stage_ci.stage] = index;
            index++;
        }
    }

    // While these are split into the 4 sub-states from GPL, they are validated for normal pipelines too.
    // These are VUs that fall strangely between both GPL and non-GPL pipelines
    skip |= ValidateGraphicsPipelineVertexInputState(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelinePreRasterizationState(pipeline, create_info_loc);

    skip |= ValidateGraphicsPipelineNullRenderPass(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelineLibrary(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelineInputAssemblyState(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelineTessellationState(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelineColorBlendState(pipeline, subpass_desc, create_info_loc);
    skip |= ValidateGraphicsPipelineRasterizationState(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelineNullState(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelineRasterizationOrderAttachmentAccess(pipeline, subpass_desc, create_info_loc);
    skip |= ValidateGraphicsPipelineDynamicState(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelineShaderState(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelineBlendEnable(pipeline, create_info_loc);
    skip |= ValidateGraphicsPipelineMeshTask(pipeline, create_info_loc);

    // pStages are ignored if not using one of these library states
    if (pipeline.OwnsLibState(pipeline.fragment_shader_state) || pipeline.OwnsLibState(pipeline.pre_raster_state)) {
        uint32_t stage_index = 0;
        for (const auto& stage_ci : pipeline.shader_stages_ci) {
            skip |= ValidatePipelineShaderStage(pipeline, stage_ci, pipeline_ci_pnext,
                                                create_info_loc.dot(Field::pStages, stage_index++));
        }
    }

    const void* pipeline_pnext = pipeline.GetCreateInfoPNext();
    if (const auto* discard_rectangle_state =
            vku::FindStructInPNextChain<VkPipelineDiscardRectangleStateCreateInfoEXT>(pipeline_pnext)) {
        skip |= ValidatePipelineDiscardRectangleStateCreateInfo(pipeline, *discard_rectangle_state, create_info_loc);
    }

    // VkAttachmentSampleCountInfoAMD == VkAttachmentSampleCountInfoNV
    if (const auto attachment_sample_count_info = vku::FindStructInPNextChain<VkAttachmentSampleCountInfoAMD>(pipeline_pnext)) {
        skip |= ValidatePipelineAttachmentSampleCountInfo(pipeline, *attachment_sample_count_info, create_info_loc);
    }

    if (const auto* pipeline_robustness_info = vku::FindStructInPNextChain<VkPipelineRobustnessCreateInfo>(pipeline_pnext)) {
        skip |= ValidatePipelineRobustnessCreateInfo(pipeline, *pipeline_robustness_info, create_info_loc);
    }

    if (const auto* fragment_shading_rate_state =
            vku::FindStructInPNextChain<VkPipelineFragmentShadingRateStateCreateInfoKHR>(pipeline_pnext)) {
        skip |= ValidateGraphicsPipelineFragmentShadingRateState(pipeline, *fragment_shading_rate_state, create_info_loc);
    }

    if (const auto* fragment_density_map_layered =
            vku::FindStructInPNextChain<VkPipelineFragmentDensityMapLayeredCreateInfoVALVE>(pipeline_pnext)) {
        if (fragment_density_map_layered->maxFragmentDensityMapLayers >
            phys_dev_ext_props.fragment_density_map_layered_props.maxFragmentDensityMapLayers) {
            skip |= LogError(
                "VUID-VkPipelineFragmentDensityMapLayeredCreateInfoVALVE-maxFragmentDensityMapLayers-10825", device,
                create_info_loc.pNext(Struct::VkPipelineFragmentDensityMapLayeredCreateInfoVALVE,
                                      Field::maxFragmentDensityMapLayers),
                "is %" PRIu32
                " but the VkPhysicalDeviceFragmentDensityMapLayeredPropertiesVALVE::maxFragmentDensityMapLayers is %" PRIu32 ".",
                fragment_density_map_layered->maxFragmentDensityMapLayers,
                phys_dev_ext_props.fragment_density_map_layered_props.maxFragmentDensityMapLayers);
        }
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelinePortability(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;
    if (!enabled_features.triangleFans && (pipeline.topology_at_rasterizer == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN)) {
        skip |= LogError("VUID-VkPipelineInputAssemblyStateCreateInfo-triangleFans-04452", device, create_info_loc,
                         "(portability error): VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN is not supported.");
    }

    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT) &&
        !pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE)) {
        // Validate vertex inputs
        for (const auto& state : pipeline.vertex_input_state->bindings) {
            const auto& desc = state.second.desc;
            const uint32_t min_alignment = phys_dev_ext_props.portability_props.minVertexInputBindingStrideAlignment;
            if (min_alignment != 0 && !IsIntegerMultipleOf(desc.stride, min_alignment)) {
                skip |= LogError(
                    "VUID-VkVertexInputBindingDescription-stride-04456", device, create_info_loc,
                    "(portability error): Vertex input stride (%" PRIu32
                    ") must be at least as large as and a "
                    "multiple of VkPhysicalDevicePortabilitySubsetPropertiesKHR::minVertexInputBindingStrideAlignment (%" PRIu32
                    ").",
                    desc.stride, min_alignment);
            }
        }

        // Validate vertex attributes
        if (!enabled_features.vertexAttributeAccessBeyondStride) {
            for (const auto& binding_state : pipeline.vertex_input_state->bindings) {
                for (const auto& attrib_state : binding_state.second.locations) {
                    const auto& attrib = attrib_state.second.desc;
                    const auto& desc = binding_state.second.desc;
                    if ((attrib.offset + GetVertexInputFormatSize(attrib.format)) > desc.stride) {
                        skip |= LogError("VUID-VkVertexInputAttributeDescription-vertexAttributeAccessBeyondStride-04457", device,
                                         create_info_loc,
                                         "(portability error): attribute.offset (%" PRIu32
                                         ") + "
                                         "sizeof(vertex_description.format) (%" PRIu32
                                         ") is larger than the vertex stride (%" PRIu32 ").",
                                         attrib.offset, GetVertexInputFormatSize(attrib.format), desc.stride);
                    }
                }
            }
        }
    }

    if (const auto raster_state_ci = pipeline.RasterizationState()) {
        // Validate polygon mode
        if (!enabled_features.pointPolygons && !raster_state_ci->rasterizerDiscardEnable &&
            (raster_state_ci->polygonMode == VK_POLYGON_MODE_POINT)) {
            skip |= LogError("VUID-VkPipelineRasterizationStateCreateInfo-pointPolygons-04458", device, create_info_loc,
                             "(portability error): point polygons are not supported.");
        }

        // Validate depth-stencil state
        if (!enabled_features.separateStencilMaskRef && (raster_state_ci->cullMode == VK_CULL_MODE_NONE)) {
            const auto ds_state = pipeline.DepthStencilState();
            if (ds_state && ds_state->stencilTestEnable && (ds_state->front.reference != ds_state->back.reference)) {
                skip |= LogError("VUID-VkPipelineDepthStencilStateCreateInfo-separateStencilMaskRef-04453", device, create_info_loc,
                                 "(portability error): VkStencilOpState::reference must be the "
                                 "same for front and back.");
            }
        }

        // Validate color attachments
        const uint32_t subpass = pipeline.Subpass();
        auto render_pass = Get<vvl::RenderPass>(pipeline.GraphicsCreateInfo().renderPass);
        ASSERT_AND_RETURN_SKIP(render_pass);
        const bool ignore_color_blend_state =
            raster_state_ci->rasterizerDiscardEnable ||
            (pipeline.rendering_create_info ? (pipeline.rendering_create_info->colorAttachmentCount == 0)
                                            : (render_pass->create_info.pSubpasses[subpass].colorAttachmentCount == 0));
        const auto* color_blend_state = pipeline.ColorBlendState();
        if (!enabled_features.constantAlphaColorBlendFactors && !ignore_color_blend_state && color_blend_state) {
            const auto attachments = color_blend_state->pAttachments;
            for (uint32_t color_attachment_index = 0; color_attachment_index < color_blend_state->attachmentCount;
                 ++color_attachment_index) {
                if ((attachments[color_attachment_index].srcColorBlendFactor == VK_BLEND_FACTOR_CONSTANT_ALPHA) ||
                    (attachments[color_attachment_index].srcColorBlendFactor == VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA)) {
                    skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-constantAlphaColorBlendFactors-04454", device,
                                     create_info_loc,
                                     "(portability error): srcColorBlendFactor for color attachment %" PRIu32
                                     " must "
                                     "not be VK_BLEND_FACTOR_CONSTANT_ALPHA or VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA",
                                     color_attachment_index);
                }
                if ((attachments[color_attachment_index].dstColorBlendFactor == VK_BLEND_FACTOR_CONSTANT_ALPHA) ||
                    (attachments[color_attachment_index].dstColorBlendFactor == VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA)) {
                    skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-constantAlphaColorBlendFactors-04455", device,
                                     create_info_loc,
                                     "(portability error): dstColorBlendFactor for color attachment %" PRIu32
                                     " must "
                                     "not be VK_BLEND_FACTOR_CONSTANT_ALPHA or VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA",
                                     color_attachment_index);
                }
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidatePipelineLibraryCreateInfo(const vvl::Pipeline& pipeline,
                                                   const VkPipelineLibraryCreateInfoKHR& library_create_info,
                                                   const Location& create_info_loc) const {
    bool skip = false;

    const VkPipelineCreateFlags2 pipeline_flags = pipeline.create_flags;
    const Location flags_loc = pipeline.GetCreateFlagsLoc(create_info_loc);
    const bool has_link_time_opt = (pipeline_flags & VK_PIPELINE_CREATE_2_LINK_TIME_OPTIMIZATION_BIT_EXT) != 0;
    const bool has_retain_link_time_opt = (pipeline_flags & VK_PIPELINE_CREATE_2_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT) != 0;
    const bool has_capture_internal = (pipeline_flags & VK_PIPELINE_CREATE_2_CAPTURE_INTERNAL_REPRESENTATIONS_BIT_KHR) != 0;
    const bool has_no_protect = (pipeline_flags & VK_PIPELINE_CREATE_NO_PROTECTED_ACCESS_BIT) != 0;
    const bool has_protected_access = (pipeline_flags & VK_PIPELINE_CREATE_PROTECTED_ACCESS_ONLY_BIT) != 0;
    bool lib_all_has_capture_internal = false;
    const bool null_render_pass = pipeline.GraphicsCreateInfo().renderPass == VK_NULL_HANDLE;

    const auto gpl_info = vku::FindStructInPNextChain<VkGraphicsPipelineLibraryCreateInfoEXT>(pipeline.GetCreateInfoPNext());
    // some checks need to know if we need compare the values coming in
    const bool incoming_gpl = gpl_info && gpl_info->flags != 0;

    for (uint32_t i = 0; i < library_create_info.libraryCount; ++i) {
        const auto lib = Get<vvl::Pipeline>(library_create_info.pLibraries[i]);
        if (!lib) {
            continue;
        }

        const Location& library_loc = create_info_loc.pNext(Struct::VkPipelineLibraryCreateInfoKHR, Field::pLibraries, i);
        const VkPipelineCreateFlags2 lib_pipeline_flags = lib->create_flags;

        if ((pipeline_flags & VK_PIPELINE_CREATE_2_OPACITY_MICROMAP_DISALLOW_MIXED_SPECIAL_INDEX_BIT_KHR) &&
            !(lib_pipeline_flags & VK_PIPELINE_CREATE_2_OPACITY_MICROMAP_DISALLOW_MIXED_SPECIAL_INDEX_BIT_KHR)) {
            const LogObjectList objlist(device, lib->Handle());
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pLibraries-11594", objlist, library_loc,
                             "(%s) was created with flags (%s), which is missing "
                             "VK_PIPELINE_CREATE_2_OPACITY_MICROMAP_DISALLOW_MIXED_SPECIAL_INDEX_BIT_KHR, but %s is %s.",
                             string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(),
                             string_VkPipelineCreateFlags2(lib_pipeline_flags).c_str(), flags_loc.Fields().c_str(),
                             string_VkPipelineCreateFlags2(pipeline_flags).c_str());
        }
        const bool lib_has_retain_link_time_opt =
            (lib_pipeline_flags & VK_PIPELINE_CREATE_2_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT) != 0;
        if (has_link_time_opt && !lib_has_retain_link_time_opt) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-06609", lib->Handle(), library_loc,
                             "(%s) was created with %s, which is missing "
                             "VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT.\n%s is %s.",
                             string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(),
                             string_VkPipelineCreateFlags2(lib_pipeline_flags).c_str(), flags_loc.Fields().c_str(),
                             string_VkPipelineCreateFlags2(pipeline_flags).c_str());
        }

        if (has_retain_link_time_opt && !lib_has_retain_link_time_opt) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-06610", lib->Handle(), library_loc,
                             "(%s) was created with %s, which is missing "
                             "VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT\n%s is %s.",
                             string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(),
                             string_VkPipelineCreateFlags2(lib_pipeline_flags).c_str(), flags_loc.Fields().c_str(),
                             string_VkPipelineCreateFlags2(pipeline_flags).c_str());
        }

        if (pipeline.descriptor_heap_mode != lib->descriptor_heap_mode) {
            if (pipeline.descriptor_heap_mode) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-11273", lib->Handle(), library_loc,
                                 "was created with %s, which is missing "
                                 "VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT included in %s (%s).",
                                 string_VkPipelineCreateFlags2(lib->create_flags).c_str(), flags_loc.Fields().c_str(),
                                 string_VkPipelineCreateFlags2(pipeline.create_flags).c_str());
            } else {
                skip |=
                    LogError("VUID-VkGraphicsPipelineCreateInfo-flags-11274", lib->Handle(), library_loc,
                             "was created with %s, but VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT is not included in %s (%s).",
                             string_VkPipelineCreateFlags2(lib->create_flags).c_str(), flags_loc.Fields().c_str(),
                             string_VkPipelineCreateFlags2(pipeline.create_flags).c_str());
            }
        }

        if (pipeline.descriptor_buffer_mode != lib->descriptor_buffer_mode) {
            if (pipeline.descriptor_buffer_mode) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-12355", lib->Handle(), library_loc,
                                 "was created with %s, which is missing "
                                 "VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT included in %s (%s).",
                                 string_VkPipelineCreateFlags2(lib->create_flags).c_str(), flags_loc.Fields().c_str(),
                                 string_VkPipelineCreateFlags2(pipeline.create_flags).c_str());
            } else {
                skip |=
                    LogError("VUID-VkGraphicsPipelineCreateInfo-flags-12356", lib->Handle(), library_loc,
                             "was created with %s, but VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT is not included in %s (%s).",
                             string_VkPipelineCreateFlags2(lib->create_flags).c_str(), flags_loc.Fields().c_str(),
                             string_VkPipelineCreateFlags2(pipeline.create_flags).c_str());
            }
        }

        const bool lib_has_no_protect = (lib_pipeline_flags & VK_PIPELINE_CREATE_NO_PROTECTED_ACCESS_BIT) != 0;
        if (has_no_protect != lib_has_no_protect) {
            if (has_no_protect) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-12357", lib->Handle(), library_loc,
                                 "was created with %s, which is missing "
                                 "VK_PIPELINE_CREATE_NO_PROTECTED_ACCESS_BIT included in %s (%s).",
                                 string_VkPipelineCreateFlags2(lib->create_flags).c_str(), flags_loc.Fields().c_str(),
                                 string_VkPipelineCreateFlags2(pipeline.create_flags).c_str());
            } else {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-12358", lib->Handle(), library_loc,
                                 "was created with %s, but VK_PIPELINE_CREATE_NO_PROTECTED_ACCESS_BIT is not included in %s (%s).",
                                 string_VkPipelineCreateFlags2(lib->create_flags).c_str(), flags_loc.Fields().c_str(),
                                 string_VkPipelineCreateFlags2(pipeline.create_flags).c_str());
            }
        }

        const bool lib_has_protected_access = (lib_pipeline_flags & VK_PIPELINE_CREATE_PROTECTED_ACCESS_ONLY_BIT) != 0;
        if (has_protected_access != lib_has_protected_access) {
            if (has_protected_access) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-12359", lib->Handle(), library_loc,
                                 "was created with %s, which is missing "
                                 "VK_PIPELINE_CREATE_2_PROTECTED_ACCESS_ONLY_BIT included in %s (%s).",
                                 string_VkPipelineCreateFlags2(lib->create_flags).c_str(), flags_loc.Fields().c_str(),
                                 string_VkPipelineCreateFlags2(pipeline.create_flags).c_str());
            } else {
                skip |=
                    LogError("VUID-VkGraphicsPipelineCreateInfo-flags-12360", lib->Handle(), library_loc,
                             "was created with %s, but VK_PIPELINE_CREATE_2_PROTECTED_ACCESS_ONLY_BIT is not included in %s (%s).",
                             string_VkPipelineCreateFlags2(lib->create_flags).c_str(), flags_loc.Fields().c_str(),
                             string_VkPipelineCreateFlags2(pipeline.create_flags).c_str());
            }
        }

        const bool lib_has_capture_internal =
            (lib_pipeline_flags & VK_PIPELINE_CREATE_2_CAPTURE_INTERNAL_REPRESENTATIONS_BIT_KHR) != 0;
        if (lib_has_capture_internal) {
            lib_all_has_capture_internal = true;
            if (!has_capture_internal && incoming_gpl) {
                const Location& gpl_flags_loc = create_info_loc.pNext(Struct::VkGraphicsPipelineLibraryCreateInfoEXT, Field::flags);
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pLibraries-06647", lib->Handle(), library_loc,
                                 "(%s) was created with %s\n"
                                 "%s is %s\n"
                                 "%s is %s.",
                                 string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(),
                                 string_VkPipelineCreateFlags2(lib_pipeline_flags).c_str(), gpl_flags_loc.Fields().c_str(),
                                 string_VkPipelineCreateFlags2(gpl_info->flags).c_str(), flags_loc.Fields().c_str(),
                                 string_VkPipelineCreateFlags2(pipeline_flags).c_str());
            }
        } else {
            if (lib_all_has_capture_internal) {
                skip |=
                    LogError("VUID-VkGraphicsPipelineCreateInfo-pLibraries-06646", lib->Handle(), library_loc,
                             "(%s) was created with %s.", string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(),
                             string_VkPipelineCreateFlags2(lib_pipeline_flags).c_str());
            } else if (has_capture_internal && incoming_gpl) {
                const Location& gpl_flags_loc = create_info_loc.pNext(Struct::VkGraphicsPipelineLibraryCreateInfoEXT, Field::flags);
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-06645", lib->Handle(), library_loc,
                                 "(%s) was created with %s\n"
                                 "%s is %s\n"
                                 "%s is %s.",
                                 string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(),
                                 string_VkPipelineCreateFlags2(lib_pipeline_flags).c_str(), gpl_flags_loc.Fields().c_str(),
                                 string_VkPipelineCreateFlags2(gpl_info->flags).c_str(), flags_loc.Fields().c_str(),
                                 string_VkPipelineCreateFlags2(pipeline_flags).c_str());
            }
        }

        if ((lib->uses_shader_module_id) && !(pipeline_flags & VK_PIPELINE_CREATE_2_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT)) {
            skip |= LogError("VUID-VkPipelineLibraryCreateInfoKHR-pLibraries-06855", lib->Handle(), library_loc,
                             "(%s) was created with %s but VkPipelineShaderStageModuleIdentifierCreateInfoEXT::identifierSize was "
                             "not equal to 0 for the pipeline",
                             string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(),
                             string_VkPipelineCreateFlags2(lib_pipeline_flags).c_str());
        }

        // Check if RenderPass are both not null when linking in a GPL stage that uses them
        if (incoming_gpl && null_render_pass && lib->GraphicsCreateInfo().renderPass != VK_NULL_HANDLE) {
            // If linking with a Vertex Input, the renderpass is ignored and skip check
            const VkGraphicsPipelineLibraryFlagsEXT valid_rp_lib = VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT |
                                                                   VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT |
                                                                   VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT;
            const bool gpl_needs_rp = (gpl_info->flags & valid_rp_lib) != 0;
            const bool lib_needs_rp = (lib->graphics_lib_type & valid_rp_lib) != 0;
            if (gpl_needs_rp && lib_needs_rp) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderpass-06625", lib->Handle(),
                                 create_info_loc.dot(Field::renderPass),
                                 "is VK_NULL_HANDLE and includes "
                                 "VkGraphicsPipelineLibraryCreateInfoEXT::flags (%s), but pLibraries[%" PRIu32
                                 "] includes VkGraphicsPipelineLibraryCreateInfoEXT::flags (%s) and "
                                 "was created with render pass not VK_NULL_HANDLE (%s).",
                                 string_VkGraphicsPipelineLibraryFlagsEXT(gpl_info->flags).c_str(), i,
                                 string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(),
                                 FormatHandle(lib->GraphicsCreateInfo().renderPass).c_str());
            }
        }

        // One check to see if incoming PreRaster/Fragment doesn't match what is linking
        // One check to make sure 2 libs (one with PreRaster and Fragment) are both being linked with different masks
        const VkGraphicsPipelineLibraryFlagsEXT valid_view_mask =
            VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT | VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT;
        const bool lib_needs_view_mask = (lib->graphics_lib_type & valid_view_mask) != 0;
        if (lib_needs_view_mask) {
            if (incoming_gpl) {
                const bool gpl_needs_view_mask = (gpl_info->flags & valid_view_mask) != 0;
                const uint32_t gpl_view_mask = pipeline.rendering_create_info ? pipeline.rendering_create_info->viewMask : 0;
                const uint32_t lib_view_mask = lib->rendering_create_info ? lib->rendering_create_info->viewMask : 0;
                if (gpl_needs_view_mask && gpl_view_mask != lib_view_mask) {
                    skip |=
                        LogError("VUID-VkGraphicsPipelineCreateInfo-flags-06626", lib->Handle(),
                                 create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::viewMask),
                                 "(0x%" PRIx32 ") and VkGraphicsPipelineLibraryCreateInfoEXT::flags (%s), but pLibraries[%" PRIu32
                                 "] (%s) was created with viewMask (0x%" PRIx32 ")",
                                 gpl_view_mask, string_VkGraphicsPipelineLibraryFlagsEXT(gpl_info->flags).c_str(), i,
                                 string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(), lib_view_mask);
                }
            }
            // only need to check the remaining libs (prevents double error going each way)
            for (uint32_t k = i + 1; k < library_create_info.libraryCount; k++) {
                if (const auto other_lib = Get<vvl::Pipeline>(library_create_info.pLibraries[k])) {
                    if ((other_lib->graphics_lib_type & valid_view_mask) == 0) {
                        continue;
                    }

                    const uint32_t lib_view_mask = lib->rendering_create_info ? lib->rendering_create_info->viewMask : 0;
                    const uint32_t other_lib_view_mask =
                        other_lib->rendering_create_info ? other_lib->rendering_create_info->viewMask : 0;
                    if (lib_view_mask != other_lib_view_mask) {
                        LogObjectList objlist(lib->Handle(), other_lib->Handle());
                        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pLibraries-06627", objlist,
                                         create_info_loc.pNext(Struct::VkPipelineLibraryCreateInfoKHR, Field::pLibraries, i),
                                         "(%s - %s) was created with viewMask (0x%" PRIx32 "), but pLibraries[%" PRIu32
                                         "] (%s - %s) was created with viewMask (0x%" PRIx32 ")",
                                         FormatHandle(lib->Handle()).c_str(),
                                         string_VkGraphicsPipelineLibraryFlagsEXT(lib->graphics_lib_type).c_str(), lib_view_mask, k,
                                         FormatHandle(other_lib->Handle()).c_str(),
                                         string_VkGraphicsPipelineLibraryFlagsEXT(other_lib->graphics_lib_type).c_str(),
                                         other_lib_view_mask);
                    }
                }
            }
        }
    }

    return skip;
}

// vkspec.html#pipelines-graphics-subsets-vertex-input
bool CoreChecks::ValidateGraphicsPipelineVertexInputState(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;
    // If using a mesh shader, all vertex input is ignored
    if (!pipeline.OwnsLibState(pipeline.vertex_input_state) || (pipeline.active_shaders & VK_SHADER_STAGE_MESH_BIT_EXT)) {
        return skip;
    }

    const bool ignore_vertex_input_state = pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT);
    const bool ignore_input_assembly_state = IsExtEnabled(extensions.vk_ext_extended_dynamic_state3) &&
                                             pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE) &&
                                             pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY) &&
                                             phys_dev_ext_props.extended_dynamic_state3_props.dynamicPrimitiveTopologyUnrestricted;

    if (!ignore_vertex_input_state) {
        skip |= ValidatePipelineVertexDivisors(pipeline, create_info_loc);
    }

    const auto* input_state = pipeline.InputState();
    const auto* assembly_state = pipeline.InputAssemblyState();
    const bool invalid_input_state = !ignore_vertex_input_state && !input_state;
    const bool invalid_assembly_state = !ignore_input_assembly_state && !assembly_state;

    if (invalid_input_state && invalid_assembly_state && pipeline.IsGraphicsLibrary()) {
        // Failed to defined a Vertex Input State
        if (!pipeline.pre_raster_state) {
            skip |= LogError(
                "VUID-VkGraphicsPipelineCreateInfo-flags-08898", device, create_info_loc,
                "pVertexInputState and pInputAssemblyState are both NULL which means this is an invalid Vertex Input "
                "State.%s"
                "\npInputAssemblyState can be NULL if all the following set:"
                "\n  Enable VK_EXT_extended_dynamic_state3 (%senabled)"
                "\n  Use VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE (%s)"
                "\n  Use VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY (%s)"
                "\n  dynamicPrimitiveTopologyUnrestricted is VK_TRUE (%s)"
                "\nIf there is a mesh stage, these can also be NULL, but the Vertex Input library can also just be skipped if "
                "creating a mesh pipeline.",
                pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT)
                    ? ""
                    : "\npVertexInputState can be NULL if using VK_DYNAMIC_STATE_VERTEX_INPUT_EXT",
                IsExtEnabled(extensions.vk_ext_extended_dynamic_state3) ? "" : "not ",
                pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE) ? "set" : "not set",
                pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY) ? "set" : "not set",
                phys_dev_ext_props.extended_dynamic_state3_props.dynamicPrimitiveTopologyUnrestricted ? "VK_TRUE" : "VK_FALSE");
        } else if ((pipeline.active_shaders & VK_SHADER_STAGE_VERTEX_BIT)) {
            skip |= LogError(
                "VUID-VkGraphicsPipelineCreateInfo-flags-08897", device, create_info_loc,
                "pVertexInputState and pInputAssemblyState are both NULL which means this is an invalid Vertex Input "
                "State.%s"
                "\npInputAssemblyState can be NULL if all the following set:"
                "\n  Enable VK_EXT_extended_dynamic_state3 (%senabled)"
                "\n  Use VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE (%s)"
                "\n  Use VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY (%s)"
                "\n  dynamicPrimitiveTopologyUnrestricted is VK_TRUE (%s)",
                pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT)
                    ? ""
                    : "\npVertexInputState can be NULL if using VK_DYNAMIC_STATE_VERTEX_INPUT_EXT",
                IsExtEnabled(extensions.vk_ext_extended_dynamic_state3) ? "" : "not ",
                pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE) ? "set" : "not set",
                pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY) ? "set" : "not set",
                phys_dev_ext_props.extended_dynamic_state3_props.dynamicPrimitiveTopologyUnrestricted ? "VK_TRUE" : "VK_FALSE");
        }
    } else if (invalid_input_state) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-02097", device, create_info_loc.dot(Field::pVertexInputState),
                         "is NULL.");
    } else if (invalid_assembly_state) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-dynamicPrimitiveTopologyUnrestricted-09031", device,
                         create_info_loc.dot(Field::pInputAssemblyState), "is NULL.");
    }
    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineNullRenderPass(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;
    // If the vertex input is by itself renderpass is ignored
    if (!pipeline.IsRenderPassStateRequired()) return skip;

    // If the VkRenderPass is null, it can be from a legit Dynamic Rendering pass
    if (pipeline.IsRenderPassNull()) {
        if (!enabled_features.dynamicRendering) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-dynamicRendering-06576", device,
                             create_info_loc.dot(Field::renderPass), "is NULL, but the dynamicRendering feature was not enabled");
        }
    } else if (!pipeline.RenderPassState()) {
        // If the vvl::RenderPass object is not found AND the handle is not null, this we know this is an invalid render pass
        const auto gpl_info = vku::FindStructInPNextChain<VkGraphicsPipelineLibraryCreateInfoEXT>(pipeline.GetCreateInfoPNext());
        const bool has_flags =
            gpl_info && (gpl_info->flags & (VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT |
                                            VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT |
                                            VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT)) != 0;
        const char* vuid =
            has_flags ? "VUID-VkGraphicsPipelineCreateInfo-flags-06643" : "VUID-VkGraphicsPipelineCreateInfo-renderPass-06603";
        skip |= LogError(vuid, device, create_info_loc.dot(Field::renderPass), "is not a valid render pass.");
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineLibrary(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;

    const VkPipelineCreateFlags2 pipeline_flags = pipeline.create_flags;
    const Location flags_loc = pipeline.GetCreateFlagsLoc(create_info_loc);
    const bool is_create_library = (pipeline_flags & VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR) != 0;

    // It is possible to have no FS state in a complete pipeline whether or not GPL is used
    if (pipeline.OwnsLibState(pipeline.pre_raster_state) && !pipeline.OwnsLibState(pipeline.fragment_shader_state) &&
        ((pipeline.create_info_shaders & FragmentShaderState::ValidShaderStages()) != 0)) {
        // Hint to help as shown from https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8761
        const char* hint = is_create_library ? "" : " (Is rasterizerDiscardEnable mistakenly set to VK_TRUE?)";
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-06894", device, create_info_loc,
                         "does not have fragment shader state, but stages (%s) contains VK_SHADER_STAGE_FRAGMENT_BIT.%s",
                         string_VkShaderStageFlags(pipeline.create_info_shaders).c_str(), hint);
    }

    if (!pipeline.OwnsLibState(pipeline.fragment_shader_state) && !pipeline.OwnsLibState(pipeline.pre_raster_state) &&
        pipeline.shader_stages_ci.size() > 0) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-stageCount-09587", device, create_info_loc.dot(Field::stageCount),
                         "is %zu, but the pipeline does not have a pre-rasterization or fragment shader state.",
                         pipeline.shader_stages_ci.size());
    }

    if (is_create_library) {
        if (!enabled_features.graphicsPipelineLibrary) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-graphicsPipelineLibrary-06606", device, flags_loc,
                             "(%s) includes VK_PIPELINE_CREATE_LIBRARY_BIT_KHR, but "
                             "graphicsPipelineLibrary feature is not enabled.",
                             string_VkPipelineCreateFlags2(pipeline_flags).c_str());
        }
    } else {
        // check to make sure all required library states are here
        // Note: You don't require a vertex input state (ex. if using Mesh Shaders)
        if (!pipeline.pre_raster_state) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-08901", device, create_info_loc,
                             "Attempting to link pipeline libraries without a pre-rasterization shader state (did you forget to "
                             "add VK_PIPELINE_CREATE_LIBRARY_BIT_KHR to your intermediate pipeline?).");
        } else if (pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE) || !pipeline.RasterizationDisabled()) {
            if (!pipeline.fragment_shader_state) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-08909", device, create_info_loc,
                                 "Attempting to link pipeline libraries without a fragment shader state (did you forget to add "
                                 "VK_PIPELINE_CREATE_LIBRARY_BIT_KHR to your intermediate pipeline?).");
            } else if (!pipeline.fragment_output_state) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-08909", device, create_info_loc,
                                 "Attempting to link pipeline libraries without a fragment output state (did you forget to add "
                                 "VK_PIPELINE_CREATE_LIBRARY_BIT_KHR to your intermediate pipeline?).");
            }
        }
    }

    if (pipeline.OwnsLibState(pipeline.fragment_shader_state) && !pipeline.OwnsLibState(pipeline.pre_raster_state) &&
        ((pipeline.create_info_shaders & PreRasterState::ValidShaderStages()) != 0)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-06895", device, create_info_loc,
                         "does not have pre-raster state, but stages (%s) contains pre-raster shader stages.",
                         string_VkShaderStageFlags(pipeline.create_info_shaders).c_str());
    }

    // note this is the incoming layout an not ones from the pipeline library
    const auto& pipeline_ci = pipeline.GraphicsCreateInfo();
    const auto pipeline_layout_state = Get<vvl::PipelineLayout>(pipeline_ci.layout);

    if (pipeline.HasFullState()) {
        if (is_create_library) {
            skip |=
                LogError("VUID-VkGraphicsPipelineCreateInfo-flags-06608", device, flags_loc,
                         "(%s) includes VK_PIPELINE_CREATE_LIBRARY_BIT_KHR, but defines a complete set of state.\nHint: Did you "
                         "forget to add VkGraphicsPipelineLibraryCreateInfoEXT with the library stages to create?\nHint: Did you "
                         "forget to remove VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR?",
                         string_VkPipelineCreateFlags2(pipeline_flags).c_str());
        }

        // A valid pipeline layout must _always_ be provided, even if the pipeline is defined completely from libraries.
        // This a change from the original GPL spec. See https://gitlab.khronos.org/vulkan/vulkan/-/issues/3334 for some
        // context
        // If libraries are included then pipeline layout can be NULL. See
        // https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/6144
        if (!pipeline_layout_state && (!pipeline.library_create_info || pipeline.library_create_info->libraryCount == 0)) {
            if (!pipeline.descriptor_heap_mode) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-None-07826", device, create_info_loc.dot(Field::layout),
                                 "is not a valid VkPipelineLayout, but defines a complete set of state.");
            }
        }

        // graphics_lib_type effectively tracks which parts of the pipeline are defined by graphics libraries.
        // If the complete state is defined by libraries, we need to check for compatibility with each library's layout
        const bool from_libraries_only = pipeline.graphics_lib_type == AllVkGraphicsPipelineLibraryFlagBitsEXT;
        if (from_libraries_only) {
            const VkPipelineLayout linking_layout_handle =
                pipeline_layout_state ? pipeline_layout_state->VkHandle() : VK_NULL_HANDLE;
            const VkPipelineLayout pre_raster_layout_handle =
                pipeline.PreRasterPipelineLayoutState() ? pipeline.PreRasterPipelineLayoutState()->VkHandle() : VK_NULL_HANDLE;
            const VkPipelineLayout fs_layout_handle = pipeline.FragmentShaderPipelineLayoutState()
                                                          ? pipeline.FragmentShaderPipelineLayoutState()->VkHandle()
                                                          : VK_NULL_HANDLE;

            const bool pre_raster_independent_set = pipeline.pre_raster_state && pipeline.pre_raster_state->IsIndependentSets();
            // NOTE: it is possible for an executable pipeline to not contain FS state
            const bool fs_independent_set = pipeline.fragment_shader_state && pipeline.fragment_shader_state->IsIndependentSets();
            if (!pre_raster_independent_set && !fs_independent_set) {
                // The layout defined at link time must be compatible with each (pre-raster and fragment shader) library state's
                // layout (vertex input and fragment output state do not contain a layout)
                if (pipeline_layout_state) {
                    if (std::string err_msg; !VerifyPipelineLayoutCompatibility(
                            *pipeline_layout_state, *pipeline.PreRasterPipelineLayoutState(), err_msg)) {
                        LogObjectList objlist(linking_layout_handle, pre_raster_layout_handle);
                        skip |=
                            LogError("VUID-VkGraphicsPipelineCreateInfo-layout-07827", objlist, create_info_loc.dot(Field::layout),
                                     "(%s) is incompatible with the %s specified in the pre-rasterization library\n%s",
                                     FormatHandle(linking_layout_handle).c_str(), FormatHandle(pre_raster_layout_handle).c_str(),
                                     err_msg.c_str());
                    }
                    if (std::string err_msg; !VerifyPipelineLayoutCompatibility(
                            *pipeline_layout_state, *pipeline.FragmentShaderPipelineLayoutState(), err_msg)) {
                        LogObjectList objlist(linking_layout_handle, fs_layout_handle);
                        skip |= LogError(
                            "VUID-VkGraphicsPipelineCreateInfo-layout-07827", objlist, create_info_loc.dot(Field::layout),
                            "(%s) is incompatible with the %s specified in the fragment shader library: %s",
                            FormatHandle(linking_layout_handle).c_str(), FormatHandle(fs_layout_handle).c_str(), err_msg.c_str());
                    }
                } else if (!pipeline.descriptor_heap_mode) {
                    skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-layout-07827", device, create_info_loc.dot(Field::layout),
                                     "is null/invalid and therefore not compatible with the libraries layout");
                }
            }

            if (pre_raster_independent_set && fs_independent_set) {
                const bool has_link_time_opt = (pipeline.create_flags & VK_PIPELINE_CREATE_2_LINK_TIME_OPTIMIZATION_BIT_EXT) != 0;
                const char* vuid = has_link_time_opt ? "VUID-VkGraphicsPipelineCreateInfo-flags-06729"
                                                     : "VUID-VkGraphicsPipelineCreateInfo-flags-06730";

                if (pipeline_layout_state) {
                    std::string err_msg;
                    if (!VerifyPipelineLayoutCompatibilityUnion(*pipeline_layout_state, *pipeline.PreRasterPipelineLayoutState(),
                                                                *pipeline.FragmentShaderPipelineLayoutState(), err_msg)) {
                        LogObjectList objlist(linking_layout_handle, pre_raster_layout_handle, fs_layout_handle);
                        skip |=
                            LogError(vuid, objlist, create_info_loc.dot(Field::layout),
                                     "(%s) is incompatible with the layout specified in the union of (pre-rasterization, fragment "
                                     "shader) libraries.\n%s",
                                     FormatHandle(linking_layout_handle).c_str(), err_msg.c_str());
                    }

                    // Special case where we also need to check for linking independent set
                    if (!has_link_time_opt && !pipeline_layout_state->IsIndependentSets()) {
                        LogObjectList objlist(linking_layout_handle, pre_raster_layout_handle, fs_layout_handle);
                        skip |= LogError(
                            vuid, objlist, create_info_loc.dot(Field::layout),
                            "(%s) was not created with VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT, but the "
                            "pre-rasterization (%s) "
                            "and fragment shader (%s) were created with VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT.\nThis "
                            "is only allowed if you use VK_PIPELINE_CREATE_2_LINK_TIME_OPTIMIZATION_BIT_EXT as well.",
                            FormatHandle(linking_layout_handle).c_str(), FormatHandle(pre_raster_layout_handle).c_str(),
                            FormatHandle(fs_layout_handle).c_str());
                    }

                } else {
                    skip |= LogError(vuid, device, create_info_loc.dot(Field::layout),
                                     "is null/invalid and therefore not compatible with the union of libraries layout");
                }
            }
        }
    }

    // The pipeline libraries can be declared in two different ways
    enum GPLInitType : uint8_t {
        uninitialized = 0,
        gpl_flags,       // VkGraphicsPipelineLibraryCreateInfoEXT::flags
        link_libraries,  // VkPipelineLibraryCreateInfoKHR::pLibraries
    };

    struct GPLValidInfo {
        GPLInitType init = GPLInitType::uninitialized;
        VkPipelineLayoutCreateFlags flags = VK_PIPELINE_LAYOUT_CREATE_FLAG_BITS_MAX_ENUM;
        const vvl::PipelineLayout* layout = nullptr;
        // Can't use MultisampleState() to get this value as we are checking here if they are identical
        const VkPipelineMultisampleStateCreateInfo* ms_state = nullptr;
        const VkPipelineFragmentShadingRateStateCreateInfoKHR* shading_rate_state = nullptr;
    };

    GPLValidInfo pre_raster_info;
    GPLValidInfo frag_shader_info;
    GPLValidInfo frag_output_info;

    const auto gpl_info = vku::FindStructInPNextChain<VkGraphicsPipelineLibraryCreateInfoEXT>(pipeline_ci.pNext);
    if (gpl_info) {
        if (gpl_info->flags & VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT) {
            pre_raster_info.init = GPLInitType::gpl_flags;
            pre_raster_info.flags =
                (pipeline.PreRasterPipelineLayoutState()) ? pipeline.PreRasterPipelineLayoutState()->CreateFlags() : 0;
            pre_raster_info.layout = pipeline.PreRasterPipelineLayoutState().get();
            pre_raster_info.shading_rate_state =
                vku::FindStructInPNextChain<VkPipelineFragmentShadingRateStateCreateInfoKHR>(pipeline_ci.pNext);
        }
        if (gpl_info->flags & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT) {
            frag_shader_info.init = GPLInitType::gpl_flags;
            frag_shader_info.flags =
                (pipeline.FragmentShaderPipelineLayoutState()) ? pipeline.FragmentShaderPipelineLayoutState()->CreateFlags() : 0;
            frag_shader_info.layout = pipeline.FragmentShaderPipelineLayoutState().get();
            if (pipeline.fragment_shader_state->ms_state) {
                frag_shader_info.ms_state = pipeline.fragment_shader_state->ms_state.get()->ptr();
            }
            frag_shader_info.shading_rate_state =
                vku::FindStructInPNextChain<VkPipelineFragmentShadingRateStateCreateInfoKHR>(pipeline_ci.pNext);
        }
        if (gpl_info->flags & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT) {
            frag_output_info.init = GPLInitType::gpl_flags;
            if (pipeline.fragment_output_state->ms_state) {
                frag_output_info.ms_state = pipeline.fragment_output_state->ms_state.get()->ptr();
            }
        }
    }

    if (pipeline.library_create_info) {
        skip |= ValidatePipelineLibraryCreateInfo(pipeline, *pipeline.library_create_info, create_info_loc);

        // extra loop here to set values for the unified checks below
        for (uint32_t i = 0; i < pipeline.library_create_info->libraryCount; ++i) {
            const auto lib = Get<vvl::Pipeline>(pipeline.library_create_info->pLibraries[i]);
            if (!lib) {
                continue;
            }

            const auto& lib_ci = lib->GraphicsCreateInfo();
            if (lib->graphics_lib_type & VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT) {
                pre_raster_info.init = GPLInitType::link_libraries;
                const auto layout_state = lib->PreRasterPipelineLayoutState();
                if (layout_state) {
                    pre_raster_info.flags = layout_state->CreateFlags();
                    pre_raster_info.layout = layout_state.get();
                }
                pre_raster_info.shading_rate_state =
                    vku::FindStructInPNextChain<VkPipelineFragmentShadingRateStateCreateInfoKHR>(lib_ci.pNext);
            }
            if (lib->graphics_lib_type & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT) {
                frag_shader_info.init = GPLInitType::link_libraries;
                const auto layout_state = lib->FragmentShaderPipelineLayoutState();
                if (layout_state) {
                    frag_shader_info.flags = layout_state->CreateFlags();
                    frag_shader_info.layout = layout_state.get();
                }
                if (lib->fragment_shader_state->ms_state) {
                    frag_shader_info.ms_state = lib->fragment_shader_state->ms_state.get()->ptr();
                }
                frag_shader_info.shading_rate_state =
                    vku::FindStructInPNextChain<VkPipelineFragmentShadingRateStateCreateInfoKHR>(lib_ci.pNext);
            }
            if (lib->graphics_lib_type & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT) {
                frag_output_info.init = GPLInitType::link_libraries;
                if (lib->fragment_output_state->ms_state) {
                    frag_output_info.ms_state = lib->fragment_output_state->ms_state.get()->ptr();
                }
            }
        }
    }

    if ((pipeline.OwnsLibState(pipeline.pre_raster_state) || pipeline.OwnsLibState(pipeline.fragment_shader_state)) &&
        !pipeline_layout_state && !pipeline.descriptor_heap_mode) {
        const char* vuid = (pre_raster_info.init == GPLInitType::gpl_flags || frag_shader_info.init == GPLInitType::gpl_flags)
                               ? "VUID-VkGraphicsPipelineCreateInfo-flags-06642"
                               : "VUID-VkGraphicsPipelineCreateInfo-layout-06602";
        skip |= LogError(vuid, device, create_info_loc.dot(Field::layout), "is not a valid VkPipelineLayout.");
    }

    if (pipeline_layout_state && pre_raster_info.init == GPLInitType::gpl_flags) {
        for (uint32_t i = 0; i < pipeline_layout_state->set_layouts.list.size(); i++) {
            if (pipeline_layout_state->set_layouts.list[i] != nullptr) {
                continue;
            }

            if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE) && pipeline.RasterizationDisabled()) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-06683", device,
                                 create_info_loc.pNext(Struct::VkGraphicsPipelineLibraryCreateInfoEXT, Field::flags),
                                 "is %s, but layout was created with pSetLayouts[%" PRIu32 "] == VK_NULL_HANDLE",
                                 string_VkGraphicsPipelineLibraryFlagsEXT(gpl_info->flags).c_str(), i);
            } else if (frag_shader_info.init == GPLInitType::gpl_flags) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-06682", device,
                                 create_info_loc.pNext(Struct::VkGraphicsPipelineLibraryCreateInfoEXT, Field::flags),
                                 "is %s, but layout was created with pSetLayouts[%" PRIu32 "] == VK_NULL_HANDLE",
                                 string_VkGraphicsPipelineLibraryFlagsEXT(gpl_info->flags).c_str(), i);
            }
        }
    }

    // pre-raster and fragemnt shader state is defined by some combination of this library and pLibraries
    if (pre_raster_info.init != GPLInitType::uninitialized && frag_shader_info.init != GPLInitType::uninitialized) {
        if (!pre_raster_info.layout || !frag_shader_info.layout) {
            // This will occur if 06642 is violated, if here, just exit as things are invalid already
            // Put here, and not at 06642 to allow the other checks that don't care about pipeline layouts
            return skip;
        }

        // For VUs saying "includes only one... pLibraries includes the other flag" this would be when |only_libs == false|
        // This is because it is not valid to have both init be from |gpl_flags|
        const bool only_libs =
            (pre_raster_info.init == GPLInitType::link_libraries) && (frag_shader_info.init == GPLInitType::link_libraries);

        // Check for consistent independent sets across libraries
        const auto pre_raster_independant_set = (pre_raster_info.flags & VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT);
        const auto fs_independant_set = (frag_shader_info.flags & VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT);
        const bool not_independent_sets = !pre_raster_independant_set && !fs_independant_set;
        if (pre_raster_independant_set ^ fs_independant_set) {
            const char* vuid =
                only_libs ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06615" : "VUID-VkGraphicsPipelineCreateInfo-flags-06614";
            LogObjectList objlist(pre_raster_info.layout->Handle(), frag_shader_info.layout->Handle());
            skip |= LogError(
                vuid, objlist, create_info_loc,
                "is attempting to create a graphics pipeline libraries with pre-raster and fragment shader state. However "
                "the pre-raster layout create flags (%s) are %s defined with VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT, "
                "and the fragment shader layout create flags (%s) are %s defined with "
                "VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT",
                string_VkPipelineLayoutCreateFlags(pre_raster_info.flags).c_str(), (pre_raster_independant_set != 0) ? "" : "not",
                string_VkPipelineLayoutCreateFlags(frag_shader_info.flags).c_str(), (fs_independant_set != 0) ? "" : "not");
        } else if (not_independent_sets) {
            // "layout used by this pipeline and the library must be identically defined"
            // Inside VkPipelineLayoutCreateInfo, |pSetLayouts| and |pPushConstantRanges| are checked below, this leaves this VU to
            // cover everything else. Currently no valid pNext structs are extending pipeline layout creation
            if (pre_raster_info.layout->create_flags != frag_shader_info.layout->create_flags) {
                const char* vuid = only_libs ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06613"
                                             : "VUID-VkGraphicsPipelineCreateInfo-flags-06612";
                LogObjectList objlist(pre_raster_info.layout->Handle(), frag_shader_info.layout->Handle());
                skip |=
                    LogError(vuid, objlist, create_info_loc,
                             "VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT was not set and the graphics pipeline libraries "
                             "have differently defined VkPipelineLayout:\n|Pre-Raster State| %s was created with %s\n|Fragment "
                             "Shader State| %s was created with %s\n",
                             FormatHandle(pre_raster_info.layout->Handle()).c_str(),
                             string_VkPipelineLayoutCreateFlags(pre_raster_info.layout->create_flags).c_str(),
                             FormatHandle(frag_shader_info.layout->Handle()).c_str(),
                             string_VkPipelineLayoutCreateFlags(frag_shader_info.layout->create_flags).c_str());
            }
        }

        // Push Constants must match regardless of VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT
        if (pre_raster_info.layout && !pre_raster_info.layout->push_constant_ranges_layout->empty() && frag_shader_info.layout &&
            !frag_shader_info.layout->push_constant_ranges_layout->empty()) {
            const uint32_t pre_raster_count = static_cast<uint32_t>(pre_raster_info.layout->push_constant_ranges_layout->size());
            const uint32_t frag_shader_count = static_cast<uint32_t>(frag_shader_info.layout->push_constant_ranges_layout->size());

            if (pre_raster_count != frag_shader_count) {
                const char* vuid = only_libs ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06621"
                                             : "VUID-VkGraphicsPipelineCreateInfo-flags-06620";
                LogObjectList objlist(pre_raster_info.layout->Handle(), frag_shader_info.layout->Handle());
                skip |= LogError(vuid, objlist, create_info_loc,
                                 "the graphics pipeline libraries have different push constants\n|Pre-Raster State| %s has "
                                 "pushConstantRangeCount of %" PRIu32
                                 "\n|Fragment Shader State| %s has pushConstantRangeCount of %" PRIu32 ".",
                                 FormatHandle(pre_raster_info.layout->Handle()).c_str(), pre_raster_count,
                                 FormatHandle(frag_shader_info.layout->Handle()).c_str(), frag_shader_count);
            } else {
                for (uint32_t i = 0; i < pre_raster_count; i++) {
                    VkPushConstantRange pre_raster_range = pre_raster_info.layout->push_constant_ranges_layout->at(i);
                    VkPushConstantRange frag_shader_range = frag_shader_info.layout->push_constant_ranges_layout->at(i);
                    if (pre_raster_range.stageFlags != frag_shader_range.stageFlags ||
                        pre_raster_range.offset != frag_shader_range.offset || pre_raster_range.size != frag_shader_range.size) {
                        const char* vuid = only_libs ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06621"
                                                     : "VUID-VkGraphicsPipelineCreateInfo-flags-06620";
                        LogObjectList objlist(pre_raster_info.layout->Handle(), frag_shader_info.layout->Handle());
                        skip |= LogError(vuid, objlist, create_info_loc,
                                         "the graphics pipeline libraries have different push constants\n|Pre-Raster State| %s has "
                                         "pPushConstantRanges[%" PRIu32 "] of {%s, %" PRIu32 ", %" PRIu32
                                         "}\n|Fragment Shader State| %s has pPushConstantRanges[%" PRIu32 "] of {%s, %" PRIu32
                                         ", %" PRIu32 "}.",
                                         FormatHandle(pre_raster_info.layout->Handle()).c_str(), i,
                                         string_VkShaderStageFlags(pre_raster_range.stageFlags).c_str(), pre_raster_range.offset,
                                         pre_raster_range.size, FormatHandle(frag_shader_info.layout->Handle()).c_str(), i,
                                         string_VkShaderStageFlags(frag_shader_range.stageFlags).c_str(), frag_shader_range.offset,
                                         frag_shader_range.size);
                    }
                }
            }
        }

        // Check VkPipelineFragmentShadingRateStateCreateInfoKHR
        if (frag_shader_info.shading_rate_state || pre_raster_info.shading_rate_state) {
            const char* vuid =
                only_libs ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06639" : "VUID-VkGraphicsPipelineCreateInfo-flags-06638";
            if (!pre_raster_info.shading_rate_state) {
                skip |= LogError(
                    vuid, device, create_info_loc,
                    "Fragment Shader has a valid VkPipelineFragmentShadingRateStateCreateInfoKHR, but Pre Rasterization has "
                    "no VkPipelineFragmentShadingRateStateCreateInfoKHR in the pNext chain.");
            } else if (!frag_shader_info.shading_rate_state) {
                skip |= LogError(
                    vuid, device, create_info_loc,
                    "Pre Rasterization has a valid VkPipelineFragmentShadingRateStateCreateInfoKHR, but Fragment Shader has "
                    "no VkPipelineFragmentShadingRateStateCreateInfoKHR in the pNext chain.");
            } else if (!ComparePipelineFragmentShadingRateStateCreateInfo(*frag_shader_info.shading_rate_state,
                                                                          *pre_raster_info.shading_rate_state)) {
                skip |= LogError(vuid, device, create_info_loc,
                                 "Fragment Shader and Pre Rasterization were created with different "
                                 "VkPipelineFragmentShadingRateStateCreateInfoKHR.\n"
                                 "Fragment Shader:\n"
                                 "\tfragmentSize: (W = %" PRIu32 ", H = %" PRIu32
                                 ")\n"
                                 "\tcombinerOps[0]: %s\n"
                                 "\tcombinerOps[1]: %s\n"
                                 "Pre Rasterization:\n"
                                 "\tfragmentSize: (W = %" PRIu32 ", H = %" PRIu32
                                 ")\n"
                                 "\tcombinerOps[0]: %s\n"
                                 "\tcombinerOps[1]: %s\n",
                                 frag_shader_info.shading_rate_state->fragmentSize.width,
                                 frag_shader_info.shading_rate_state->fragmentSize.height,
                                 string_VkFragmentShadingRateCombinerOpKHR(frag_shader_info.shading_rate_state->combinerOps[0]),
                                 string_VkFragmentShadingRateCombinerOpKHR(frag_shader_info.shading_rate_state->combinerOps[1]),
                                 pre_raster_info.shading_rate_state->fragmentSize.width,
                                 pre_raster_info.shading_rate_state->fragmentSize.height,
                                 string_VkFragmentShadingRateCombinerOpKHR(pre_raster_info.shading_rate_state->combinerOps[0]),
                                 string_VkFragmentShadingRateCombinerOpKHR(pre_raster_info.shading_rate_state->combinerOps[1]));
            }
        }

        // Check for consistent shader bindings + layout across libraries
        const auto& pre_raster_set_layouts = pre_raster_info.layout->set_layouts;
        const auto& fs_set_layouts = frag_shader_info.layout->set_layouts;
        const uint32_t pre_raster_count = pre_raster_info.layout ? static_cast<uint32_t>(pre_raster_set_layouts.list.size()) : 0;
        const uint32_t frag_shader_count = frag_shader_info.layout ? static_cast<uint32_t>(fs_set_layouts.list.size()) : 0;
        if (not_independent_sets && pre_raster_count != frag_shader_count && !pipeline.descriptor_heap_mode) {
            const char* vuid =
                only_libs ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06613" : "VUID-VkGraphicsPipelineCreateInfo-flags-06612";
            LogObjectList objlist(pre_raster_info.layout->Handle(), frag_shader_info.layout->Handle());
            skip |=
                LogError(vuid, objlist, create_info_loc,
                         "VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT was not set and the graphics pipeline "
                         "library have differently defined VkPipelineLayout:\n|Pre-Raster State| %s has setLayoutCount of %" PRIu32
                         "\n|Fragment Shader State| %s has setLayoutCount of %" PRIu32 "\n",
                         FormatHandle(pre_raster_info.layout->Handle()).c_str(), pre_raster_count,
                         FormatHandle(frag_shader_info.layout->Handle()).c_str(), frag_shader_count);
        }

        const auto num_set_layouts = std::max(pre_raster_count, frag_shader_count);
        for (uint32_t i = 0; i < num_set_layouts; ++i) {
            // if using VK_NULL_HANDLE, index into set_layouts will be null
            const auto pre_raster_dsl = i < pre_raster_count ? pre_raster_set_layouts.list[i] : nullptr;
            const auto fs_dsl = i < frag_shader_count ? fs_set_layouts.list[i] : nullptr;

            if (!pre_raster_dsl && fs_dsl) {
                // Null DSL at pSetLayouts[i] in pre-raster state. Make sure that shader bindings in corresponding DSL in
                // fragment shader state do not overlap.
                for (const auto& fs_binding : fs_dsl->GetBindings()) {
                    const VkShaderStageFlags overlap_flags = fs_binding.stageFlags & PreRasterState::ValidShaderStages();
                    if (overlap_flags == 0) {
                        continue;
                    }
                    const auto pre_raster_layout_handle_str = FormatHandle(pre_raster_info.layout->Handle());
                    const auto fs_layout_handle_str = FormatHandle(frag_shader_info.layout->Handle());
                    const auto fs_dsl_handle_str = FormatHandle(fs_dsl->Handle());
                    const char* vuid = nullptr;
                    std::ostringstream msg;
                    if (pre_raster_info.init == GPLInitType::gpl_flags) {
                        vuid = "VUID-VkGraphicsPipelineCreateInfo-flags-06756";
                        msg << "represents a library containing |Pre-Raster State| and " << pre_raster_layout_handle_str
                            << " at pSetLayouts[" << i << "] is null. "
                            << "However, a library with |Fragment Shader State| is specified in "
                               "VkPipelineLibraryCreateInfoKHR::pLibraries contains "
                            << fs_layout_handle_str << " with pSetLayouts[" << i << "] " << fs_dsl_handle_str
                            << " (non-null) where binding " << fs_binding.binding << " contains stageFlags with "
                            << string_VkShaderStageFlags(overlap_flags)
                            << ", which is not allowed to overlap with |Pre-Raster State|.";
                    } else if (frag_shader_info.init == GPLInitType::gpl_flags) {
                        vuid = "VUID-VkGraphicsPipelineCreateInfo-flags-06757";
                        msg << "represents a library containing |Fragment Shader State| where " << fs_layout_handle_str
                            << " at pSetLayouts[" << i << "] " << fs_dsl_handle_str << " (non-null) where binding "
                            << fs_binding.binding << " contains stageFlags with " << string_VkShaderStageFlags(overlap_flags)
                            << ", which is not allowed to overlap with |Pre-Raster State|. This is because a library with "
                               "|Pre-Raster State| is specified in "
                               "VkPipelineLibraryCreateInfoKHR::pLibraries which contains "
                            << pre_raster_layout_handle_str << " where pSetLayouts[" << i << "] is NULL";
                    } else {
                        vuid = "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06758";
                        msg << "is linking both |Pre-Raster State| and |Fragment Shader State| libraries. The |Pre-Raster "
                               "State| contains "
                            << pre_raster_layout_handle_str << " with pSetLayouts[" << i << "] set to NULL. "
                            << "However, the |Fragment Shader State| " << fs_layout_handle_str << " at pSetLayouts[" << i << "] "
                            << fs_dsl_handle_str << " (non-null) where bindings " << fs_binding.binding
                            << " contains stageFlags with " << string_VkShaderStageFlags(overlap_flags)
                            << ", which is not allowed to overlap with |Pre-Raster State|.";
                    }
                    LogObjectList objlist(pre_raster_info.layout->Handle(), fs_dsl->Handle(), frag_shader_info.layout->Handle());
                    skip |= LogError(vuid, objlist, create_info_loc, "%s", msg.str().c_str());
                    break;
                }
            } else if (pre_raster_dsl && !fs_dsl) {
                // Null DSL at pSetLayouts[i] in FS state. Make sure that shader bindings in corresponding DSL in pre-raster
                // state do not overlap.
                for (const auto& pre_raster_binding : pre_raster_dsl->GetBindings()) {
                    const VkShaderStageFlags overlap_flags =
                        pre_raster_binding.stageFlags & FragmentShaderState::ValidShaderStages();
                    if (overlap_flags == 0) {
                        continue;
                    }
                    const auto pre_raster_layout_handle_str = FormatHandle(pre_raster_info.layout->Handle());
                    const auto fs_layout_handle_str = FormatHandle(frag_shader_info.layout->Handle());
                    const auto pre_raster_dsl_handle_str = FormatHandle(pre_raster_dsl->Handle());
                    const char* vuid = nullptr;
                    std::ostringstream msg;
                    if (frag_shader_info.init == GPLInitType::gpl_flags) {
                        vuid = "VUID-VkGraphicsPipelineCreateInfo-flags-06756";
                        msg << "represents a library containing |Fragment Shader State| and " << fs_layout_handle_str
                            << " at pSetLayouts[" << i << "] is null. "
                            << "However, a library with |Pre-Raster State| is specified in "
                               "VkPipelineLibraryCreateInfoKHR::pLibraries contains "
                            << pre_raster_layout_handle_str << " with pSetLayouts[" << i << "] " << pre_raster_dsl_handle_str
                            << " (non-null) where binding " << pre_raster_binding.binding << " contains stageFlags with "
                            << string_VkShaderStageFlags(overlap_flags)
                            << ", which is not allowed to overlap with |Fragment Shader State|.";
                    } else if (pre_raster_info.init == GPLInitType::gpl_flags) {
                        vuid = "VUID-VkGraphicsPipelineCreateInfo-flags-06757";
                        msg << "represents a library containing |Pre-Raster State| where " << pre_raster_layout_handle_str
                            << " at pSetLayouts[" << i << "] " << pre_raster_dsl_handle_str << " (non-null) where binding "
                            << pre_raster_binding.binding << " contains stageFlags with "
                            << string_VkShaderStageFlags(overlap_flags)
                            << ", which is not allowed to overlap with |Fragment Shader State|. This is because a library with "
                               "|Fragment Shader State| is specified in "
                               "VkPipelineLibraryCreateInfoKHR::pLibraries which contains "
                            << fs_layout_handle_str << " where pSetLayouts[" << i << "] is NULL";
                    } else {
                        vuid = "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06758";
                        msg << "is linking both |Pre-Raster State| and |Fragment Shader State| libraries. The |Fragment "
                               "Shader State| contains "
                            << fs_layout_handle_str << " with pSetLayouts[" << i << "] set to NULL. "
                            << "However, the |Pre-Raster State| " << pre_raster_layout_handle_str << " at pSetLayouts[" << i << "] "
                            << pre_raster_dsl_handle_str << " (non-null) where bindings " << pre_raster_binding.binding
                            << " contains stageFlags with " << string_VkShaderStageFlags(overlap_flags)
                            << ", which is not allowed to overlap with |Fragment Shader State|.";
                    }
                    LogObjectList objlist(pre_raster_dsl->Handle(), pre_raster_info.layout->Handle(),
                                          frag_shader_info.layout->Handle());
                    skip |= LogError(vuid, objlist, create_info_loc, "%s", msg.str().c_str());
                    break;
                }
            } else if (!pre_raster_dsl && !fs_dsl) {
                if (!not_independent_sets) {
                    // With INDEPENDENT_SETS this is valid
                    // https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/8081
                    break;
                }
                const auto pre_raster_layout_handle_str = FormatHandle(pre_raster_info.layout->Handle());
                const auto fs_layout_handle_str = FormatHandle(frag_shader_info.layout->Handle());
                const char* vuid = nullptr;
                std::ostringstream msg;
                if (frag_shader_info.init == GPLInitType::gpl_flags) {
                    vuid = "VUID-VkGraphicsPipelineCreateInfo-flags-06679";
                    msg << "represents a library containing |Fragment Shader State| where " << fs_layout_handle_str
                        << " at pSetLayouts[" << i
                        << "] is NULL. This is invalid because a library with |Pre-Raster State| is specified in "
                           "VkPipelineLibraryCreateInfoKHR::pLibraries and "
                        << pre_raster_layout_handle_str << " has pSetLayouts[" << i << "] set to NULL too.";
                } else if (pre_raster_info.init == GPLInitType::gpl_flags) {
                    vuid = "VUID-VkGraphicsPipelineCreateInfo-flags-06679";
                    msg << "represents a library containing |Pre-Raster State| where " << pre_raster_layout_handle_str
                        << " at pSetLayouts[" << i
                        << "] is NULL. This is invalid because a library with |Fragment Shader State| is specified in "
                           "VkPipelineLibraryCreateInfoKHR::pLibraries and "
                        << fs_layout_handle_str << " has pSetLayouts[" << i << "] set to NULL too.";
                } else {
                    vuid = "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06681";
                    msg << "is linking both |Pre-Raster State| and |Fragment Shader State| libraries but both VkPipelineLayout at "
                           "pSetLayouts["
                        << i << "] are NULL\n|Pre-Raster State| " << pre_raster_layout_handle_str << "\n|Fragment Shader State| "
                        << fs_layout_handle_str << "\n";
                }
                LogObjectList objlist(pre_raster_info.layout->Handle(), frag_shader_info.layout->Handle());
                skip |= LogError(vuid, objlist, create_info_loc, "%s", msg.str().c_str());
                break;
            } else if (not_independent_sets && !pipeline.descriptor_heap_mode) {
                // both handles are valid, but without VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT, need to check everything
                // is identically defined
                if (pre_raster_dsl->GetCreateFlags() != fs_dsl->GetCreateFlags()) {
                    const char* vuid = only_libs ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06613"
                                                 : "VUID-VkGraphicsPipelineCreateInfo-flags-06612";
                    LogObjectList objlist(pre_raster_dsl->Handle(), pre_raster_info.layout->Handle(), fs_dsl->Handle(),
                                          frag_shader_info.layout->Handle());
                    skip |= LogError(vuid, objlist, create_info_loc,
                                     "VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT was not set and the graphics pipeline "
                                     "library have differently defined VkPipelineLayout:\n|Pre-Raster State| %s at "
                                     "pSetLayouts[%" PRIu32
                                     "] has flags %s"
                                     "\n|Fragment Shader State| %s at pSetLayouts[%" PRIu32 "] has flags %s\n",
                                     FormatHandle(pre_raster_info.layout->Handle()).c_str(), i,
                                     string_VkDescriptorSetLayoutCreateFlags(pre_raster_dsl->GetCreateFlags()).c_str(),
                                     FormatHandle(frag_shader_info.layout->Handle()).c_str(), i,
                                     string_VkDescriptorSetLayoutCreateFlags(fs_dsl->GetCreateFlags()).c_str());
                } else if (pre_raster_dsl->GetBindingCount() != fs_dsl->GetBindingCount()) {
                    const char* vuid = only_libs ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06613"
                                                 : "VUID-VkGraphicsPipelineCreateInfo-flags-06612";
                    LogObjectList objlist(pre_raster_dsl->Handle(), pre_raster_info.layout->Handle(), fs_dsl->Handle(),
                                          frag_shader_info.layout->Handle());
                    skip |= LogError(vuid, objlist, create_info_loc,
                                     "VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT was not set and the graphics pipeline "
                                     "library have differently defined VkPipelineLayout:\n|Pre-Raster State| %s at "
                                     "pSetLayouts[%" PRIu32 "] has bindingCount %" PRIu32
                                     "\n|Fragment Shader State| %s at pSetLayouts[%" PRIu32 "] has bindingCount %" PRIu32 "\n",
                                     FormatHandle(pre_raster_info.layout->Handle()).c_str(), i, pre_raster_dsl->GetBindingCount(),
                                     FormatHandle(frag_shader_info.layout->Handle()).c_str(), i, fs_dsl->GetBindingCount());
                } else {
                    const uint32_t binding_count = pre_raster_dsl->GetBindingCount();
                    const auto& pre_raster_bindings = pre_raster_dsl->GetBindings();
                    const auto& fs_bindings = fs_dsl->GetBindings();
                    for (uint32_t binding_index = 0; binding_index < binding_count; binding_index++) {
                        const auto& pre_raster_binding = pre_raster_bindings[binding_index];
                        const auto& fs_binding = fs_bindings[binding_index];
                        if (!CompareDescriptorSetLayoutBinding(*pre_raster_binding.ptr(), *fs_binding.ptr())) {
                            const char* vuid = only_libs ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06613"
                                                         : "VUID-VkGraphicsPipelineCreateInfo-flags-06612";
                            LogObjectList objlist(pre_raster_dsl->Handle(), pre_raster_info.layout->Handle(), fs_dsl->Handle(),
                                                  frag_shader_info.layout->Handle());
                            skip |= LogError(
                                vuid, objlist, create_info_loc,
                                "VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT was not set and the graphics pipeline "
                                "library have differently defined VkPipelineLayout:\n|Pre-Raster State| %s at pSetLayouts[%" PRIu32
                                "].pBindings[%" PRIu32
                                "] of:\n"
                                "\tbinding: %" PRIu32
                                "\n"
                                "\tdescriptorType: %s\n"
                                "\tdescriptorCount: %" PRIu32
                                "\n"
                                "\tstageFlags: %s\n"
                                "\tpImmutableSamplers: 0x%p\n"
                                "|Fragment Shader State| %s at pSetLayouts[%" PRIu32 "] pBindings[%" PRIu32
                                "] of:\n"
                                "\tbinding: %" PRIu32
                                "\n"
                                "\tdescriptorType: %s\n"
                                "\tdescriptorCount: %" PRIu32
                                "\n"
                                "\tstageFlags: %s\n"
                                "\tpImmutableSamplers: 0x%p\n",
                                FormatHandle(pre_raster_info.layout->Handle()).c_str(), i, binding_index,
                                pre_raster_binding.binding, string_VkDescriptorType(pre_raster_binding.descriptorType),
                                pre_raster_binding.descriptorCount,
                                string_VkShaderStageFlags(pre_raster_binding.stageFlags).c_str(),
                                pre_raster_binding.pImmutableSamplers, FormatHandle(frag_shader_info.layout->Handle()).c_str(), i,
                                binding_index, fs_binding.binding, string_VkDescriptorType(fs_binding.descriptorType),
                                fs_binding.descriptorCount, string_VkShaderStageFlags(fs_binding.stageFlags).c_str(),
                                fs_binding.pImmutableSamplers);
                        }
                    }
                }
            }
        }
    }

    if (frag_shader_info.init != GPLInitType::uninitialized && frag_output_info.init != GPLInitType::uninitialized) {
        if (!frag_shader_info.ms_state && frag_output_info.ms_state) {
            // if Fragment Output has sampleShadingEnable == false, Fragement Shader may be null
            if (frag_output_info.ms_state->sampleShadingEnable) {
                const char* vuid =
                    (frag_shader_info.init == GPLInitType::gpl_flags)   ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-09567"
                    : (frag_output_info.init == GPLInitType::gpl_flags) ? "VUID-VkGraphicsPipelineCreateInfo-flags-06637"
                                                                        : "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06636";
                skip |= LogError(
                    vuid, device, create_info_loc,
                    "Fragment Output Interface were created with VkPipelineMultisampleStateCreateInfo::sampleShadingEnable to "
                    "VK_TRUE, but Fragment Shader has a pMultisampleState of NULL.");
            }
        } else if (frag_shader_info.ms_state && !frag_output_info.ms_state) {
            const char* vuid = (frag_shader_info.init == GPLInitType::gpl_flags) ? "VUID-VkGraphicsPipelineCreateInfo-flags-06633"
                               : (frag_output_info.init == GPLInitType::gpl_flags)
                                   ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06634"
                                   : "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06635";
            skip |= LogError(vuid, device, create_info_loc,
                             "Fragment Shader has a valid VkPipelineMultisampleStateCreateInfo, but Fragment Output Interface has "
                             "a pMultisampleState of NULL.");
        } else if (frag_shader_info.ms_state && frag_output_info.ms_state) {
            if (!ComparePipelineMultisampleStateCreateInfo(*frag_shader_info.ms_state, *frag_output_info.ms_state)) {
                const char* vuid =
                    (frag_shader_info.init == GPLInitType::gpl_flags)   ? "VUID-VkGraphicsPipelineCreateInfo-flags-06633"
                    : (frag_output_info.init == GPLInitType::gpl_flags) ? "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06634"
                                                                        : "VUID-VkGraphicsPipelineCreateInfo-pLibraries-06635";
                skip |= LogError(vuid, device, create_info_loc,
                                 "Fragment Shader and Fragment Output Interface were created with different "
                                 "VkPipelineMultisampleStateCreateInfo."
                                 "Fragment Shader pMultisampleState:\n"
                                 "\tpNext: %p\n"
                                 "\trasterizationSamples: %s\n"
                                 "\tsampleShadingEnable: %d\n"
                                 "\tminSampleShading: %f\n"
                                 "\tpSampleMask: %p\n"
                                 "\talphaToCoverageEnable: %d\n"
                                 "\talphaToOneEnable: %d\n"
                                 "Fragment Output Interface pMultisampleState:\n"
                                 "\tpNext: %p\n"
                                 "\trasterizationSamples: %s\n"
                                 "\tsampleShadingEnable: %d\n"
                                 "\tminSampleShading: %f\n"
                                 "\tpSampleMask: %p\n"
                                 "\talphaToCoverageEnable: %d\n"
                                 "\talphaToOneEnable: %d\n",
                                 frag_shader_info.ms_state->pNext,
                                 string_VkSampleCountFlagBits(frag_shader_info.ms_state->rasterizationSamples),
                                 frag_shader_info.ms_state->sampleShadingEnable, frag_shader_info.ms_state->minSampleShading,
                                 frag_shader_info.ms_state->pSampleMask, frag_shader_info.ms_state->alphaToCoverageEnable,
                                 frag_shader_info.ms_state->alphaToOneEnable, frag_output_info.ms_state->pNext,
                                 string_VkSampleCountFlagBits(frag_output_info.ms_state->rasterizationSamples),
                                 frag_output_info.ms_state->sampleShadingEnable, frag_output_info.ms_state->minSampleShading,
                                 frag_output_info.ms_state->pSampleMask, frag_output_info.ms_state->alphaToCoverageEnable,
                                 frag_output_info.ms_state->alphaToOneEnable);
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineBlendEnable(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;
    const auto rp_state = pipeline.RenderPassState();
    if (!rp_state || rp_state->UsesDynamicRendering()) {
        return skip;
    }

    const auto subpass = pipeline.Subpass();
    const auto* subpass_desc = &rp_state->create_info.pSubpasses[subpass];
    if (!subpass_desc) {
        return skip;
    }

    for (uint32_t i = 0; i < pipeline.AttachmentStates().size() && i < subpass_desc->colorAttachmentCount; ++i) {
        const auto attachment = subpass_desc->pColorAttachments[i].attachment;
        if (attachment == VK_ATTACHMENT_UNUSED) {
            continue;
        }

        const auto attachment_desc = rp_state->create_info.pAttachments[attachment];

        if (!pipeline.RasterizationDisabled() && pipeline.AttachmentStates()[i].blendEnable) {
            const VkFormatFeatureFlags2 format_features = GetPotentialFormatFeatures(attachment_desc.format);
            if (!(format_features & VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BLEND_BIT)) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-06041", device,
                                 create_info_loc.dot(Field::pColorBlendState).dot(Field::pAttachments, i).dot(Field::blendEnable),
                                 "is VK_TRUE but format %s of the corresponding attachment description (subpass %" PRIu32
                                 ", attachment %" PRIu32 ") doesn't support VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT.\n%s",
                                 string_VkFormat(attachment_desc.format), subpass, attachment,
                                 string_VkFormatFeatureFlags2(format_features).c_str());
            }
        }

        if (attachment_desc.format == VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 &&
            !pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT)) {
            const VkColorComponentFlags& color_write_mask = pipeline.AttachmentStates()[i].colorWriteMask;
            const VkColorComponentFlags rgba =
                VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
            if ((color_write_mask & rgba) != rgba && (color_write_mask & rgba) != 0) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-None-09043", device, create_info_loc.dot(Field::renderPass),
                                 "was created with pAttachments[%" PRIu32
                                 "].format of VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, but pColorBlendState->pAttachments[%" PRIu32
                                 "].colorWriteMask is %s.\nThe 999E5 format is special as it overlaps the traditional 8-bit "
                                 "boundary and has no real alpha channel. When updating, the driver needs the full RGBA mask in "
                                 "order to properly handle this format.",
                                 attachment, i, string_VkColorComponentFlags(color_write_mask).c_str());
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineMeshTask(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;

    const ShaderStageState* task_state = nullptr;
    const ShaderStageState* mesh_state = nullptr;
    for (const auto& stage_state : pipeline.stage_states) {
        const VkShaderStageFlagBits stage = stage_state.GetStage();
        if (stage == VK_SHADER_STAGE_MESH_BIT_EXT) {
            mesh_state = &stage_state;
        } else if (stage == VK_SHADER_STAGE_TASK_BIT_EXT) {
            task_state = &stage_state;
        }
    }

    if (!mesh_state) {
        return skip;
    }

    if (task_state && mesh_state->spirv_state && mesh_state->spirv_state->static_data_.has_built_in_draw_index) {
        // There is a dedicated equivalent for shader object
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-09631", device, create_info_loc,
                         "The pipeline is being created with a Task and Mesh shader bound, but the Mesh Shader "
                         "uses DrawIndex (gl_DrawID) which will be an undefined value when reading.");
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineExternalFormatResolve(const vvl::Pipeline& pipeline, const vvl::RenderPass& rp_state,
                                                               const vku::safe_VkSubpassDescription2& subpass_desc,
                                                               const Location& create_info_loc) const {
    bool skip = false;
    if (!enabled_features.externalFormatResolve) return skip;

    if (subpass_desc.colorAttachmentCount == 0 || !subpass_desc.pResolveAttachments) {
        return skip;
    }
    // can only have 1 color attachment
    const uint32_t attachment = subpass_desc.pResolveAttachments[0].attachment;
    if (attachment == VK_ATTACHMENT_UNUSED) {
        return skip;
    }
    const uint64_t external_format = GetExternalFormat(rp_state.create_info.pAttachments[attachment].pNext);
    if (external_format == 0) {
        return skip;
    }

    const auto* multisample_state = pipeline.MultisampleState();
    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT) && multisample_state) {
        if (multisample_state->rasterizationSamples != VK_SAMPLE_COUNT_1_BIT) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09313", device,
                             create_info_loc.dot(Field::pMultisampleState).dot(Field::rasterizationSamples),
                             "is %" PRIu32 ", but externalFormat is %" PRIu64 " for subpass %" PRIu32 ".",
                             multisample_state->rasterizationSamples, external_format, pipeline.Subpass());
        }
    }

    const auto* color_blend_state = pipeline.ColorBlendState();
    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT) && color_blend_state) {
        for (uint32_t i = 0; i < color_blend_state->attachmentCount; i++) {
            if (color_blend_state->pAttachments[i].blendEnable) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09314", device,
                                 create_info_loc.dot(Field::pColorBlendState).dot(Field::pAttachments, i).dot(Field::blendEnable),
                                 "is VK_TRUE, but externalFormat is %" PRIu64 " for subpass %" PRIu32 ".", external_format,
                                 pipeline.Subpass());
            }
        }
    }

    const auto fragment_shading_rate =
        vku::FindStructInPNextChain<VkPipelineFragmentShadingRateStateCreateInfoKHR>(pipeline.GetCreateInfoPNext());
    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR) && fragment_shading_rate) {
        if (fragment_shading_rate->fragmentSize.width != 1) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09315", device,
                             create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::fragmentSize)
                                 .dot(Field::width),
                             "is %" PRIu32 ", but externalFormat is %" PRIu64 " for subpass %" PRIu32 ".",
                             fragment_shading_rate->fragmentSize.width, external_format, pipeline.Subpass());
        }
        if (fragment_shading_rate->fragmentSize.height != 1) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09316", device,
                             create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::fragmentSize)
                                 .dot(Field::height),
                             "is %" PRIu32 ", but externalFormat is %" PRIu64 " for subpass %" PRIu32 ".",
                             fragment_shading_rate->fragmentSize.height, external_format, pipeline.Subpass());
        }
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineExternalFormatResolveDynamicRendering(const vvl::Pipeline& pipeline,
                                                                               const Location& create_info_loc) const {
    bool skip = false;
    if (!enabled_features.externalFormatResolve) return skip;

    const uint64_t external_format = GetExternalFormat(pipeline.GetCreateInfoPNext());
    const auto* rendering_struct = pipeline.rendering_create_info;
    if (external_format == 0 || !rendering_struct) {
        return skip;
    }

    if (rendering_struct->viewMask != 0) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09301", device,
                         create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::viewMask),
                         "is 0x%" PRIx32 ", but externalFormat is %" PRIu64 ".", rendering_struct->viewMask, external_format);
    }
    if (rendering_struct->colorAttachmentCount != 1) {
        skip |=
            LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09309", device,
                     create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::colorAttachmentCount),
                     "is %" PRIu32 ", but externalFormat is %" PRIu64 ".", rendering_struct->colorAttachmentCount, external_format);
    }

    if (pipeline.OwnsLibState(pipeline.fragment_shader_state) && pipeline.fragment_shader_state->fragment_entry_point) {
        auto entrypoint = pipeline.fragment_shader_state->fragment_entry_point;
        if (entrypoint->execution_mode.Has(spirv::ExecutionModeSet::depth_replacing_bit)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09310", device,
                             create_info_loc.pNext(Struct::VkExternalFormatANDROID, Field::externalFormat),
                             "is %" PRIu64 " but the fragment shader declares DepthReplacing.", external_format);
        } else if (entrypoint->execution_mode.Has(spirv::ExecutionModeSet::stencil_ref_replacing_bit)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09310", device,
                             create_info_loc.pNext(Struct::VkExternalFormatANDROID, Field::externalFormat),
                             "is %" PRIu64 " but the fragment shader declares StencilRefReplacingEXT.", external_format);
        }
    }

    const auto* multisample_state = pipeline.MultisampleState();
    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT) && multisample_state) {
        if (multisample_state->rasterizationSamples != VK_SAMPLE_COUNT_1_BIT) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09304", device,
                             create_info_loc.dot(Field::pMultisampleState).dot(Field::rasterizationSamples),
                             "is %s, but externalFormat is %" PRIu64 ".",
                             string_VkSampleCountFlagBits(multisample_state->rasterizationSamples), external_format);
        }
    }

    const auto* color_blend_state = pipeline.ColorBlendState();
    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT) && color_blend_state) {
        for (uint32_t i = 0; i < color_blend_state->attachmentCount; i++) {
            if (color_blend_state->pAttachments[i].blendEnable) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09305", device,
                                 create_info_loc.dot(Field::pColorBlendState).dot(Field::pAttachments, i).dot(Field::blendEnable),
                                 "is VK_TRUE, but externalFormat is %" PRIu64 ".", external_format);
            }
        }
    }

    const auto fragment_shading_rate =
        vku::FindStructInPNextChain<VkPipelineFragmentShadingRateStateCreateInfoKHR>(pipeline.GetCreateInfoPNext());
    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR) && fragment_shading_rate) {
        if (fragment_shading_rate->fragmentSize.width != 1) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09306", device,
                             create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::fragmentSize)
                                 .dot(Field::width),
                             "is %" PRIu32 ", but externalFormat is %" PRIu64 ".", fragment_shading_rate->fragmentSize.width,
                             external_format);
        }
        if (fragment_shading_rate->fragmentSize.height != 1) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-externalFormatResolve-09307", device,
                             create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::fragmentSize)
                                 .dot(Field::height),
                             "is %" PRIu32 ", but externalFormat is %" PRIu64 ".", fragment_shading_rate->fragmentSize.height,
                             external_format);
        }
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineInputAssemblyState(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;
    const Location ia_loc = create_info_loc.dot(Field::pInputAssemblyState);

    // if vertex_input_state is not set, will be null
    const auto* ia_state = pipeline.InputAssemblyState();
    if (ia_state) {
        const VkPrimitiveTopology topology = ia_state->topology;
        if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE) && ia_state->primitiveRestartEnable) {
            if (!enabled_features.primitiveTopologyPatchListRestart && topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) {
                skip |= LogError("VUID-VkPipelineInputAssemblyStateCreateInfo-topology-06253", device, ia_loc.dot(Field::topology),
                                 "is VK_PRIMITIVE_TOPOLOGY_PATCH_LIST and primitiveRestartEnable is VK_TRUE, but the "
                                 "primitiveTopologyPatchListRestart feature was not enabled.");
            } else if (!enabled_features.primitiveTopologyListRestart &&
                       IsValueIn(topology, {VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
                                            VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY,
                                            VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY})) {
                skip |= LogError("VUID-VkPipelineInputAssemblyStateCreateInfo-topology-06252", device, ia_loc.dot(Field::topology),
                                 "is %s and primitiveRestartEnable is VK_TRUE, but the primitiveTopologyListRestart feature "
                                 "was not enabled.",
                                 string_VkPrimitiveTopology(topology));
            }
        }

        if (!enabled_features.geometryShader &&
            IsValueIn(topology,
                      {VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY,
                       VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY})) {
            skip |= LogError("VUID-VkPipelineInputAssemblyStateCreateInfo-topology-00429", device, ia_loc.dot(Field::topology),
                             "is %s but the geometryShader feature was not enabled.", string_VkPrimitiveTopology(topology));
        }
        if (!enabled_features.tessellationShader && topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) {
            skip |= LogError("VUID-VkPipelineInputAssemblyStateCreateInfo-topology-00430", device, ia_loc.dot(Field::topology),
                             "is VK_PRIMITIVE_TOPOLOGY_PATCH_LIST but the tessellationShader feature was not enabled.");
        }
    }

    const bool ignore_topology = pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY) &&
                                 phys_dev_ext_props.extended_dynamic_state3_props.dynamicPrimitiveTopologyUnrestricted;
    // pre_raster has tessellation stage, vertex input has topology
    // Both are needed for these checks
    if (!ignore_topology && pipeline.pre_raster_state && pipeline.vertex_input_state) {
        // Either both or neither TC/TE shaders should be defined
        const bool has_control = (pipeline.active_shaders & VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT) != 0;
        const bool has_eval = (pipeline.active_shaders & VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) != 0;
        const bool has_tessellation = has_control && has_eval;  // need both
        // VK_PRIMITIVE_TOPOLOGY_PATCH_LIST primitive topology is only valid for tessellation pipelines.
        // Mismatching primitive topology and tessellation fails graphics pipeline creation.
        if (has_tessellation && (!ia_state || ia_state->topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-08888", device, ia_loc.dot(Field::topology),
                             "is %s for tessellation shaders in pipeline (needs to be VK_PRIMITIVE_TOPOLOGY_PATCH_LIST).",
                             ia_state ? string_VkPrimitiveTopology(ia_state->topology) : "null");
        }
        if (!has_tessellation && (ia_state && ia_state->topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-topology-08889", device, ia_loc.dot(Field::topology),
                             "is VK_PRIMITIVE_TOPOLOGY_PATCH_LIST but no tessellation shaders.");
        }
    };
    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineTessellationState(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;

    if (pipeline.OwnsLibState(pipeline.pre_raster_state) &&
        (pipeline.create_info_shaders & VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT)) {
        if (!pipeline.TessellationState() && (!pipeline.IsDynamic(CB_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT) ||
                                              !IsExtEnabled(extensions.vk_ext_extended_dynamic_state3))) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-09022", device, create_info_loc.dot(Field::pStages),
                             "includes a tessellation control shader stage, but pTessellationState is NULL."
                             "\nIf the following are all set, it can be NULL"
                             "\n  Enable VK_EXT_extended_dynamic_state3 (%senabled)"
                             "\n  Use VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT (%s)\n",
                             IsExtEnabled(extensions.vk_ext_extended_dynamic_state3) ? "" : "not ",
                             pipeline.IsDynamic(CB_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT) ? "set" : "not set");
        }
    }
    return skip;
}

bool CoreChecks::ValidateGraphicsPipelinePreRasterizationState(const vvl::Pipeline& pipeline,
                                                               const Location& create_info_loc) const {
    bool skip = false;
    // Only validate once during creation
    if (!pipeline.OwnsLibState(pipeline.pre_raster_state)) {
        return skip;
    }
    const VkShaderStageFlags stages = pipeline.create_info_shaders;
    if ((stages & PreRasterState::ValidShaderStages()) == 0) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-06896", device, create_info_loc,
                         "contains pre-raster state, but stages (%s) does not contain any pre-raster shaders.",
                         string_VkShaderStageFlags(stages).c_str());
    }

    if (!enabled_features.geometryShader && (stages & VK_SHADER_STAGE_GEOMETRY_BIT)) {
        skip |= LogError("VUID-VkPipelineShaderStageCreateInfo-stage-00704", device, create_info_loc,
                         "pStages include Geometry Shader but geometryShader feature was not enabled.");
    }
    if (!enabled_features.tessellationShader &&
        (stages & (VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT))) {
        skip |= LogError("VUID-VkPipelineShaderStageCreateInfo-stage-00705", device, create_info_loc,
                         "pStages include Tessellation Shader but tessellationShader feature was not enabled.");
    }

    // VS or mesh is required
    if (!(stages & (VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_MESH_BIT_EXT))) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-stage-02096", device, create_info_loc,
                         "no stage in pStages contains a Vertex Shader or Mesh Shader.");
    }
    // Can't mix mesh and VTG
    if ((stages & (VK_SHADER_STAGE_MESH_BIT_EXT | VK_SHADER_STAGE_TASK_BIT_EXT)) &&
        (stages & (VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT |
                   VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT))) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-02095", device, create_info_loc,
                         "in pStages, Geometric shader stages must either be all mesh (mesh | task) "
                         "or all VTG (vertex, tess control, tess eval, geom).");
    }

    // VK_SHADER_STAGE_MESH_BIT_EXT and VK_SHADER_STAGE_MESH_BIT_NV are equivalent
    if (!(enabled_features.meshShader) && (stages & VK_SHADER_STAGE_MESH_BIT_EXT)) {
        skip |= LogError("VUID-VkPipelineShaderStageCreateInfo-stage-02091", device, create_info_loc,
                         "pStages include Mesh Shader but meshShader feature was not enabled.");
    }

    // VK_SHADER_STAGE_TASK_BIT_EXT and VK_SHADER_STAGE_TASK_BIT_NV are equivalent
    if (!(enabled_features.taskShader) && (stages & VK_SHADER_STAGE_TASK_BIT_EXT)) {
        skip |= LogError("VUID-VkPipelineShaderStageCreateInfo-stage-02092", device, create_info_loc,
                         "pStages include Task Shader but taskShader feature was not enabled.");
    }

    // Either both or neither TC/TE shaders should be defined
    const bool has_control = (stages & VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT) != 0;
    const bool has_eval = (stages & VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) != 0;
    if (has_control && !has_eval) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-00729", device, create_info_loc,
                         "pStages include a VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT but no "
                         "VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT stage.");
    }
    if (!has_control && has_eval) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-00730", device, create_info_loc,
                         "pStages include a VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT but no "
                         "VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT stage.");
    }
    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineColorBlendAttachmentState(const vvl::Pipeline& pipeline,
                                                                   const vku::safe_VkSubpassDescription2* subpass_desc,
                                                                   const Location& color_loc) const {
    bool skip = false;
    const auto& attachment_states = pipeline.AttachmentStates();
    if (attachment_states.empty()) return skip;
    if (pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT)) return skip;

    if (!enabled_features.independentBlend && attachment_states.size() > 1) {
        for (size_t i = 1; i < attachment_states.size(); i++) {
            if (!ComparePipelineColorBlendAttachmentState(attachment_states[0], attachment_states[i])) {
                skip |= LogError("VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605", device,
                                 color_loc.dot(Field::pAttachments, (uint32_t)i),
                                 "is different than pAttachments[0] and independentBlend feature was not enabled.");
                break;
            }
        }
    }

    const VkBlendOp first_color_blend_op = attachment_states[0].colorBlendOp;
    const VkBlendOp first_alpha_blend_op = attachment_states[0].alphaBlendOp;

    for (size_t i = 0; i < attachment_states.size(); i++) {
        const VkPipelineColorBlendAttachmentState& attachment_state = attachment_states[i];
        const Location& attachment_loc = color_loc.dot(Field::pAttachments, (uint32_t)i);
        if (!enabled_features.dualSrcBlend) {
            if (IsSecondaryColorInputBlendFactor(attachment_state.srcColorBlendFactor)) {
                skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-srcColorBlendFactor-00608", device,
                                 attachment_loc.dot(Field::srcColorBlendFactor),
                                 "(%s) is a dual-source blend factor, but dualSrcBlend feature was not enabled.",
                                 string_VkBlendFactor(attachment_state.srcColorBlendFactor));
            }
            if (IsSecondaryColorInputBlendFactor(attachment_state.dstColorBlendFactor)) {
                skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-dstColorBlendFactor-00609", device,
                                 attachment_loc.dot(Field::dstColorBlendFactor),
                                 "(%s) is a dual-source blend factor, but dualSrcBlend feature was not enabled.",
                                 string_VkBlendFactor(attachment_state.dstColorBlendFactor));
            }
            if (IsSecondaryColorInputBlendFactor(attachment_state.srcAlphaBlendFactor)) {
                skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-srcAlphaBlendFactor-00610", device,
                                 attachment_loc.dot(Field::srcAlphaBlendFactor),
                                 "(%s) is a dual-source blend factor, but dualSrcBlend feature was not enabled.",
                                 string_VkBlendFactor(attachment_state.srcAlphaBlendFactor));
            }
            if (IsSecondaryColorInputBlendFactor(attachment_state.dstAlphaBlendFactor)) {
                skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-dstAlphaBlendFactor-00611", device,
                                 attachment_loc.dot(Field::dstAlphaBlendFactor),
                                 "(%s) is a dual-source blend factor, but dualSrcBlend feature was not enabled.",
                                 string_VkBlendFactor(attachment_state.dstAlphaBlendFactor));
            }
        }

        // if blendEnabled is false, these values are ignored
        if (attachment_state.blendEnable) {
            bool advance_blend = false;
            if (IsAdvanceBlendOperation(attachment_state.colorBlendOp)) {
                advance_blend = true;
                if (phys_dev_ext_props.blend_operation_advanced_props.advancedBlendAllOperations == VK_FALSE) {
                    // This VUID checks if a subset of advance blend ops are allowed
                    switch (attachment_state.colorBlendOp) {
                        case VK_BLEND_OP_ZERO_EXT:
                        case VK_BLEND_OP_SRC_EXT:
                        case VK_BLEND_OP_DST_EXT:
                        case VK_BLEND_OP_SRC_OVER_EXT:
                        case VK_BLEND_OP_DST_OVER_EXT:
                        case VK_BLEND_OP_SRC_IN_EXT:
                        case VK_BLEND_OP_DST_IN_EXT:
                        case VK_BLEND_OP_SRC_OUT_EXT:
                        case VK_BLEND_OP_DST_OUT_EXT:
                        case VK_BLEND_OP_SRC_ATOP_EXT:
                        case VK_BLEND_OP_DST_ATOP_EXT:
                        case VK_BLEND_OP_XOR_EXT:
                        case VK_BLEND_OP_INVERT_EXT:
                        case VK_BLEND_OP_INVERT_RGB_EXT:
                        case VK_BLEND_OP_LINEARDODGE_EXT:
                        case VK_BLEND_OP_LINEARBURN_EXT:
                        case VK_BLEND_OP_VIVIDLIGHT_EXT:
                        case VK_BLEND_OP_LINEARLIGHT_EXT:
                        case VK_BLEND_OP_PINLIGHT_EXT:
                        case VK_BLEND_OP_HARDMIX_EXT:
                        case VK_BLEND_OP_PLUS_EXT:
                        case VK_BLEND_OP_PLUS_CLAMPED_EXT:
                        case VK_BLEND_OP_PLUS_CLAMPED_ALPHA_EXT:
                        case VK_BLEND_OP_PLUS_DARKER_EXT:
                        case VK_BLEND_OP_MINUS_EXT:
                        case VK_BLEND_OP_MINUS_CLAMPED_EXT:
                        case VK_BLEND_OP_CONTRAST_EXT:
                        case VK_BLEND_OP_INVERT_OVG_EXT:
                        case VK_BLEND_OP_RED_EXT:
                        case VK_BLEND_OP_GREEN_EXT:
                        case VK_BLEND_OP_BLUE_EXT: {
                            skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-advancedBlendAllOperations-01409", device,
                                             attachment_loc.dot(Field::colorBlendOp),
                                             "(%s) but "
                                             "VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT::"
                                             "advancedBlendAllOperations is "
                                             "VK_FALSE",
                                             string_VkBlendOp(attachment_state.colorBlendOp));
                            break;
                        }
                        default:
                            break;
                    }
                }

                if (phys_dev_ext_props.blend_operation_advanced_props.advancedBlendIndependentBlend == VK_FALSE &&
                    attachment_state.colorBlendOp != first_color_blend_op) {
                    skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-advancedBlendIndependentBlend-01407", device,
                                     attachment_loc.dot(Field::colorBlendOp), "(%s) is not same the other attachments (%s).",
                                     string_VkBlendOp(attachment_state.colorBlendOp), string_VkBlendOp(first_color_blend_op));
                }
            }

            if (IsAdvanceBlendOperation(attachment_state.alphaBlendOp)) {
                advance_blend = true;
                if (phys_dev_ext_props.blend_operation_advanced_props.advancedBlendIndependentBlend == VK_FALSE &&
                    attachment_state.alphaBlendOp != first_alpha_blend_op) {
                    skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-advancedBlendIndependentBlend-01408", device,
                                     attachment_loc.dot(Field::alphaBlendOp), "(%s) is not same the other attachments (%s).",
                                     string_VkBlendOp(attachment_state.alphaBlendOp), string_VkBlendOp(first_alpha_blend_op));
                }
            }

            if (advance_blend) {
                if (attachment_state.colorBlendOp != attachment_state.alphaBlendOp) {
                    skip |=
                        LogError("VUID-VkPipelineColorBlendAttachmentState-colorBlendOp-01406", device, attachment_loc,
                                 "has different colorBlendOp (%s) and alphaBlendOp (%s) but one of "
                                 "them is an advance blend operation.",
                                 string_VkBlendOp(attachment_state.colorBlendOp), string_VkBlendOp(attachment_state.alphaBlendOp));
                } else {
                    const uint32_t color_attachment_count = pipeline.rendering_create_info
                                                                ? pipeline.rendering_create_info->colorAttachmentCount
                                                            : subpass_desc ? subpass_desc->colorAttachmentCount
                                                                           : 0;
                    if (color_attachment_count != 0 &&
                        color_attachment_count >
                            phys_dev_ext_props.blend_operation_advanced_props.advancedBlendMaxColorAttachments) {
                        // color_attachment_count is found one of multiple spots above
                        //
                        // error can guarantee it is the same VkBlendOp
                        skip |= LogError("VUID-VkPipelineColorBlendAttachmentState-colorBlendOp-01410", device, attachment_loc,
                                         "has an advance blend operation (%s) but the colorAttachmentCount (%" PRIu32
                                         ") is larger than advancedBlendMaxColorAttachments (%" PRIu32 ").",
                                         string_VkBlendOp(attachment_state.colorBlendOp), color_attachment_count,
                                         phys_dev_ext_props.blend_operation_advanced_props.advancedBlendMaxColorAttachments);
                        break;  // if this fails once, will fail every iteration
                    }
                }
            }
        }
    }
    return skip;
}

bool CoreChecks::IsColorBlendStateAttachmentCountIgnore(const vvl::Pipeline& pipeline) const {
    return pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT) &&
           pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT) &&
           pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT) &&
           (pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT) || !enabled_features.advancedBlendCoherentOperations);
}
bool CoreChecks::ValidatePipelineColorBlendAdvancedStateCreateInfo(
    const vvl::Pipeline& pipeline, const VkPipelineColorBlendAdvancedStateCreateInfoEXT& color_blend_advanced,
    const Location& color_loc) const {
    bool skip = false;
    if (pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT)) return skip;

    const auto& prop = phys_dev_ext_props.blend_operation_advanced_props;
    if (!prop.advancedBlendCorrelatedOverlap && color_blend_advanced.blendOverlap != VK_BLEND_OVERLAP_UNCORRELATED_EXT) {
        skip |= LogError("VUID-VkPipelineColorBlendAdvancedStateCreateInfoEXT-blendOverlap-01426", device,
                         color_loc.pNext(Struct::VkPipelineColorBlendAdvancedStateCreateInfoEXT, Field::blendOverlap),
                         "is %s, but advancedBlendCorrelatedOverlap was not enabled.",
                         string_VkBlendOverlapEXT(color_blend_advanced.blendOverlap));
    }
    if (!prop.advancedBlendNonPremultipliedDstColor && color_blend_advanced.dstPremultiplied != VK_TRUE) {
        skip |= LogError("VUID-VkPipelineColorBlendAdvancedStateCreateInfoEXT-dstPremultiplied-01425", device,
                         color_loc.pNext(Struct::VkPipelineColorBlendAdvancedStateCreateInfoEXT, Field::dstPremultiplied),
                         "is VK_FALSE, but advancedBlendNonPremultipliedDstColor was not enabled.");
    }
    if (!prop.advancedBlendNonPremultipliedSrcColor && color_blend_advanced.srcPremultiplied != VK_TRUE) {
        skip |= LogError("VUID-VkPipelineColorBlendAdvancedStateCreateInfoEXT-srcPremultiplied-01424", device,
                         color_loc.pNext(Struct::VkPipelineColorBlendAdvancedStateCreateInfoEXT, Field::srcPremultiplied),
                         "is VK_FALSE, but advancedBlendNonPremultipliedSrcColor was not enabled.");
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineColorBlendState(const vvl::Pipeline& pipeline,
                                                         const vku::safe_VkSubpassDescription2* subpass_desc,
                                                         const Location& create_info_loc) const {
    bool skip = false;
    const Location color_loc = create_info_loc.dot(Field::pColorBlendState);
    const auto color_blend_state = pipeline.ColorBlendState();
    if (!color_blend_state) {
        return skip;
    }
    const auto rp_state = pipeline.RenderPassState();
    const bool null_rp = pipeline.IsRenderPassNull();
    if (!null_rp && !rp_state) return skip;  // invalid render pass

    // VkAttachmentSampleCountInfoAMD == VkAttachmentSampleCountInfoNV
    const auto* attachment_sample_count_info =
        vku::FindStructInPNextChain<VkAttachmentSampleCountInfoAMD>(pipeline.GetCreateInfoPNext());
    const auto* rendering_struct = pipeline.rendering_create_info;
    if (null_rp && rendering_struct && attachment_sample_count_info &&
        (attachment_sample_count_info->colorAttachmentCount != rendering_struct->colorAttachmentCount)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-06063", device,
                         create_info_loc.pNext(Struct::VkAttachmentSampleCountInfoAMD, Field::attachmentCount),
                         "(%" PRIu32 ") is different then %s (%" PRIu32 ").", attachment_sample_count_info->colorAttachmentCount,
                         create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::colorAttachmentCount).Fields().c_str(),
                         rendering_struct->colorAttachmentCount);
    }

    if (!null_rp && subpass_desc && color_blend_state->attachmentCount != subpass_desc->colorAttachmentCount) {
        if (!IsColorBlendStateAttachmentCountIgnore(pipeline)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-07609", device, color_loc.dot(Field::attachmentCount),
                             "(%" PRIu32 ") is different than %s pSubpasses[%" PRIu32 "].colorAttachmentCount (%" PRIu32 ").",
                             color_blend_state->attachmentCount, FormatHandle(rp_state->Handle()).c_str(), pipeline.Subpass(),
                             subpass_desc->colorAttachmentCount);
        }
    }

    skip |= ValidateGraphicsPipelineColorBlendAttachmentState(pipeline, subpass_desc, color_loc);

    if (!enabled_features.logicOp && (color_blend_state->logicOpEnable == VK_TRUE) &&
        !pipeline.IsDynamic(CB_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT)) {
        skip |= LogError("VUID-VkPipelineColorBlendStateCreateInfo-logicOpEnable-00606", device,
                         color_loc.dot(Field::logicOpEnable), "is VK_TRUE, but the logicOp feature was not enabled.");
    }

    if (auto color_write = vku::FindStructInPNextChain<VkPipelineColorWriteCreateInfoEXT>(color_blend_state->pNext)) {
        if (color_write->attachmentCount > phys_dev_props.limits.maxColorAttachments) {
            skip |= LogError("VUID-VkPipelineColorWriteCreateInfoEXT-attachmentCount-06655", device,
                             color_loc.pNext(Struct::VkPipelineColorWriteCreateInfoEXT, Field::attachmentCount),
                             "(%" PRIu32 ") is larger than the maxColorAttachments limit (%" PRIu32 ").",
                             color_write->attachmentCount, phys_dev_props.limits.maxColorAttachments);
        }
        if (!enabled_features.colorWriteEnable && !pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT)) {
            for (uint32_t i = 0; i < color_write->attachmentCount; ++i) {
                if (color_write->pColorWriteEnables[i] != VK_TRUE) {
                    skip |= LogError("VUID-VkPipelineColorWriteCreateInfoEXT-pAttachments-04801", device,
                                     color_loc.pNext(Struct::VkPipelineColorWriteCreateInfoEXT, Field::pColorWriteEnables, i),
                                     "is VK_FALSE, but colorWriteEnable feature was not enabled.");
                }
            }
        }
    }

    if (const auto* color_blend_advanced =
            vku::FindStructInPNextChain<VkPipelineColorBlendAdvancedStateCreateInfoEXT>(color_blend_state->pNext)) {
        skip |= ValidatePipelineColorBlendAdvancedStateCreateInfo(pipeline, *color_blend_advanced, color_loc);
    }
    return skip;
}

bool CoreChecks::ValidatePipelineRasterizationStateStreamCreateInfo(
    const vvl::Pipeline& pipeline, const VkPipelineRasterizationStateStreamCreateInfoEXT& rasterization_state_stream_ci,
    const Location& raster_loc) const {
    bool skip = false;
    if (!enabled_features.geometryStreams) {
        skip |= LogError("VUID-VkPipelineRasterizationStateStreamCreateInfoEXT-geometryStreams-02324", device,
                         raster_loc.dot(Field::pNext),
                         "chain includes VkPipelineRasterizationStateStreamCreateInfoEXT, but "
                         "geometryStreams feature was not enabled.");
    } else if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_STREAM_EXT)) {
        const auto& props = phys_dev_ext_props.transform_feedback_props;
        if (props.transformFeedbackRasterizationStreamSelect == VK_FALSE &&
            rasterization_state_stream_ci.rasterizationStream != 0) {
            skip |= LogError("VUID-VkPipelineRasterizationStateStreamCreateInfoEXT-rasterizationStream-02326", device,
                             raster_loc.pNext(Struct::VkPipelineRasterizationStateStreamCreateInfoEXT, Field::rasterizationStream),
                             "is (%" PRIu32 ") but transformFeedbackRasterizationStreamSelect is VK_FALSE.",
                             rasterization_state_stream_ci.rasterizationStream);
        } else if (rasterization_state_stream_ci.rasterizationStream >= props.maxTransformFeedbackStreams) {
            skip |= LogError("VUID-VkPipelineRasterizationStateStreamCreateInfoEXT-rasterizationStream-02325", device,
                             raster_loc.pNext(Struct::VkPipelineRasterizationStateStreamCreateInfoEXT, Field::rasterizationStream),
                             "(%" PRIu32 ") is not less than maxTransformFeedbackStreams (%" PRIu32 ").",
                             rasterization_state_stream_ci.rasterizationStream, props.maxTransformFeedbackStreams);
        }
    }
    return skip;
}

bool CoreChecks::ValidatePipelineRasterizationConservativeStateCreateInfo(
    const vvl::Pipeline& pipeline, const VkPipelineRasterizationConservativeStateCreateInfoEXT& rasterization_conservative_state_ci,
    const Location& raster_loc) const {
    bool skip = false;

    if (rasterization_conservative_state_ci.extraPrimitiveOverestimationSize < 0.0f ||
        rasterization_conservative_state_ci.extraPrimitiveOverestimationSize >
            phys_dev_ext_props.conservative_rasterization_props.maxExtraPrimitiveOverestimationSize) {
        skip |=
            LogError("VUID-VkPipelineRasterizationConservativeStateCreateInfoEXT-extraPrimitiveOverestimationSize-01769", device,
                     raster_loc.pNext(Struct::VkPipelineRasterizationConservativeStateCreateInfoEXT,
                                      Field::extraPrimitiveOverestimationSize),
                     "is (%f), which is not between 0.0 and "
                     "maxExtraPrimitiveOverestimationSize (%f).",
                     rasterization_conservative_state_ci.extraPrimitiveOverestimationSize,
                     phys_dev_ext_props.conservative_rasterization_props.maxExtraPrimitiveOverestimationSize);
    }

    if (!phys_dev_ext_props.conservative_rasterization_props.conservativePointAndLineRasterization &&
        rasterization_conservative_state_ci.conservativeRasterizationMode != VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT) {
        if (IsLineTopology(pipeline.topology_at_rasterizer) || IsPointTopology(pipeline.topology_at_rasterizer)) {
            skip |= LogError(
                "VUID-VkGraphicsPipelineCreateInfo-conservativePointAndLineRasterization-08892", device,
                raster_loc.pNext(Struct::VkPipelineRasterizationConservativeStateCreateInfoEXT,
                                 Field::conservativeRasterizationMode),
                "is %s but the Rasterization Input Topology is %s.",
                string_VkConservativeRasterizationModeEXT(rasterization_conservative_state_ci.conservativeRasterizationMode),
                string_VkPrimitiveTopology(pipeline.topology_at_rasterizer));
        }
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineRasterizationState(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;
    const auto raster_state = pipeline.RasterizationState();
    if (!raster_state) return skip;
    const Location raster_loc = create_info_loc.dot(Field::pRasterizationState);

    if ((raster_state->depthClampEnable == VK_TRUE) && !enabled_features.depthClamp &&
        !pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT)) {
        skip |= LogError("VUID-VkPipelineRasterizationStateCreateInfo-depthClampEnable-00782", device,
                         raster_loc.dot(Field::depthClampEnable), "is VK_TRUE, but the depthClamp feature was not enabled.");
    }

    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BIAS) && (raster_state->depthBiasClamp != 0.0) &&
        !enabled_features.depthBiasClamp) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-00754", device, raster_loc.dot(Field::depthBiasClamp),
                         "is %f, but the depthBiasClamp feature was not enabled", raster_state->depthBiasClamp);
    }

    // If rasterization is enabled...
    if (raster_state->rasterizerDiscardEnable == VK_FALSE) {
        // pMultisampleState can be null for graphics library
        const bool has_ms_state =
            !pipeline.IsGraphicsLibrary() ||
            ((pipeline.graphics_lib_type & (VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT |
                                            VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT)) != 0);
        if (has_ms_state) {
            const auto ms_state = pipeline.MultisampleState();
            if (ms_state && (ms_state->alphaToOneEnable == VK_TRUE) && !enabled_features.alphaToOne &&
                !pipeline.IsDynamic(CB_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT)) {
                skip |= LogError("VUID-VkPipelineMultisampleStateCreateInfo-alphaToOneEnable-00785", device,
                                 create_info_loc.dot(Field::pMultisampleState).dot(Field::alphaToOneEnable),
                                 "is VK_TRUE, but the alphaToOne feature was not enabled.");
            }
        }
    }

    if (auto provoking_vertex_state_ci =
            vku::FindStructInPNextChain<VkPipelineRasterizationProvokingVertexStateCreateInfoEXT>(raster_state->pNext)) {
        if (provoking_vertex_state_ci->provokingVertexMode == VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT &&
            !enabled_features.provokingVertexLast && !pipeline.IsDynamic(CB_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT)) {
            skip |= LogError(
                "VUID-VkPipelineRasterizationProvokingVertexStateCreateInfoEXT-provokingVertexMode-04883", device,
                raster_loc.pNext(Struct::VkPipelineRasterizationProvokingVertexStateCreateInfoEXT, Field::provokingVertexMode),
                "is VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT but the provokingVertexLast feature was not enabled.");
        }
    }

    if (const auto rasterization_state_stream_ci =
            vku::FindStructInPNextChain<VkPipelineRasterizationStateStreamCreateInfoEXT>(raster_state->pNext)) {
        skip |= ValidatePipelineRasterizationStateStreamCreateInfo(pipeline, *rasterization_state_stream_ci, raster_loc);
    }

    if (const auto rasterization_conservative_state_ci =
            vku::FindStructInPNextChain<VkPipelineRasterizationConservativeStateCreateInfoEXT>(raster_state->pNext)) {
        skip |=
            ValidatePipelineRasterizationConservativeStateCreateInfo(pipeline, *rasterization_conservative_state_ci, raster_loc);
    } else {
        if (pipeline.IsDynamic(CB_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT) &&
            !pipeline.IsDynamic(CB_DYNAMIC_STATE_EXTRA_PRIMITIVE_OVERESTIMATION_SIZE_EXT)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-09639", device, raster_loc.dot(Field::pNext),
                             "is missing VkPipelineRasterizationConservativeStateCreateInfoEXT which it needs because this "
                             "pipeline has VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT but not "
                             "VK_DYNAMIC_STATE_EXTRA_PRIMITIVE_OVERESTIMATION_SIZE_EXT.\n%s",
                             PrintPNextChain(Struct::VkPipelineRasterizationStateCreateInfo, raster_state->pNext).c_str());
        }
    }

    if (const auto* depth_bias_representation = vku::FindStructInPNextChain<VkDepthBiasRepresentationInfoEXT>(raster_state->pNext);
        depth_bias_representation != nullptr) {
        skip |= ValidateDepthBiasRepresentationInfo(raster_loc, LogObjectList(device), *depth_bias_representation);
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineRenderPassRasterization(const vvl::Pipeline& pipeline, const vvl::RenderPass& rp_state,
                                                                 const vku::safe_VkSubpassDescription2& subpass_desc,
                                                                 const Location& create_info_loc) const {
    bool skip = false;
    const auto raster_state = pipeline.RasterizationState();
    if (!raster_state || raster_state->rasterizerDiscardEnable) return skip;

    // If subpass uses a depth/stencil attachment, pDepthStencilState must be a pointer to a valid structure
    if (pipeline.fragment_shader_state) {
        if (subpass_desc.pDepthStencilAttachment && subpass_desc.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED) {
            const Location ds_loc = create_info_loc.dot(Field::pDepthStencilState);
            const auto ds_state = pipeline.DepthStencilState();
            if (!ds_state) {
                if (!pipeline.IsDepthStencilStateDynamic() || !IsExtEnabled(extensions.vk_ext_extended_dynamic_state3)) {
                    skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-09028", rp_state.Handle(), ds_loc,
                                     "is NULL when rasterization is enabled "
                                     "and subpass %" PRIu32 " uses a depth/stencil attachment.",
                                     pipeline.Subpass());
                }
            } else {
                const float minDepthBounds = ds_state->minDepthBounds;
                const float maxDepthBounds = ds_state->maxDepthBounds;
                if (ds_state->depthBoundsTestEnable == VK_TRUE && !pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE)) {
                    if (!enabled_features.depthBounds) {
                        skip |= LogError("VUID-VkPipelineDepthStencilStateCreateInfo-depthBoundsTestEnable-00598", device,
                                         ds_loc.dot(Field::depthBoundsTestEnable),
                                         "is VK_TRUE, but depthBounds feature was not enabled.");
                    }

                    if (!IsExtEnabled(extensions.vk_ext_depth_range_unrestricted) &&
                        !pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BOUNDS)) {
                        if (!(minDepthBounds >= 0.0) || !(minDepthBounds <= 1.0)) {
                            skip |= LogError(
                                "VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-02510", device, ds_loc.dot(Field::minDepthBounds),
                                "is %f, depthBoundsTestEnable is VK_TRUE, but VK_EXT_depth_range_unrestricted extension "
                                "is not enabled (and not using VK_DYNAMIC_STATE_DEPTH_BOUNDS).",
                                minDepthBounds);
                        }
                        if (!(maxDepthBounds >= 0.0) || !(maxDepthBounds <= 1.0)) {
                            skip |= LogError(
                                "VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-02510", device, ds_loc.dot(Field::minDepthBounds),
                                "is %f, depthBoundsTestEnable is VK_TRUE, but VK_EXT_depth_range_unrestricted extension "
                                "is not enabled (and not using VK_DYNAMIC_STATE_DEPTH_BOUNDS).",
                                maxDepthBounds);
                        }
                    }
                }
                if (ds_state->depthBoundsTestEnable == VK_TRUE || pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE)) {
                    if (minDepthBounds > maxDepthBounds && !pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BOUNDS)) {
                        skip |= LogError(
                            "VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-10913", device, ds_loc.dot(Field::minDepthBounds),
                            "(%f) is greater than maxDepthBounds (%f).%s", minDepthBounds, maxDepthBounds,
                            pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE)
                                ? " (Even if depthBoundsTestEnable is dynamic, it can never be enabled and therefore this "
                                  "is still invalid)"
                                : "");
                    }
                }
            }
        }
    }

    // If subpass uses color attachments, pColorBlendState must be valid pointer
    if (pipeline.fragment_output_state && !pipeline.fragment_output_state->color_blend_state &&
        !pipeline.IsColorBlendStateDynamic()) {
        for (uint32_t i = 0; i < subpass_desc.colorAttachmentCount; ++i) {
            if (subpass_desc.pColorAttachments[i].attachment != VK_ATTACHMENT_UNUSED) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-09030", rp_state.Handle(),
                                 create_info_loc.dot(Field::pColorBlendState),
                                 "is NULL when rasterization is enabled and "
                                 "pSubpasses[%" PRIu32 "].pColorAttachments[%" PRIu32 "].attachment (%" PRIu32
                                 ") is a color attachments.",
                                 pipeline.Subpass(), i, subpass_desc.pColorAttachments[i].attachment);
                break;  // only wnat one error, else becomes spam
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidateSampleLocationsInfo(const VkSampleLocationsInfoEXT& sample_location_info, const Location& loc) const {
    bool skip = false;
    const VkSampleCountFlagBits sample_count = sample_location_info.sampleLocationsPerPixel;
    const uint32_t sample_total_size = sample_location_info.sampleLocationGridSize.width *
                                       sample_location_info.sampleLocationGridSize.height * SampleCountSize(sample_count);
    if (sample_location_info.sampleLocationsCount != sample_total_size) {
        skip |= LogError("VUID-VkSampleLocationsInfoEXT-sampleLocationsCount-01527", device, loc.dot(Field::sampleLocationsCount),
                         "(%" PRIu32
                         ") must equal grid width * grid height * pixel "
                         "sample rate which currently is (%" PRIu32 " * %" PRIu32 " * %" PRIu32 ").",
                         sample_location_info.sampleLocationsCount, sample_location_info.sampleLocationGridSize.width,
                         sample_location_info.sampleLocationGridSize.height, SampleCountSize(sample_count));
    }
    if ((phys_dev_ext_props.sample_locations_props.sampleLocationSampleCounts & sample_count) == 0) {
        skip |=
            LogError("VUID-VkSampleLocationsInfoEXT-sampleLocationsPerPixel-01526", device, loc.dot(Field::sampleLocationsPerPixel),
                     "is %s, but VkPhysicalDeviceSampleLocationsPropertiesEXT::sampleLocationSampleCounts is %s.",
                     string_VkSampleCountFlagBits(sample_count),
                     string_VkSampleCountFlags(phys_dev_ext_props.sample_locations_props.sampleLocationSampleCounts).c_str());
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineMultisampleState(const vvl::Pipeline& pipeline, const vvl::RenderPass& rp_state,
                                                          const vku::safe_VkSubpassDescription2& subpass_desc,
                                                          const Location& create_info_loc) const {
    bool skip = false;
    const auto* multisample_state = pipeline.MultisampleState();
    if (!multisample_state) return skip;
    const Location ms_loc = create_info_loc.dot(Field::pMultisampleState);

    auto accum_color_samples = [&subpass_desc, &rp_state](uint32_t& samples) {
        for (uint32_t i = 0; i < subpass_desc.colorAttachmentCount; i++) {
            const auto attachment = subpass_desc.pColorAttachments[i].attachment;
            if (attachment != VK_ATTACHMENT_UNUSED) {
                samples |= static_cast<uint32_t>(rp_state.create_info.pAttachments[attachment].samples);
            }
        }
    };

    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT)) {
        const uint32_t raster_samples = SampleCountSize(multisample_state->rasterizationSamples);
        if (!IsMixSamplingSupported()) {
            uint32_t subpass_num_samples = 0;

            accum_color_samples(subpass_num_samples);

            if (subpass_desc.pDepthStencilAttachment && subpass_desc.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED) {
                const auto attachment = subpass_desc.pDepthStencilAttachment->attachment;
                subpass_num_samples |= static_cast<uint32_t>(rp_state.create_info.pAttachments[attachment].samples);
            }

            // subpass_num_samples is 0 when the subpass has no attachments or if all attachments are VK_ATTACHMENT_UNUSED.
            // Only validate the value of subpass_num_samples if the subpass has attachments that are not VK_ATTACHMENT_UNUSED.
            if (subpass_num_samples && (!IsPowerOfTwo(subpass_num_samples) || (subpass_num_samples != raster_samples))) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-multisampledRenderToSingleSampled-06853", device,
                                 ms_loc.dot(Field::rasterizationSamples),
                                 "(%" PRIu32
                                 ") does not match the number of samples of the RenderPass color and/or depth attachment (%" PRIu32
                                 ").",
                                 raster_samples, subpass_num_samples);
            }
        }

        if (IsExtEnabled(extensions.vk_amd_mixed_attachment_samples)) {
            VkSampleCountFlagBits max_sample_count = static_cast<VkSampleCountFlagBits>(0);
            for (uint32_t i = 0; i < subpass_desc.colorAttachmentCount; ++i) {
                if (subpass_desc.pColorAttachments[i].attachment != VK_ATTACHMENT_UNUSED) {
                    max_sample_count = std::max(
                        max_sample_count, rp_state.create_info.pAttachments[subpass_desc.pColorAttachments[i].attachment].samples);
                }
            }
            if (subpass_desc.pDepthStencilAttachment && subpass_desc.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED) {
                max_sample_count = std::max(
                    max_sample_count, rp_state.create_info.pAttachments[subpass_desc.pDepthStencilAttachment->attachment].samples);
            }
            if (!pipeline.RasterizationDisabled() && (max_sample_count != static_cast<VkSampleCountFlagBits>(0)) &&
                (multisample_state->rasterizationSamples != max_sample_count)) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-subpass-01505", device, ms_loc.dot(Field::rasterizationSamples),
                                 "(%s) is different from the max attachment samples (%s) used in pSubpasses[%" PRIu32 "].",
                                 string_VkSampleCountFlagBits(multisample_state->rasterizationSamples),
                                 string_VkSampleCountFlagBits(max_sample_count), pipeline.Subpass());
            }
        }

        if (IsExtEnabled(extensions.vk_nv_framebuffer_mixed_samples)) {
            uint32_t subpass_color_samples = 0;

            accum_color_samples(subpass_color_samples);

            if (subpass_desc.pDepthStencilAttachment && subpass_desc.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED) {
                const auto attachment = subpass_desc.pDepthStencilAttachment->attachment;
                const uint32_t subpass_depth_samples = static_cast<uint32_t>(rp_state.create_info.pAttachments[attachment].samples);
                const auto ds_state = pipeline.DepthStencilState();
                if (ds_state) {
                    const bool ds_test_enabled = (ds_state->depthTestEnable == VK_TRUE) ||
                                                 (ds_state->depthBoundsTestEnable == VK_TRUE) ||
                                                 (ds_state->stencilTestEnable == VK_TRUE);

                    if (ds_test_enabled && (!IsPowerOfTwo(subpass_depth_samples) || (raster_samples != subpass_depth_samples))) {
                        skip |= LogError(
                            "VUID-VkGraphicsPipelineCreateInfo-subpass-01411", device, ms_loc.dot(Field::rasterizationSamples),
                            "(%" PRIu32 ") does not match the number of samples of the RenderPass depth attachment (%" PRIu32 ").",
                            raster_samples, subpass_depth_samples);
                    }
                }
            }

            if (IsPowerOfTwo(subpass_color_samples)) {
                if (raster_samples < subpass_color_samples) {
                    skip |= LogError(
                        "VUID-VkGraphicsPipelineCreateInfo-subpass-01412", device, ms_loc.dot(Field::rasterizationSamples),
                        "(%" PRIu32
                        ") "
                        "is not greater than or equal to the number of samples of the RenderPass color attachment (%" PRIu32 ").",
                        raster_samples, subpass_color_samples);
                }

                if (multisample_state) {
                    bool truncate_mode = false;
                    if (const auto* coverage_reduction_mode_state =
                            vku::FindStructInPNextChain<VkPipelineCoverageReductionStateCreateInfoNV>(multisample_state->pNext)) {
                        truncate_mode =
                            enabled_features.coverageReductionMode &&
                            coverage_reduction_mode_state->coverageReductionMode == VK_COVERAGE_REDUCTION_MODE_TRUNCATE_NV;
                    }
                    if (raster_samples > subpass_color_samples && multisample_state->sampleShadingEnable && !truncate_mode) {
                        skip |=
                            LogError("VUID-VkPipelineMultisampleStateCreateInfo-rasterizationSamples-01415", device,
                                     ms_loc.dot(Field::rasterizationSamples),
                                     "(%" PRIu32 ") is greater than the number of samples of the subpass color attachment (%" PRIu32
                                     ") and sampleShadingEnable is VK_TRUE.",
                                     raster_samples, subpass_color_samples);
                    }

                    const auto* coverage_modulation_state =
                        vku::FindStructInPNextChain<VkPipelineCoverageModulationStateCreateInfoNV>(multisample_state->pNext);

                    if (coverage_modulation_state && (coverage_modulation_state->coverageModulationTableEnable == VK_TRUE)) {
                        if (coverage_modulation_state->coverageModulationTableCount != (raster_samples / subpass_color_samples)) {
                            skip |= LogError(
                                "VUID-VkPipelineCoverageModulationStateCreateInfoNV-coverageModulationTableEnable-01405", device,
                                ms_loc.pNext(Struct::VkPipelineCoverageModulationStateCreateInfoNV,
                                             Field::coverageModulationTableCount),
                                "is %" PRIu32 ".", coverage_modulation_state->coverageModulationTableCount);
                        }
                    }
                }
            }
        }

        if (IsExtEnabled(extensions.vk_nv_coverage_reduction_mode)) {
            uint32_t subpass_color_samples = 0;
            uint32_t subpass_depth_samples = 0;

            accum_color_samples(subpass_color_samples);

            if (subpass_desc.pDepthStencilAttachment && subpass_desc.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED) {
                const auto attachment = subpass_desc.pDepthStencilAttachment->attachment;
                subpass_depth_samples = static_cast<uint32_t>(rp_state.create_info.pAttachments[attachment].samples);
            }

            if (multisample_state && IsPowerOfTwo(subpass_color_samples) &&
                (subpass_depth_samples == 0 || IsPowerOfTwo(subpass_depth_samples))) {
                const auto* coverage_reduction_state =
                    vku::FindStructInPNextChain<VkPipelineCoverageReductionStateCreateInfoNV>(multisample_state->pNext);

                if (coverage_reduction_state) {
                    const VkCoverageReductionModeNV coverage_reduction_mode = coverage_reduction_state->coverageReductionMode;
                    uint32_t combination_count = 0;
                    std::vector<VkFramebufferMixedSamplesCombinationNV> combinations;
                    DispatchGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV(physical_device, &combination_count,
                                                                                            nullptr);
                    combinations.resize(combination_count);
                    DispatchGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV(physical_device, &combination_count,
                                                                                            &combinations[0]);

                    bool combination_found = false;
                    for (const auto& combination : combinations) {
                        if (coverage_reduction_mode == combination.coverageReductionMode &&
                            raster_samples == combination.rasterizationSamples &&
                            subpass_depth_samples == combination.depthStencilSamples &&
                            subpass_color_samples == combination.colorSamples) {
                            combination_found = true;
                            break;
                        }
                    }

                    if (!combination_found) {
                        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-coverageReductionMode-02722", device, create_info_loc,
                                         "specifies a combination of coverage "
                                         "reduction mode (%s), pMultisampleState->rasterizationSamples (%" PRIu32
                                         "), sample counts for "
                                         "the subpass color and depth/stencil attachments is not a valid combination returned by "
                                         "vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV.",
                                         string_VkCoverageReductionModeNV(coverage_reduction_mode), raster_samples);
                    }
                }
            }
        }
        const auto msrtss_info = vku::FindStructInPNextChain<VkMultisampledRenderToSingleSampledInfoEXT>(subpass_desc.pNext);
        if (msrtss_info && msrtss_info->multisampledRenderToSingleSampledEnable &&
            (msrtss_info->rasterizationSamples != multisample_state->rasterizationSamples)) {
            skip |=
                LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-06854", rp_state.Handle(),
                         create_info_loc.dot(Field::pSubpasses, pipeline.Subpass())
                             .pNext(Struct::VkMultisampledRenderToSingleSampledInfoEXT, Field::rasterizationSamples),
                         "(%" PRIu32 ") is not equal to %s (%" PRIu32 ") and multisampledRenderToSingleSampledEnable is VK_TRUE.",
                         msrtss_info->rasterizationSamples, ms_loc.dot(Field::rasterizationSamples).Fields().c_str(),
                         multisample_state->rasterizationSamples);
        }

        if (rp_state.UsesNoAttachment(pipeline.Subpass())) {
            if ((multisample_state->rasterizationSamples & phys_dev_props.limits.framebufferNoAttachmentsSampleCounts) == 0) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-subpass-00758", rp_state.Handle(),
                                 ms_loc.dot(Field::rasterizationSamples),
                                 "(%s) is not in "
                                 "framebufferNoAttachmentsSampleCounts (%s) but attempting to use a zero-attachment subpass.",
                                 string_VkSampleCountFlagBits(multisample_state->rasterizationSamples),
                                 string_VkSampleCountFlags(phys_dev_props.limits.framebufferNoAttachmentsSampleCounts).c_str());
            }
        }
    }

    // VK_NV_fragment_coverage_to_color
    const auto coverage_to_color_state = vku::FindStructInPNextChain<VkPipelineCoverageToColorStateCreateInfoNV>(multisample_state);
    if (coverage_to_color_state && coverage_to_color_state->coverageToColorEnable == VK_TRUE) {
        bool attachment_is_valid = false;
        std::string error_detail;

        if (coverage_to_color_state->coverageToColorLocation < subpass_desc.colorAttachmentCount) {
            const auto& color_attachment_ref = subpass_desc.pColorAttachments[coverage_to_color_state->coverageToColorLocation];
            if (color_attachment_ref.attachment != VK_ATTACHMENT_UNUSED) {
                const auto& color_attachment = rp_state.create_info.pAttachments[color_attachment_ref.attachment];

                switch (color_attachment.format) {
                    case VK_FORMAT_R8_UINT:
                    case VK_FORMAT_R8_SINT:
                    case VK_FORMAT_R16_UINT:
                    case VK_FORMAT_R16_SINT:
                    case VK_FORMAT_R32_UINT:
                    case VK_FORMAT_R32_SINT:
                        attachment_is_valid = true;
                        break;
                    default:
                        std::ostringstream str;
                        str << "references an attachment with an invalid format (" << string_VkFormat(color_attachment.format)
                            << ").";
                        error_detail = str.str();
                        break;
                }
            } else {
                std::ostringstream str;
                str << "references an invalid attachment. The subpass pColorAttachments["
                    << coverage_to_color_state->coverageToColorLocation << "].attachment has the value VK_ATTACHMENT_UNUSED.";
                error_detail = str.str();
            }
        } else {
            std::ostringstream str;
            str << "references an non-existing attachment since the subpass colorAttachmentCount is "
                << subpass_desc.colorAttachmentCount << ".";
            error_detail = str.str();
        }

        if (!attachment_is_valid) {
            skip |= LogError("VUID-VkPipelineCoverageToColorStateCreateInfoNV-coverageToColorEnable-01404", device,
                             ms_loc.pNext(Struct::VkPipelineCoverageToColorStateCreateInfoNV, Field::coverageToColorLocation),
                             "is %" PRIu32 ", but %s", coverage_to_color_state->coverageToColorLocation, error_detail.c_str());
        }
    }

    // VK_EXT_sample_locations
    const auto* sample_location_state =
        vku::FindStructInPNextChain<VkPipelineSampleLocationsStateCreateInfoEXT>(multisample_state->pNext);
    if (sample_location_state != nullptr) {
        if ((sample_location_state->sampleLocationsEnable == VK_TRUE) &&
            !pipeline.IsDynamic(CB_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT) &&
            !pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT)) {
            const VkSampleLocationsInfoEXT sample_location_info = sample_location_state->sampleLocationsInfo;
            const Location sample_info_loc =
                ms_loc.pNext(Struct::VkPipelineSampleLocationsStateCreateInfoEXT, Field::sampleLocationsInfo);
            skip |= ValidateSampleLocationsInfo(sample_location_info, sample_info_loc.dot(Field::sampleLocationsInfo));
            const VkExtent2D grid_size = sample_location_info.sampleLocationGridSize;

            VkMultisamplePropertiesEXT multisample_prop = vku::InitStructHelper();
            DispatchGetPhysicalDeviceMultisamplePropertiesEXT(physical_device, multisample_state->rasterizationSamples,
                                                              &multisample_prop);
            const VkExtent2D max_grid_size = multisample_prop.maxSampleLocationGridSize;

            // Note order or "divide" in "sampleLocationsInfo must evenly divide VkMultisamplePropertiesEXT"
            if (!IsIntegerMultipleOf(max_grid_size.width, grid_size.width)) {
                skip |=
                    LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07610", device,
                             sample_info_loc.dot(Field::sampleLocationGridSize).dot(Field::width),
                             "(%" PRIu32
                             ") is not evenly divided by VkMultisamplePropertiesEXT::sampleLocationGridSize.width (%" PRIu32 ").",
                             grid_size.width, max_grid_size.width);
            }
            if (!IsIntegerMultipleOf(max_grid_size.height, grid_size.height)) {
                skip |=
                    LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07611", device,
                             sample_info_loc.dot(Field::sampleLocationGridSize).dot(Field::height),
                             "(%" PRIu32
                             ") is not evenly divided by VkMultisamplePropertiesEXT::sampleLocationGridSize.height (%" PRIu32 ").",
                             grid_size.height, max_grid_size.height);
            }
            if (sample_location_info.sampleLocationsPerPixel != multisample_state->rasterizationSamples) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07612", device,
                                 sample_info_loc.dot(Field::sampleLocationsPerPixel), "(%s) is different from %s (%s).",
                                 string_VkSampleCountFlagBits(sample_location_info.sampleLocationsPerPixel),
                                 ms_loc.dot(Field::rasterizationSamples).Fields().c_str(),
                                 string_VkSampleCountFlagBits(multisample_state->rasterizationSamples));
            }
        }
    }

    if (enabled_features.customResolve || IsExtEnabled(extensions.vk_qcom_render_pass_shader_resolve)) {
        uint32_t subpass_input_attachment_samples = 0;

        for (uint32_t i = 0; i < subpass_desc.inputAttachmentCount; i++) {
            const auto attachment = subpass_desc.pInputAttachments[i].attachment;
            if (attachment != VK_ATTACHMENT_UNUSED) {
                subpass_input_attachment_samples |= static_cast<uint32_t>(rp_state.create_info.pAttachments[attachment].samples);
            }
        }

        if ((subpass_desc.flags & VK_SUBPASS_DESCRIPTION_FRAGMENT_REGION_BIT_EXT) != 0) {
            const uint32_t raster_samples = SampleCountSize(multisample_state->rasterizationSamples);
            if ((raster_samples != subpass_input_attachment_samples) &&
                !pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT)) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-rasterizationSamples-04899", device,
                                 ms_loc.dot(Field::rasterizationSamples),
                                 "%s is different then pSubpasses[%" PRIu32 "] input attachment samples (%" PRIu32
                                 ") but flags include VK_SUBPASS_DESCRIPTION_FRAGMENT_REGION_BIT_EXT.",
                                 string_VkSampleCountFlagBits(multisample_state->rasterizationSamples), pipeline.Subpass(),
                                 subpass_input_attachment_samples);
            }
            if (multisample_state->sampleShadingEnable == VK_TRUE) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-sampleShadingEnable-04900", device,
                                 ms_loc.dot(Field::sampleShadingEnable),
                                 "is VK_TRUE, but pSubpasses[%" PRIu32
                                 "] includes "
                                 "VK_SUBPASS_DESCRIPTION_FRAGMENT_REGION_BIT_EXT.",
                                 pipeline.Subpass());
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineNullState(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;

    const bool null_rp = pipeline.IsRenderPassNull();
    if (null_rp) {
        if (!pipeline.DepthStencilState()) {
            if (pipeline.fragment_shader_state && !pipeline.fragment_output_state) {
                if (!pipeline.IsDepthStencilStateDynamic() || !IsExtEnabled(extensions.vk_ext_extended_dynamic_state3)) {
                    skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-09035", device,
                                     create_info_loc.dot(Field::pDepthStencilState), "is NULL.");
                }
            }
        }
    }

    if (IsExtEnabled(extensions.vk_ext_graphics_pipeline_library)) {
        if (pipeline.OwnsLibState(pipeline.fragment_output_state) && !pipeline.MultisampleState()) {
            // if VK_KHR_dynamic_rendering is not enabled, can be null renderpass if using GPL
            if (!null_rp) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderpass-06631", device,
                                 create_info_loc.dot(Field::pMultisampleState),
                                 "is NULL, but pipeline is being created with fragment shader that uses samples.");
            }
        }
    }

    const auto& pipeline_ci = pipeline.GraphicsCreateInfo();
    if (!pipeline_ci.pMultisampleState && pipeline.OwnsLibState(pipeline.fragment_output_state)) {
        const bool dynamic_alpha_to_one =
            pipeline.IsDynamic(CB_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT) || !enabled_features.alphaToOne;
        if (!IsExtEnabled(extensions.vk_ext_extended_dynamic_state3) ||
            !pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT) ||
            !pipeline.IsDynamic(CB_DYNAMIC_STATE_SAMPLE_MASK_EXT) ||
            !pipeline.IsDynamic(CB_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT) || !dynamic_alpha_to_one) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pMultisampleState-09026", device,
                             create_info_loc.dot(Field::pMultisampleState),
                             "is NULL."
                             "\nIf the following are all set, it can be NULL"
                             "\n  Enable VK_EXT_extended_dynamic_state3 (%senabled)"
                             "\n  Use VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT (%s)"
                             "\n  Use VK_DYNAMIC_STATE_SAMPLE_MASK_EXT (%s)"
                             "\n  Use VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT (%s)"
                             "\n  Use VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT (%s) or enable alphaToOne feature (%s)\n",
                             IsExtEnabled(extensions.vk_ext_extended_dynamic_state3) ? "" : "not ",
                             pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT) ? "set" : "not set",
                             pipeline.IsDynamic(CB_DYNAMIC_STATE_SAMPLE_MASK_EXT) ? "set" : "not set",
                             pipeline.IsDynamic(CB_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT) ? "set" : "not set",
                             pipeline.IsDynamic(CB_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT) ? "set" : "not set",
                             enabled_features.alphaToOne ? "VK_TRUE" : "VK_FALSE");
        }
    }

    if (!pipeline.RasterizationState()) {
        if (!pipeline_ci.pRasterizationState && pipeline.OwnsLibState(pipeline.pre_raster_state)) {
            if (!IsExtEnabled(extensions.vk_ext_extended_dynamic_state3) ||
                !pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT) ||
                !pipeline.IsDynamic(CB_DYNAMIC_STATE_POLYGON_MODE_EXT) ||
                !pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE) ||
                !pipeline.IsDynamic(CB_DYNAMIC_STATE_CULL_MODE) || !pipeline.IsDynamic(CB_DYNAMIC_STATE_FRONT_FACE) ||
                !pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BIAS_ENABLE) || !pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BIAS) ||
                !pipeline.IsDynamic(CB_DYNAMIC_STATE_LINE_WIDTH)) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pRasterizationState-06601", device,
                                 create_info_loc.dot(Field::pRasterizationState),
                                 "is NULL."
                                 "\nIf the following are all set, it can be NULL"
                                 "\n  Enable VK_EXT_extended_dynamic_state3 (%senabled)"
                                 "\n  Use VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT (%s)"
                                 "\n  Use VK_DYNAMIC_STATE_POLYGON_MODE_EXT (%s)"
                                 "\n  Use VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE (%s)"
                                 "\n  Use VK_DYNAMIC_STATE_CULL_MODE (%s)"
                                 "\n  Use VK_DYNAMIC_STATE_FRONT_FACE (%s)"
                                 "\n  Use VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE (%s)"
                                 "\n  Use VK_DYNAMIC_STATE_DEPTH_BIAS (%s)"
                                 "\n  Use VK_DYNAMIC_STATE_LINE_WIDTH (%s)\n",
                                 IsExtEnabled(extensions.vk_ext_extended_dynamic_state3) ? "" : "not ",
                                 pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT) ? "set" : "not set",
                                 pipeline.IsDynamic(CB_DYNAMIC_STATE_POLYGON_MODE_EXT) ? "set" : "not set",
                                 pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE) ? "set" : "not set",
                                 pipeline.IsDynamic(CB_DYNAMIC_STATE_CULL_MODE) ? "set" : "not set",
                                 pipeline.IsDynamic(CB_DYNAMIC_STATE_FRONT_FACE) ? "set" : "not set",
                                 pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BIAS_ENABLE) ? "set" : "not set",
                                 pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BIAS) ? "set" : "not set",
                                 pipeline.IsDynamic(CB_DYNAMIC_STATE_LINE_WIDTH) ? "set" : "not set");
            }
        }
    }

    return skip;
}

// VK_EXT_rasterization_order_attachment_access
bool CoreChecks::ValidateGraphicsPipelineRasterizationOrderAttachmentAccess(const vvl::Pipeline& pipeline,
                                                                            const vku::safe_VkSubpassDescription2* subpass_desc,
                                                                            const Location& create_info_loc) const {
    bool skip = false;
    const auto rp_state = pipeline.RenderPassState();
    const bool null_rp = pipeline.IsRenderPassNull();
    if (!null_rp && !rp_state) return skip;  // invalid render pass

    if (const auto ds_state = pipeline.DepthStencilState()) {
        const Location ds_loc = create_info_loc.dot(Field::pDepthStencilState);
        const bool raster_order_attach_depth =
            ds_state->flags & VK_PIPELINE_DEPTH_STENCIL_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_DEPTH_ACCESS_BIT_EXT;
        const bool raster_order_attach_stencil =
            ds_state->flags & VK_PIPELINE_DEPTH_STENCIL_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_STENCIL_ACCESS_BIT_EXT;

        if (null_rp) {
            if (!enabled_features.dynamicRenderingLocalRead && (raster_order_attach_depth || raster_order_attach_stencil)) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-None-09526", device, ds_loc.dot(Field::flags),
                                 "is %s but renderPass is VK_NULL_HANDLE.",
                                 string_VkPipelineDepthStencilStateCreateFlags(ds_state->flags).c_str());
            }
        } else if (!rp_state->UsesDynamicRendering()) {
            if (raster_order_attach_depth) {
                // Can't validate these in stateless as possible to have a pDepthStencilState pointer with garbage
                if (!enabled_features.rasterizationOrderDepthAttachmentAccess) {
                    skip |= LogError("VUID-VkPipelineDepthStencilStateCreateInfo-rasterizationOrderDepthAttachmentAccess-06463",
                                     device, ds_loc.dot(Field::flags),
                                     "is (%s) but rasterizationOrderDepthAttachmentAccess feature was not enabled",
                                     string_VkPipelineDepthStencilStateCreateFlags(ds_state->flags).c_str());
                }

                if (subpass_desc &&
                    (subpass_desc->flags & VK_SUBPASS_DESCRIPTION_RASTERIZATION_ORDER_ATTACHMENT_DEPTH_ACCESS_BIT_EXT) == 0) {
                    skip |=
                        LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-09528", rp_state->Handle(), ds_loc.dot(Field::flags),
                                 "is (%s) but VkRenderPassCreateInfo::VkSubpassDescription::flags == %s",
                                 string_VkPipelineDepthStencilStateCreateFlags(ds_state->flags).c_str(),
                                 string_VkSubpassDescriptionFlags(subpass_desc->flags).c_str());
                }
            }
            if (raster_order_attach_stencil) {
                if (!enabled_features.rasterizationOrderStencilAttachmentAccess) {
                    skip |= LogError("VUID-VkPipelineDepthStencilStateCreateInfo-rasterizationOrderStencilAttachmentAccess-06464",
                                     device, ds_loc.dot(Field::flags),
                                     "is (%s) but rasterizationOrderStencilAttachmentAccess feature was not enabled",
                                     string_VkPipelineDepthStencilStateCreateFlags(ds_state->flags).c_str());
                }

                if (subpass_desc &&
                    (subpass_desc->flags & VK_SUBPASS_DESCRIPTION_RASTERIZATION_ORDER_ATTACHMENT_STENCIL_ACCESS_BIT_EXT) == 0) {
                    skip |=
                        LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-09529", rp_state->Handle(), ds_loc.dot(Field::flags),
                                 "is (%s) but VkRenderPassCreateInfo::VkSubpassDescription::flags == %s",
                                 string_VkPipelineDepthStencilStateCreateFlags(ds_state->flags).c_str(),
                                 string_VkSubpassDescriptionFlags(subpass_desc->flags).c_str());
                }
            }
        }
    }

    if (const auto color_blend_state = pipeline.ColorBlendState()) {
        const Location color_loc = create_info_loc.dot(Field::pColorBlendState);
        if (color_blend_state->flags & VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_EXT) {
            if (!enabled_features.rasterizationOrderColorAttachmentAccess) {
                skip |=
                    LogError("VUID-VkPipelineColorBlendStateCreateInfo-rasterizationOrderColorAttachmentAccess-06465", device,
                             color_loc.dot(Field::flags), "(%s) but rasterizationOrderColorAttachmentAccess feature is not enabled",
                             string_VkPipelineColorBlendStateCreateFlags(color_blend_state->flags).c_str());
            }

            if (!enabled_features.dynamicRenderingLocalRead && null_rp) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-flags-06482", device, color_loc.dot(Field::flags),
                                 "includes "
                                 "VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_EXT, but "
                                 "renderpass is VK_NULL_HANDLE.");
            }

            if (!null_rp && subpass_desc &&
                (subpass_desc->flags & VK_SUBPASS_DESCRIPTION_RASTERIZATION_ORDER_ATTACHMENT_COLOR_ACCESS_BIT_EXT) == 0) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-09527", rp_state->Handle(),
                                 color_loc.dot(Field::flags), "(%s) but VkRenderPassCreateInfo::VkSubpassDescription::flags == %s",
                                 string_VkPipelineColorBlendStateCreateFlags(color_blend_state->flags).c_str(),
                                 string_VkSubpassDescriptionFlags(subpass_desc->flags).c_str());
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineDynamicState(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    auto get_state_index = [&pipeline](const VkDynamicState state) {
        const auto dynamic_info = pipeline.GraphicsCreateInfo().pDynamicState;
        for (uint32_t i = 0; i < dynamic_info->dynamicStateCount; i++) {
            if (dynamic_info->pDynamicStates[i] == state) {
                return i;
            }
        }
        assert(false);
        return dynamic_info->dynamicStateCount;
    };

    bool skip = false;
    if (pipeline.create_info_shaders & VK_SHADER_STAGE_MESH_BIT_EXT) {
        if (pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07065", device,
                             create_info_loc.dot(Field::pDynamicState)
                                 .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY)),
                             "is VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY, but the pipeline contains a mesh shader.");
        } else if (pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07065", device,
                             create_info_loc.dot(Field::pDynamicState)
                                 .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE)),
                             "is VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE, but the pipeline contains a mesh shader.");
        }

        if (pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07066", device,
                             create_info_loc.dot(Field::pDynamicState)
                                 .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE)),
                             "is VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE, but the pipeline contains a mesh shader.");
        } else if (pipeline.IsDynamic(CB_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07066", device,
                             create_info_loc.dot(Field::pDynamicState)
                                 .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT)),
                             "is VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT, but the pipeline contains a mesh shader.");
        }

        if (pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07067", device,
                             create_info_loc.dot(Field::pDynamicState)
                                 .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT)),
                             "is VK_DYNAMIC_STATE_VERTEX_INPUT_EXT, but the pipeline contains a mesh shader.");
        }
    }

    if (api_version < VK_API_VERSION_1_3 && !enabled_features.extendedDynamicState &&
        (pipeline.IsDynamic(CB_DYNAMIC_STATE_CULL_MODE) || pipeline.IsDynamic(CB_DYNAMIC_STATE_FRONT_FACE) ||
         pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY) || pipeline.IsDynamic(CB_DYNAMIC_STATE_VIEWPORT_WITH_COUNT) ||
         pipeline.IsDynamic(CB_DYNAMIC_STATE_SCISSOR_WITH_COUNT) ||
         pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE) ||
         pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_TEST_ENABLE) || pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_WRITE_ENABLE) ||
         pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_COMPARE_OP) || pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE) ||
         pipeline.IsDynamic(CB_DYNAMIC_STATE_STENCIL_TEST_ENABLE) || pipeline.IsDynamic(CB_DYNAMIC_STATE_STENCIL_OP))) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-03378", device, create_info_loc.dot(Field::pDynamicState),
            "contains dynamic states from VK_EXT_extended_dynamic_state, but the extendedDynamicState feature was not enabled.");
    }

    if (api_version < VK_API_VERSION_1_3 && !enabled_features.extendedDynamicState2) {
        if (pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE)) {
            skip |=
                LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-04868", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE)),
                         "is VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE, but the extendedDynamicState2 feature was not enabled.");
        } else if (pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_BIAS_ENABLE)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-04868", device,
                             create_info_loc.dot(Field::pDynamicState)
                                 .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE)),
                             "is VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE, but the extendedDynamicState2 feature was not enabled.");
        } else if (pipeline.IsDynamic(CB_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE)) {
            skip |=
                LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-04868", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE)),
                         "is VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE, but the extendedDynamicState2 feature was not enabled.");
        }
    }

    if (!enabled_features.extendedDynamicState2LogicOp && pipeline.IsDynamic(CB_DYNAMIC_STATE_LOGIC_OP_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-04869", device,
            create_info_loc.dot(Field::pDynamicState).dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_LOGIC_OP_EXT)),
            "is VK_DYNAMIC_STATE_LOGIC_OP_EXT, but the extendedDynamicState2LogicOp feature was not enabled.");
    }

    if (!enabled_features.extendedDynamicState2PatchControlPoints &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-04870", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT)),
                         "is VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT, but the extendedDynamicState2PatchControlPoints feature "
                         "was not enabled.");
    }

    if (!enabled_features.extendedDynamicState3TessellationDomainOrigin &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_TESSELLATION_DOMAIN_ORIGIN_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3TessellationDomainOrigin-07370", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_TESSELLATION_DOMAIN_ORIGIN_EXT)),
                         "is VK_DYNAMIC_STATE_TESSELLATION_DOMAIN_ORIGIN_EXT, but the "
                         "extendedDynamicState3TessellationDomainOrigin feature was not enabled.");
    }

    if (!enabled_features.extendedDynamicState3DepthClampEnable && pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3DepthClampEnable-07371", device,
            create_info_loc.dot(Field::pDynamicState)
                .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT)),
            "is VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT, but the extendedDynamicState3DepthClampEnable feature was not enabled.");
    }

    if (!enabled_features.extendedDynamicState3PolygonMode && pipeline.IsDynamic(CB_DYNAMIC_STATE_POLYGON_MODE_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3PolygonMode-07372", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_POLYGON_MODE_EXT)),
                         "is VK_DYNAMIC_STATE_POLYGON_MODE_EXT, but the extendedDynamicState3PolygonMode feature was not enabled.");
    }

    if (!enabled_features.extendedDynamicState3RasterizationSamples &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3RasterizationSamples-07373", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT)),
                         "is VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT but the extendedDynamicState3RasterizationSamples feature "
                         "is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3SampleMask && pipeline.IsDynamic(CB_DYNAMIC_STATE_SAMPLE_MASK_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3SampleMask-07374", device,
            create_info_loc.dot(Field::pDynamicState).dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_SAMPLE_MASK_EXT)),
            "is VK_DYNAMIC_STATE_SAMPLE_MASK_EXT but the extendedDynamicState3SampleMask feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3AlphaToCoverageEnable &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3AlphaToCoverageEnable-07375", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT)),
                         "is VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT but the extendedDynamicState3AlphaToCoverageEnable "
                         "feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3AlphaToOneEnable && pipeline.IsDynamic(CB_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3AlphaToOneEnable-07376", device,
            create_info_loc.dot(Field::pDynamicState)
                .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT)),
            "is VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT but the extendedDynamicState3AlphaToOneEnable feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3LogicOpEnable && pipeline.IsDynamic(CB_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT)) {
        skip |=
            LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3LogicOpEnable-07377", device,
                     create_info_loc.dot(Field::pDynamicState)
                         .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT)),
                     "is VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT but the extendedDynamicState3LogicOpEnable feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ColorBlendEnable && pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ColorBlendEnable-07378", device,
            create_info_loc.dot(Field::pDynamicState)
                .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT)),
            "is VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT but the extendedDynamicState3ColorBlendEnable feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ColorBlendEquation &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ColorBlendEquation-07379", device,
            create_info_loc.dot(Field::pDynamicState)
                .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT)),
            "is VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT but the extendedDynamicState3ColorBlendEquation feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ColorWriteMask && pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ColorWriteMask-07380", device,
            create_info_loc.dot(Field::pDynamicState)
                .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT)),
            "is VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT but the extendedDynamicState3ColorWriteMask feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3RasterizationStream &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_STREAM_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3RasterizationStream-07381", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_RASTERIZATION_STREAM_EXT)),
                         "is VK_DYNAMIC_STATE_RASTERIZATION_STREAM_EXT but the extendedDynamicState3RasterizationStream feature is "
                         "not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ConservativeRasterizationMode &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ConservativeRasterizationMode-07382", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT)),
                         "is VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT but the "
                         "extendedDynamicState3ConservativeRasterizationMode feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ExtraPrimitiveOverestimationSize &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_EXTRA_PRIMITIVE_OVERESTIMATION_SIZE_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ExtraPrimitiveOverestimationSize-07383", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_EXTRA_PRIMITIVE_OVERESTIMATION_SIZE_EXT)),
                         "is VK_DYNAMIC_STATE_EXTRA_PRIMITIVE_OVERESTIMATION_SIZE_EXT but the "
                         "extendedDynamicState3ExtraPrimitiveOverestimationSize feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3DepthClipEnable && pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3DepthClipEnable-07384", device,
            create_info_loc.dot(Field::pDynamicState)
                .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT)),
            "is VK_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT but the extendedDynamicState3DepthClipEnable feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3SampleLocationsEnable &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3SampleLocationsEnable-07385", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT)),
                         "is VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT but the extendedDynamicState3SampleLocationsEnable "
                         "feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ColorBlendAdvanced &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ColorBlendAdvanced-07386", device,
            create_info_loc.dot(Field::pDynamicState)
                .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT)),
            "is VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT but the extendedDynamicState3ColorBlendAdvanced feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ProvokingVertexMode &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ProvokingVertexMode-07387", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT)),
                         "is VK_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT but the extendedDynamicState3ProvokingVertexMode feature "
                         "is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3LineRasterizationMode &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3LineRasterizationMode-07388", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT)),
                         "is VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT but the extendedDynamicState3LineRasterizationMode "
                         "feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3LineStippleEnable && pipeline.IsDynamic(CB_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3LineStippleEnable-07389", device,
            create_info_loc.dot(Field::pDynamicState)
                .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT)),
            "is VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT but the extendedDynamicState3LineStippleEnable feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3DepthClipNegativeOneToOne &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_DEPTH_CLIP_NEGATIVE_ONE_TO_ONE_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3DepthClipNegativeOneToOne-07390", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_DEPTH_CLIP_NEGATIVE_ONE_TO_ONE_EXT)),
                         "is VK_DYNAMIC_STATE_DEPTH_CLIP_NEGATIVE_ONE_TO_ONE_EXT but the "
                         "extendedDynamicState3DepthClipNegativeOneToOne feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ViewportWScalingEnable &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_VIEWPORT_W_SCALING_ENABLE_NV)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ViewportWScalingEnable-07391", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_VIEWPORT_W_SCALING_ENABLE_NV)),
                         "is VK_DYNAMIC_STATE_VIEWPORT_W_SCALING_ENABLE_NV but the extendedDynamicState3ViewportWScalingEnable "
                         "feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ViewportSwizzle && pipeline.IsDynamic(CB_DYNAMIC_STATE_VIEWPORT_SWIZZLE_NV)) {
        skip |= LogError(
            "VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ViewportSwizzle-07392", device,
            create_info_loc.dot(Field::pDynamicState)
                .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_VIEWPORT_SWIZZLE_NV)),
            "is VK_DYNAMIC_STATE_VIEWPORT_SWIZZLE_NV but the extendedDynamicState3ViewportSwizzle feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3CoverageToColorEnable &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_COVERAGE_TO_COLOR_ENABLE_NV)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3CoverageToColorEnable-07393", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_ENABLE_NV)),
                         "is VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_ENABLE_NV but the extendedDynamicState3CoverageToColorEnable "
                         "feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3CoverageToColorLocation &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_COVERAGE_TO_COLOR_LOCATION_NV)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3CoverageToColorLocation-07394", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_LOCATION_NV)),
                         "is VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_LOCATION_NV but the extendedDynamicState3CoverageToColorLocation "
                         "feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3CoverageModulationMode &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_COVERAGE_MODULATION_MODE_NV)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3CoverageModulationMode-07395", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COVERAGE_MODULATION_MODE_NV)),
                         "is VK_DYNAMIC_STATE_COVERAGE_MODULATION_MODE_NV but the extendedDynamicState3CoverageModulationMode "
                         "feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3CoverageModulationTableEnable &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_ENABLE_NV)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3CoverageModulationTableEnable-07396", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_ENABLE_NV)),
                         "is VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_ENABLE_NV but the "
                         "extendedDynamicState3CoverageModulationTableEnable feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3CoverageModulationTable &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_NV)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3CoverageModulationTable-07397", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_NV)),
                         "is VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_NV but the extendedDynamicState3CoverageModulationTable "
                         "feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3CoverageReductionMode &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_COVERAGE_REDUCTION_MODE_NV)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3CoverageReductionMode-07398", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COVERAGE_REDUCTION_MODE_NV)),
                         "is VK_DYNAMIC_STATE_COVERAGE_REDUCTION_MODE_NV but the extendedDynamicState3CoverageReductionMode "
                         "feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3RepresentativeFragmentTestEnable &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_REPRESENTATIVE_FRAGMENT_TEST_ENABLE_NV)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3RepresentativeFragmentTestEnable-07399", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_REPRESENTATIVE_FRAGMENT_TEST_ENABLE_NV)),
                         "is VK_DYNAMIC_STATE_REPRESENTATIVE_FRAGMENT_TEST_ENABLE_NV but the "
                         "extendedDynamicState3RepresentativeFragmentTestEnable feature is not enabled.");
    }

    if (!enabled_features.extendedDynamicState3ShadingRateImageEnable &&
        pipeline.IsDynamic(CB_DYNAMIC_STATE_SHADING_RATE_IMAGE_ENABLE_NV)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-extendedDynamicState3ShadingRateImageEnable-07400", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_SHADING_RATE_IMAGE_ENABLE_NV)),
                         "is VK_DYNAMIC_STATE_SHADING_RATE_IMAGE_ENABLE_NV but the extendedDynamicState3ShadingRateImageEnable "
                         "feature is not enabled.");
    }

    if (!enabled_features.vertexInputDynamicState && pipeline.IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-04807", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT)),
                         "is VK_DYNAMIC_STATE_VERTEX_INPUT_EXT but the vertexInputDynamicState feature is not enabled.");
    }

    if (!enabled_features.colorWriteEnable && pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-04800", device,
                         create_info_loc.dot(Field::pDynamicState)
                             .dot(Field::pDynamicStates, get_state_index(VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT)),
                         "is VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT but the colorWriteEnable feature is not enabled.");
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineFragmentShadingRateState(
    const vvl::Pipeline& pipeline, const VkPipelineFragmentShadingRateStateCreateInfoKHR& fragment_shading_rate_state,
    const Location& create_info_loc) const {
    bool skip = false;
    if (pipeline.IsDynamic(CB_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR)) {
        return skip;
    }
    const Location fragment_loc =
        create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::fragmentSize);

    if (fragment_shading_rate_state.fragmentSize.width == 0) {
        skip |=
            LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04494", device, fragment_loc.dot(Field::width), "is zero.");
    }

    if (fragment_shading_rate_state.fragmentSize.height == 0) {
        skip |=
            LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04495", device, fragment_loc.dot(Field::height), "is zero.");
    }

    if (fragment_shading_rate_state.fragmentSize.width != 0 && !IsPowerOfTwo(fragment_shading_rate_state.fragmentSize.width)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04496", device, fragment_loc.dot(Field::width),
                         "is %" PRIu32 ".", fragment_shading_rate_state.fragmentSize.width);
    }

    if (fragment_shading_rate_state.fragmentSize.height != 0 && !IsPowerOfTwo(fragment_shading_rate_state.fragmentSize.height)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04497", device, fragment_loc.dot(Field::height),
                         "is %" PRIu32 ".", fragment_shading_rate_state.fragmentSize.height);
    }

    if (fragment_shading_rate_state.fragmentSize.width > 4) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04498", device, fragment_loc.dot(Field::width),
                         "is %" PRIu32 ".", fragment_shading_rate_state.fragmentSize.width);
    }

    if (fragment_shading_rate_state.fragmentSize.height > 4) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04499", device, fragment_loc.dot(Field::height),
                         "is %" PRIu32 ".", fragment_shading_rate_state.fragmentSize.height);
    }

    if (!enabled_features.pipelineFragmentShadingRate && fragment_shading_rate_state.fragmentSize.width != 1) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04500", device, fragment_loc.dot(Field::width),
                         "is %" PRIu32 ", but the pipelineFragmentShadingRate feature was not enabled.",
                         fragment_shading_rate_state.fragmentSize.width);
    }

    if (!enabled_features.pipelineFragmentShadingRate && fragment_shading_rate_state.fragmentSize.height != 1) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04500", device, fragment_loc.dot(Field::height),
                         "is %" PRIu32 ", but the pipelineFragmentShadingRate feature was not enabled.",
                         fragment_shading_rate_state.fragmentSize.height);
    }

    if (!enabled_features.primitiveFragmentShadingRate &&
        fragment_shading_rate_state.combinerOps[0] != VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04501", device,
                         create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::combinerOps, 0),
                         "is %s, but the primitiveFragmentShadingRate feature was not enabled.",
                         string_VkFragmentShadingRateCombinerOpKHR(fragment_shading_rate_state.combinerOps[0]));
    }

    if (!enabled_features.attachmentFragmentShadingRate &&
        fragment_shading_rate_state.combinerOps[1] != VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04502", device,
                         create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::combinerOps, 1),
                         "is %s, but the attachmentFragmentShadingRate feature was not enabled.",
                         string_VkFragmentShadingRateCombinerOpKHR(fragment_shading_rate_state.combinerOps[1]));
    }

    if (!phys_dev_ext_props.fragment_shading_rate_props.fragmentShadingRateNonTrivialCombinerOps &&
        (fragment_shading_rate_state.combinerOps[0] != VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR &&
         fragment_shading_rate_state.combinerOps[0] != VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-fragmentShadingRateNonTrivialCombinerOps-04506", device,
                         create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::combinerOps, 0),
                         "is %s, but the fragmentShadingRateNonTrivialCombinerOps feature is not enabled.",
                         string_VkFragmentShadingRateCombinerOpKHR(fragment_shading_rate_state.combinerOps[0]));
    }

    if (!phys_dev_ext_props.fragment_shading_rate_props.fragmentShadingRateNonTrivialCombinerOps &&
        (fragment_shading_rate_state.combinerOps[1] != VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR &&
         fragment_shading_rate_state.combinerOps[1] != VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-fragmentShadingRateNonTrivialCombinerOps-04506", device,
                         create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::combinerOps, 1),
                         "is %s, but the fragmentShadingRateNonTrivialCombinerOps feature is not enabled.",
                         string_VkFragmentShadingRateCombinerOpKHR(fragment_shading_rate_state.combinerOps[1]));
    }

    auto is_valid_enum_value = [](VkFragmentShadingRateCombinerOpKHR value) {
        switch (value) {
            case VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR:
            case VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR:
            case VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN_KHR:
            case VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR:
            case VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR:
                return true;
            default:
                return false;
        };
    };

    const auto combiner_ops = fragment_shading_rate_state.combinerOps;
    if (pipeline.OwnsLibState(pipeline.pre_raster_state) || pipeline.OwnsLibState(pipeline.fragment_shader_state)) {
        if (!is_valid_enum_value(combiner_ops[0])) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-06567", device,
                             create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::combinerOps, 0),
                             "(0x%" PRIx32 ") is invalid.", combiner_ops[0]);
        }
        if (!is_valid_enum_value(combiner_ops[1])) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicState-06568", device,
                             create_info_loc.pNext(Struct::VkPipelineFragmentShadingRateStateCreateInfoKHR, Field::combinerOps, 1),
                             "(0x%" PRIx32 ") is invalid.", combiner_ops[1]);
        }
    }
    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineDynamicRendering(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;
    const auto& pipeline_ci = pipeline.GraphicsCreateInfo();
    if (pipeline_ci.renderPass != VK_NULL_HANDLE) {
        return skip;
    }

    const auto color_blend_state = pipeline.ColorBlendState();
    const auto* rendering_struct = pipeline.rendering_create_info;
    if (!rendering_struct) {
        // The spec says when thie struct is not included it is same as
        // viewMask = 0, colorAttachmentCount = 0, formats = VK_FORMAT_UNDEFINED.
        // Most VUs are worded around this, but some need to be validated here
        if (pipeline.OwnsLibState(pipeline.fragment_output_state) && color_blend_state && color_blend_state->attachmentCount > 0) {
            skip |= LogError(
                "VUID-VkGraphicsPipelineCreateInfo-renderPass-06055", device,
                create_info_loc.dot(Field::pColorBlendState).dot(Field::attachmentCount),
                "is %" PRIu32
                ", but VkPipelineRenderingCreateInfo::colorAttachmentCount is zero because the pNext chain was not included.",
                color_blend_state->attachmentCount);
        }
        return skip;
    }

    if (!pipeline.RasterizationDisabled()) {
        if (pipeline.fragment_shader_state && pipeline.fragment_output_state &&
            ((rendering_struct->depthAttachmentFormat != VK_FORMAT_UNDEFINED) ||
             (rendering_struct->stencilAttachmentFormat != VK_FORMAT_UNDEFINED)) &&
            !pipeline.DepthStencilState() && !pipeline.IsDepthStencilStateDynamic() &&
            !IsExtEnabled(extensions.vk_ext_extended_dynamic_state3)) {
            skip |= LogError(
                "VUID-VkGraphicsPipelineCreateInfo-renderPass-09033", device, create_info_loc.dot(Field::pDepthStencilState),
                "is NULL, but %s is %s and stencilAttachmentFormat is %s.",
                create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::depthAttachmentFormat).Fields().c_str(),
                string_VkFormat(rendering_struct->depthAttachmentFormat),
                string_VkFormat(rendering_struct->stencilAttachmentFormat));
        }

        if (pipeline.fragment_output_state && (rendering_struct->colorAttachmentCount != 0) && !color_blend_state &&
            !pipeline.IsColorBlendStateDynamic()) {
            for (const auto [i, format] :
                 vvl::enumerate(rendering_struct->pColorAttachmentFormats, rendering_struct->colorAttachmentCount)) {
                if (format != VK_FORMAT_UNDEFINED) {
                    skip |= LogError(
                        "VUID-VkGraphicsPipelineCreateInfo-renderPass-09037", device, create_info_loc.dot(Field::pColorBlendState),
                        "is NULL, but %s is %" PRIu32 " and pColorAttachmentFormats[%" PRIu32 "] is %s.",
                        create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::colorAttachmentCount).Fields().c_str(),
                        rendering_struct->colorAttachmentCount, i, string_VkFormat(format));
                }
            }
        }
        if (pipeline.fragment_shader_state && pipeline.fragment_output_state) {
            const auto input_attachment_index = vku::FindStructInPNextChain<VkRenderingInputAttachmentIndexInfo>(pipeline_ci.pNext);
            if (input_attachment_index) {
                skip |= ValidateRenderingInputAttachmentIndices(*input_attachment_index, device,
                                                                create_info_loc.pNext(Struct::VkRenderingInputAttachmentIndexInfo));
                if (input_attachment_index->colorAttachmentCount != rendering_struct->colorAttachmentCount) {
                    const Location loc =
                        create_info_loc.pNext(Struct::VkRenderingInputAttachmentIndexInfo, Field::colorAttachmentCount);
                    skip |=
                        LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-09531", device, loc,
                                 "(%" PRIu32 ") does not match VkPipelineRenderingCreateInfo.colorAttachmentCount (%" PRIu32 ").",
                                 input_attachment_index->colorAttachmentCount, rendering_struct->colorAttachmentCount);
                }
            }

            const auto attachment_location = vku::FindStructInPNextChain<VkRenderingAttachmentLocationInfo>(pipeline_ci.pNext);
            if (attachment_location) {
                skip |= ValidateRenderingAttachmentLocations(*attachment_location, device,
                                                             create_info_loc.pNext(Struct::VkRenderingAttachmentLocationInfo));
                if (attachment_location->colorAttachmentCount != rendering_struct->colorAttachmentCount) {
                    const Location loc =
                        create_info_loc.pNext(Struct::VkRenderingAttachmentLocationInfo, Field::colorAttachmentCount);
                    skip |=
                        LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-09532", device, loc,
                                 "(%" PRIu32 ") does not match VkPipelineRenderingCreateInfo.colorAttachmentCount (%" PRIu32 ").",
                                 attachment_location->colorAttachmentCount, rendering_struct->colorAttachmentCount);
                }
            }
        }
    }

    if (rendering_struct->viewMask != 0) {
        skip |= ValidateMultiViewShaders(pipeline, create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::viewMask),
                                         rendering_struct->viewMask, true);
    }

    if (pipeline.OwnsLibState(pipeline.fragment_output_state)) {
        for (uint32_t color_index = 0; color_index < rendering_struct->colorAttachmentCount; color_index++) {
            const VkFormat color_format = rendering_struct->pColorAttachmentFormats[color_index];
            if (color_format == VK_FORMAT_UNDEFINED) {
                continue;
            }

            VkFormatFeatureFlags2 format_features = GetPotentialFormatFeatures(color_format);
            if ((format_features &
                 (VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_2_LINEAR_COLOR_ATTACHMENT_BIT_NV)) == 0) {
                skip |= LogError(
                    "VUID-VkGraphicsPipelineCreateInfo-renderPass-06582", device,
                    create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::pColorAttachmentFormats, color_index),
                    "(%s) doesn't support VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT\n(supported features: %s).",
                    string_VkFormat(color_format), string_VkFormatFeatureFlags2(format_features).c_str());
            }

            if (color_blend_state && color_index < color_blend_state->attachmentCount) {
                const auto& color_blend_attachment = color_blend_state->pAttachments[color_index];

                if (((format_features & VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BLEND_BIT) == 0) &&
                    color_blend_attachment.blendEnable) {
                    skip |= LogError(
                        "VUID-VkGraphicsPipelineCreateInfo-renderPass-06062", device,
                        create_info_loc.dot(Field::pColorBlendState).dot(Field::pAttachments, color_index).dot(Field::blendEnable),
                        "is VK_TRUE.");
                }

                if (color_format == VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 &&
                    !pipeline.IsDynamic(CB_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT)) {
                    const VkColorComponentFlags rgba =
                        VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
                    if ((color_blend_attachment.colorWriteMask & rgba) != rgba &&
                        (color_blend_attachment.colorWriteMask & rgba) != 0) {
                        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-None-09043", device,
                                         create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo,
                                                               Field::pColorAttachmentFormats, color_index),
                                         "is VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, but pColorBlendState->pAttachments[%" PRIu32
                                         "].colorWriteMask is %s.\nThe 999E5 format is special as it overlaps the traditional "
                                         "8-bit boundary and has no real alpha channel. When updating, the driver needs the full "
                                         "RGBA mask in order to properly handle this format.",
                                         color_index, string_VkColorComponentFlags(color_blend_attachment.colorWriteMask).c_str());
                    }
                }
            }
        }

        if (rendering_struct->depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
            VkFormatFeatureFlags2 format_features = GetPotentialFormatFeatures(rendering_struct->depthAttachmentFormat);
            if ((format_features & VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT) == 0) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-06585", device,
                                 create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::depthAttachmentFormat),
                                 "(%s) doesn't support VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT\n(supported features: %s).",
                                 string_VkFormat(rendering_struct->depthAttachmentFormat),
                                 string_VkFormatFeatureFlags2(format_features).c_str());
            }
        }

        if (rendering_struct->stencilAttachmentFormat != VK_FORMAT_UNDEFINED) {
            VkFormatFeatureFlags2 format_features = GetPotentialFormatFeatures(rendering_struct->stencilAttachmentFormat);
            if ((format_features & VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT) == 0) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-06586", device,
                                 create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::stencilAttachmentFormat),
                                 "(%s) doesn't support VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT\n(supported features: %s).",
                                 string_VkFormat(rendering_struct->stencilAttachmentFormat),
                                 string_VkFormatFeatureFlags2(format_features).c_str());
            }
        }

        if ((rendering_struct->depthAttachmentFormat != VK_FORMAT_UNDEFINED) &&
            (rendering_struct->stencilAttachmentFormat != VK_FORMAT_UNDEFINED) &&
            (rendering_struct->depthAttachmentFormat != rendering_struct->stencilAttachmentFormat)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-06589", device,
                             create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::depthAttachmentFormat),
                             "(%s) is not equal to stencilAttachmentFormat (%s).",
                             string_VkFormat(rendering_struct->depthAttachmentFormat),
                             string_VkFormat(rendering_struct->stencilAttachmentFormat));
        }

        if (color_blend_state && rendering_struct->colorAttachmentCount != color_blend_state->attachmentCount &&
            !IsColorBlendStateAttachmentCountIgnore(pipeline)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-06055", device,
                             create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::colorAttachmentCount),
                             "(%" PRIu32 ") is different from %s (%" PRIu32 ").", rendering_struct->colorAttachmentCount,
                             create_info_loc.dot(Field::pColorBlendState).dot(Field::attachmentCount).Fields().c_str(),
                             color_blend_state->attachmentCount);
        }

        if (const auto* custom_resolve = vku::FindStructInPNextChain<VkCustomResolveCreateInfoEXT>(pipeline.GetCreateInfoPNext())) {
            skip |= ValidateCustomResolveCreateInfoEXT(*custom_resolve, create_info_loc);
            // The formats themselve can be different between the structs, but the color count must be the same
            if (custom_resolve->colorAttachmentCount != rendering_struct->colorAttachmentCount) {
                skip |= LogError(
                    "VUID-VkGraphicsPipelineCreateInfo-renderPass-11504", device,
                    create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::colorAttachmentCount),
                    "(%" PRIu32 ") is different from %s (%" PRIu32 ").", rendering_struct->colorAttachmentCount,
                    create_info_loc.pNext(Struct::VkCustomResolveCreateInfoEXT, Field::colorAttachmentCount).Fields().c_str(),
                    custom_resolve->colorAttachmentCount);
            }
        }
    }

    if (pipeline.IsRenderPassStateRequired()) {
        if ((enabled_features.multiview == VK_FALSE) && (rendering_struct->viewMask != 0)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-multiview-06577", device,
                             create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::viewMask),
                             "is 0x%" PRIx32 ", but the multiview feature was not enabled.", rendering_struct->viewMask);
        }

        if (MostSignificantBit(rendering_struct->viewMask) >= static_cast<int32_t>(phys_dev_props_core11.maxMultiviewViewCount)) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-renderPass-06578", device,
                             create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::viewMask),
                             "is 0x%" PRIx32 ", but maxMultiviewViewCount is %" PRIu32 "", rendering_struct->viewMask,
                             phys_dev_props_core11.maxMultiviewViewCount);
        }
    }

    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineBindPoint(const vvl::CommandBuffer& cb_state, const vvl::Pipeline& pipeline,
                                                   const Location& loc) const {
    bool skip = false;

    auto& cb_sub_state = core::SubState(cb_state);
    if (!cb_sub_state.viewport.inherited_depths.empty()) {
        bool dyn_viewport =
            pipeline.IsDynamic(CB_DYNAMIC_STATE_VIEWPORT_WITH_COUNT) || pipeline.IsDynamic(CB_DYNAMIC_STATE_VIEWPORT);
        bool dyn_scissor = pipeline.IsDynamic(CB_DYNAMIC_STATE_SCISSOR_WITH_COUNT) || pipeline.IsDynamic(CB_DYNAMIC_STATE_SCISSOR);
        if (!dyn_viewport || !dyn_scissor) {
            const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
            skip |= LogError("VUID-vkCmdBindPipeline-commandBuffer-04808", objlist, loc,
                             "Graphics pipeline incompatible with viewport/scissor inheritance.");
        }
        const auto* discard_rectangle_state =
            vku::FindStructInPNextChain<VkPipelineDiscardRectangleStateCreateInfoEXT>(pipeline.GetCreateInfoPNext());
        if ((discard_rectangle_state && discard_rectangle_state->discardRectangleCount != 0) ||
            (pipeline.IsDynamic(CB_DYNAMIC_STATE_DISCARD_RECTANGLE_ENABLE_EXT))) {
            if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_DISCARD_RECTANGLE_EXT)) {
                std::ostringstream msg;
                if (discard_rectangle_state) {
                    msg << "VkPipelineDiscardRectangleStateCreateInfoEXT::discardRectangleCount = "
                        << discard_rectangle_state->discardRectangleCount;
                } else {
                    msg << "VK_DYNAMIC_STATE_DISCARD_RECTANGLE_ENABLE_EXT";
                }
                const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                skip |= LogError(
                    "VUID-vkCmdBindPipeline-commandBuffer-04809", objlist, loc.dot(Field::commandBuffer),
                    "is a secondary command buffer with VkCommandBufferInheritanceViewportScissorInfoNV::viewportScissor2D "
                    "enabled, pipelineBindPoint is VK_PIPELINE_BIND_POINT_GRAPHICS and pipeline was created with %s, but without "
                    "VK_DYNAMIC_STATE_DISCARD_RECTANGLE_EXT.",
                    msg.str().c_str());
            }
        }
    }

    if (phys_dev_ext_props.provoking_vertex_props.provokingVertexModePerPipeline == VK_FALSE) {
        // Render passes only occur in graphics pipelines
        const auto& last_bound = cb_state.GetLastBoundGraphics();
        const vvl::Pipeline* old_pipeline_state = last_bound.pipeline_state;
        if (old_pipeline_state) {
            auto old_provoking_vertex_state_ci =
                vku::FindStructInPNextChain<VkPipelineRasterizationProvokingVertexStateCreateInfoEXT>(
                    old_pipeline_state->RasterizationStatePNext());

            auto current_provoking_vertex_state_ci =
                vku::FindStructInPNextChain<VkPipelineRasterizationProvokingVertexStateCreateInfoEXT>(
                    pipeline.RasterizationStatePNext());

            if (old_provoking_vertex_state_ci && !current_provoking_vertex_state_ci) {
                const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), old_pipeline_state->Handle());
                skip |= LogError("VUID-vkCmdBindPipeline-pipelineBindPoint-04881", objlist, loc,
                                 "Previous %s's provokingVertexMode is %s, but %s doesn't chain "
                                 "VkPipelineRasterizationProvokingVertexStateCreateInfoEXT.",
                                 FormatHandle(old_pipeline_state->Handle()).c_str(),
                                 string_VkProvokingVertexModeEXT(old_provoking_vertex_state_ci->provokingVertexMode),
                                 FormatHandle(pipeline.Handle()).c_str());
            } else if (!old_provoking_vertex_state_ci && current_provoking_vertex_state_ci) {
                const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), old_pipeline_state->Handle());
                skip |= LogError("VUID-vkCmdBindPipeline-pipelineBindPoint-04881", objlist, loc,
                                 "%s's provokingVertexMode is %s, but previous %s doesn't chain "
                                 "VkPipelineRasterizationProvokingVertexStateCreateInfoEXT.",
                                 FormatHandle(pipeline.Handle()).c_str(),
                                 string_VkProvokingVertexModeEXT(current_provoking_vertex_state_ci->provokingVertexMode),
                                 FormatHandle(old_pipeline_state->Handle()).c_str());
            } else if (old_provoking_vertex_state_ci && current_provoking_vertex_state_ci &&
                       old_provoking_vertex_state_ci->provokingVertexMode !=
                           current_provoking_vertex_state_ci->provokingVertexMode) {
                const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), old_pipeline_state->Handle());
                skip |= LogError("VUID-vkCmdBindPipeline-pipelineBindPoint-04881", objlist, loc,
                                 "%s's provokingVertexMode is %s, but previous %s's provokingVertexMode is %s.",
                                 FormatHandle(pipeline.Handle()).c_str(),
                                 string_VkProvokingVertexModeEXT(current_provoking_vertex_state_ci->provokingVertexMode),
                                 FormatHandle(old_pipeline_state->Handle()).c_str(),
                                 string_VkProvokingVertexModeEXT(old_provoking_vertex_state_ci->provokingVertexMode));
            }
        }
    }

    return skip;
}

// Validate draw-time state related to the PSO
bool CoreChecks::ValidateDrawPipeline(const LastBound& last_bound_state, const vvl::Pipeline& pipeline, const Location& loc) const {
    bool skip = false;
    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;
    const vvl::RenderPass* rp_state = cb_state.active_render_pass.get();
    if (!rp_state) return skip;

    if (rp_state->UsesDynamicRendering()) {
        skip |= ValidateDrawPipelineDynamicRenderpass(last_bound_state, pipeline, *rp_state, loc);
    } else {
        skip |= ValidateDrawPipelineRenderpass(last_bound_state, pipeline, *rp_state, loc);
    }

    skip |= ValidateDrawPipelineFramebuffer(cb_state, pipeline, loc);
    skip |= ValidateDrawPipelineFragmentDensityMapLayered(cb_state, pipeline, *rp_state, loc);
    skip |= ValidateDrawPipelineRasterizationState(last_bound_state, pipeline, loc);

    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT) && rp_state->UsesDynamicRendering()) {
        const auto msrtss_info = vku::FindStructInPNextChain<VkMultisampledRenderToSingleSampledInfoEXT>(
            rp_state->dynamic_rendering_begin_rendering_info.pNext);
        if (msrtss_info && msrtss_info->multisampledRenderToSingleSampledEnable &&
            msrtss_info->rasterizationSamples != pipeline.MultisampleState()->rasterizationSamples) {
            const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
            skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::RASTERIZATION_SAMPLES_07935), objlist, loc,
                             "VkMultisampledRenderToSingleSampledInfoEXT::multisampledRenderToSingleSampledEnable is VK_TRUE, but "
                             "the rasterizationSamples (%" PRIu32 ") is not equal to rasterizationSamples (%" PRIu32
                             ") of the the currently bound pipeline.",
                             msrtss_info->rasterizationSamples, pipeline.MultisampleState()->rasterizationSamples);
        }
    }

    if ((pipeline.create_info_shaders & (VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT |
                                         VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT | VK_SHADER_STAGE_GEOMETRY_BIT)) != 0) {
        for (const auto& query : cb_state.active_queries) {
            const auto query_pool_state = Get<vvl::QueryPool>(query.pool);
            if (query_pool_state && query_pool_state->create_info.queryType == VK_QUERY_TYPE_MESH_PRIMITIVES_GENERATED_EXT) {
                const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), query.pool);
                skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::MESH_SHADER_QUERIES_07073), objlist, loc,
                                 "query %" PRIu32
                                 " in %s with type VK_QUERY_TYPE_MESH_PRIMITIVES_GENERATED_EXT is active, but the current pipeline "
                                 "is drawing with non-mesh shader stages.",
                                 query.slot, FormatHandle(query.pool).c_str());
            }
        }
    }

    return skip;
}

// Verify that PSO creation renderPass is compatible with active (non-dynamic) renderPass
bool CoreChecks::ValidateDrawPipelineRenderpass(const LastBound& last_bound_state, const vvl::Pipeline& pipeline,
                                                const vvl::RenderPass& rp_state, const Location& loc) const {
    bool skip = false;
    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;

    // Can use RenderPassState() here even GPL because we only need to check for compatible renderpasses
    const auto& pipeline_rp_state = pipeline.RenderPassState();
    // TODO: AMD extension codes are included here, but actual function entrypoints are not yet intercepted
    if (rp_state.VkHandle() != pipeline_rp_state->VkHandle()) {
        // renderPass that PSO was created with must be compatible with active renderPass that PSO is being used with
        skip |= ValidateRenderPassCompatibility(cb_state.Handle(), rp_state, pipeline.Handle(), *pipeline_rp_state.get(), loc);
    }
    const uint32_t subpass = pipeline.Subpass();
    if (subpass != cb_state.GetActiveSubpass()) {
        const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), rp_state.Handle());
        skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::SUBPASS_INDEX_02685), objlist, loc,
                         "Pipeline was built for subpass %" PRIu32 " but used in subpass %" PRIu32 ".", subpass,
                         cb_state.GetActiveSubpass());
    }

    return skip;
}

bool CoreChecks::ValidateDrawPipelineDynamicRenderpass(const LastBound& last_bound_state, const vvl::Pipeline& pipeline,
                                                       const vvl::RenderPass& rp_state, const Location& loc) const {
    bool skip = false;
    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;

    // Can use RenderPassState() here even GPL because we only need to check for it being null
    const auto pipeline_rp_state = pipeline.RenderPassState();
    ASSERT_AND_RETURN_SKIP(pipeline_rp_state);
    if (pipeline_rp_state->VkHandle() != VK_NULL_HANDLE) {
        const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), pipeline_rp_state->Handle());
        skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_06198), objlist, loc,
                         "The last bound pipeline (%s) must have been created with a "
                         "VkGraphicsPipelineCreateInfo::renderPass equal to VK_NULL_HANDLE",
                         FormatHandle(pipeline).c_str());
        return skip;
    }

    const VkPipelineRenderingCreateInfo& pipeline_rendering_ci = pipeline.GetRenderPassPipelineRenderingCreateInfo();
    const auto rendering_view_mask = rp_state.GetDynamicRenderingViewMask();
    // There is currently a 06031 VU that catches the viewMask at vkCmdExecuteCommands time, so seems like this is not suppose to be
    // validated with inherited render passes
    if (rp_state.use_dynamic_rendering && pipeline_rendering_ci.viewMask != rendering_view_mask) {
        const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
        skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_VIEW_MASK_06178), objlist, loc,
                         "The last bound pipeline (%s) viewMask (0x%" PRIx32
                         ") must be equal to VkRenderingInfo::viewMask (0x%" PRIx32 ")",
                         FormatHandle(pipeline).c_str(), pipeline_rendering_ci.viewMask, rendering_view_mask);
    }

    skip |= ValidateDrawPipelineDynamicRenderpassNonInherited(last_bound_state, pipeline, rp_state, loc);
    skip |= ValidateDrawPipelineDynamicRenderpassFragmentFormat(last_bound_state, pipeline, rp_state, pipeline_rendering_ci, loc);
    skip |= ValidateDrawPipelineDynamicRenderpassDepthStencil(last_bound_state, pipeline, rp_state, pipeline_rendering_ci, loc);
    skip |=
        ValidateDrawPipelineDynamicRenderpassUnusedAttachments(last_bound_state, pipeline, rp_state, pipeline_rendering_ci, loc);
    skip |= ValidateDrawPipelineDynamicRenderpassLegacyDithering(last_bound_state, pipeline, rp_state, loc);
    skip |= ValidateDrawPipelineDynamicRenderpassSampleCount(last_bound_state, pipeline, rp_state, loc);
    skip |= ValidateDrawPipelineDynamicRenderpassFragmentShadingRate(last_bound_state, pipeline, rp_state, loc);
    // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11644
    // skip |= ValidateDrawPipelineDynamicRenderingAttachmentFlags(last_bound_state, pipeline, rp_state, loc);
    return skip;
}

bool CoreChecks::ValidateDrawPipelineDynamicRenderpassDepthStencil(const LastBound& last_bound_state, const vvl::Pipeline& pipeline,
                                                                   const vvl::RenderPass& rp_state,
                                                                   const VkPipelineRenderingCreateInfo& pipeline_rendering_ci,
                                                                   const Location& loc) const {
    bool skip = false;
    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;
    auto& cb_sub_state = core::SubState(cb_state);

    if (last_bound_state.IsDepthTestEnable()) {
        if (cb_sub_state.custom_resolve.started) {
            if (const auto* custom_resolve =
                    vku::FindStructInPNextChain<VkCustomResolveCreateInfoEXT>(pipeline.GetCreateInfoPNext())) {
                if (custom_resolve->depthAttachmentFormat == VK_FORMAT_UNDEFINED &&
                    rp_state.dynamic_rendering_begin_rendering_info.pDepthAttachment) {
                    const VkImageView depth_attachment =
                        rp_state.dynamic_rendering_begin_rendering_info.pDepthAttachment->resolveImageView;
                    if (depth_attachment != VK_NULL_HANDLE) {
                        const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), depth_attachment);
                        skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::CUSTOM_RESOLVE_11540), objlist, loc,
                                         "VkRenderingInfo::pDepthAttachment->resolveImageView is %s, but currently bound graphics "
                                         "pipeline %s was created "
                                         "with VkCustomResolveCreateInfoEXT::depthAttachmentFormat equal to VK_FORMAT_UNDEFINED",
                                         FormatHandle(depth_attachment).c_str(), FormatHandle(pipeline).c_str());
                    }
                }
            }
        } else if (pipeline_rendering_ci.depthAttachmentFormat == VK_FORMAT_UNDEFINED) {
            if (rp_state.use_dynamic_rendering && rp_state.dynamic_rendering_begin_rendering_info.pDepthAttachment) {
                const VkImageView depth_attachment = rp_state.dynamic_rendering_begin_rendering_info.pDepthAttachment->imageView;
                if (depth_attachment != VK_NULL_HANDLE) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), depth_attachment);
                    skip |= LogError(
                        CreateActionVuid(loc.function, vvl::ActionVUID::DEPTH_ATTACHMENT_08964), objlist, loc,
                        "VkRenderingInfo::pDepthAttachment->imageView is %s, but currently bound graphics pipeline %s was created "
                        "with VkPipelineRenderingCreateInfo::depthAttachmentFormat equal to VK_FORMAT_UNDEFINED",
                        FormatHandle(depth_attachment).c_str(), FormatHandle(pipeline).c_str());
                }
            } else if (rp_state.use_dynamic_rendering_inherited) {
                const VkFormat depth_format = rp_state.inheritance_rendering_info.depthAttachmentFormat;
                if (depth_format != VK_FORMAT_UNDEFINED) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DEPTH_ATTACHMENT_08964), objlist, loc,
                                     "VkCommandBufferInheritanceRenderingInfo::depthAttachmentFormat is %s, but currently bound "
                                     "graphics pipeline %s was created "
                                     "with VkPipelineRenderingCreateInfo::depthAttachmentFormat equal to VK_FORMAT_UNDEFINED",
                                     string_VkFormat(depth_format), FormatHandle(pipeline).c_str());
                }
            }
        }
    }

    if (last_bound_state.IsStencilTestEnable()) {
        if (cb_sub_state.custom_resolve.started) {
            if (const auto* custom_resolve =
                    vku::FindStructInPNextChain<VkCustomResolveCreateInfoEXT>(pipeline.GetCreateInfoPNext())) {
                if (custom_resolve->stencilAttachmentFormat == VK_FORMAT_UNDEFINED &&
                    rp_state.dynamic_rendering_begin_rendering_info.pStencilAttachment) {
                    const VkImageView stencil_attachment =
                        rp_state.dynamic_rendering_begin_rendering_info.pStencilAttachment->resolveImageView;
                    if (stencil_attachment != VK_NULL_HANDLE) {
                        const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), stencil_attachment);
                        skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::CUSTOM_RESOLVE_11860), objlist, loc,
                                         "VkRenderingInfo::pStencilAttachment->resolveImageView is %s, but currently bound "
                                         "graphics pipeline %s was created "
                                         "with VkCustomResolveCreateInfoEXT::stencilAttachmentFormat equal to VK_FORMAT_UNDEFINED",
                                         FormatHandle(stencil_attachment).c_str(), FormatHandle(pipeline).c_str());
                    }
                }
            }
        } else if (pipeline_rendering_ci.stencilAttachmentFormat == VK_FORMAT_UNDEFINED) {
            if (rp_state.use_dynamic_rendering && rp_state.dynamic_rendering_begin_rendering_info.pStencilAttachment) {
                const VkImageView stencil_attachment =
                    rp_state.dynamic_rendering_begin_rendering_info.pStencilAttachment->imageView;
                if (stencil_attachment != VK_NULL_HANDLE) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), stencil_attachment);
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::STENCIL_ATTACHMENT_08965), objlist, loc,
                                     "VkRenderingInfo::pStencilAttachment->imageView is %s, but currently bound graphics pipeline "
                                     "%s was created with "
                                     "VkPipelineRenderingCreateInfo::stencilAttachmentFormat equal to VK_FORMAT_UNDEFINED",
                                     FormatHandle(stencil_attachment).c_str(), FormatHandle(pipeline).c_str());
                }
            } else if (rp_state.use_dynamic_rendering_inherited) {
                const VkFormat stencil_format = rp_state.inheritance_rendering_info.stencilAttachmentFormat;
                if (stencil_format != VK_FORMAT_UNDEFINED) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::STENCIL_ATTACHMENT_08965), objlist, loc,
                                     "VkCommandBufferInheritanceRenderingInfo::stencilAttachmentFormat is %s, but currently bound "
                                     "graphics pipeline %s was created "
                                     "with VkPipelineRenderingCreateInfo::stencilAttachmentFormat equal to VK_FORMAT_UNDEFINED",
                                     string_VkFormat(stencil_format), FormatHandle(pipeline).c_str());
                }
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidateDrawPipelineDynamicRenderpassNonInherited(const LastBound& last_bound_state, const vvl::Pipeline& pipeline,
                                                                   const vvl::RenderPass& rp_state, const Location& loc) const {
    bool skip = false;
    // See https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10761
    // There are some drawtime VUs that say:
    //   "If the current render pass instance was begun with a vkCmdBeginRendering call in commandBuffer"
    // Where the "call in commandBuffer" part specifically mean we don't count the cases of secondary command buffers that are using
    // VkCommandBufferInheritanceRenderingInfo
    if (rp_state.use_dynamic_rendering_inherited) {
        return skip;
    }

    const VkRenderingFlags render_flags = rp_state.GetRenderingFlags();
    if ((render_flags & VK_RENDERING_CONTENTS_SECONDARY_COMMAND_BUFFERS_BIT) != 0 &&
        (render_flags & VK_RENDERING_CONTENTS_INLINE_BIT_KHR) == 0) {
        skip |=
            LogError(CreateActionVuid(loc.function, vvl::ActionVUID::RENDERING_CONTENTS_10582), last_bound_state.cb_state.Handle(),
                     loc, "the render pass is begun with VkRenderingInfo::flags %s (missing VK_RENDERING_CONTENTS_INLINE_BIT_KHR)",
                     string_VkRenderingFlags(render_flags).c_str());
    }

    return skip;
}

bool CoreChecks::ValidateDrawPipelineDynamicRenderpassFragmentFormat(const LastBound& last_bound_state,
                                                                     const vvl::Pipeline& pipeline, const vvl::RenderPass& rp_state,
                                                                     const VkPipelineRenderingCreateInfo& pipeline_rendering_ci,
                                                                     const Location& loc) const {
    bool skip = false;
    if ((pipeline.active_shaders & VK_SHADER_STAGE_FRAGMENT_BIT) == 0) {
        return skip;
    }

    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;
    auto& cb_sub_state = core::SubState(cb_state);

    const uint32_t color_count = rp_state.use_dynamic_rendering
                                     ? rp_state.dynamic_rendering_begin_rendering_info.colorAttachmentCount
                                     : rp_state.inheritance_rendering_info.colorAttachmentCount;
    for (uint32_t i = 0; i < color_count; ++i) {
        const bool statically_writes_to_color_attachment =
            pipeline.fs_writable_output_location_list.find(i) != pipeline.fs_writable_output_location_list.end();
        const bool mask_and_write_enabled = last_bound_state.GetColorWriteMask(i) != 0 && last_bound_state.IsColorWriteEnabled(i);
        if (!statically_writes_to_color_attachment || !mask_and_write_enabled) {
            continue;
        }

        if (cb_sub_state.custom_resolve.started) {
            const auto* custom_resolve = vku::FindStructInPNextChain<VkCustomResolveCreateInfoEXT>(pipeline.GetCreateInfoPNext());
            if (!custom_resolve) {
                continue;
            }
            if (i < custom_resolve->colorAttachmentCount && custom_resolve->pColorAttachmentFormats[i] != VK_FORMAT_UNDEFINED) {
                continue;
            }

            // Only a dynamic rendering case for CustomResolve
            const VkImageView resolve_attachment =
                rp_state.dynamic_rendering_begin_rendering_info.pColorAttachments[i].resolveImageView;
            if (resolve_attachment != VK_NULL_HANDLE) {
                const LogObjectList objlist(last_bound_state.cb_state.Handle(), pipeline.Handle(), resolve_attachment);
                skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::CUSTOM_RESOLVE_11539), objlist, loc,
                                 "VkRenderingInfo::pColorAttachments[%" PRIu32
                                 "].resolveImageView is %s, but currently bound graphics pipeline %s was created with "
                                 "VkCustomResolveCreateInfoEXT::pColorAttachmentFormats[%" PRIu32 "] equal to VK_FORMAT_UNDEFINED",
                                 i, FormatHandle(resolve_attachment).c_str(), FormatHandle(pipeline).c_str(), i);
            }
        } else {
            if (i < pipeline_rendering_ci.colorAttachmentCount &&
                pipeline_rendering_ci.pColorAttachmentFormats[i] != VK_FORMAT_UNDEFINED) {
                continue;
            }

            if (rp_state.use_dynamic_rendering) {
                const VkImageView color_attachment = rp_state.dynamic_rendering_begin_rendering_info.pColorAttachments[i].imageView;
                if (color_attachment != VK_NULL_HANDLE) {
                    const LogObjectList objlist(last_bound_state.cb_state.Handle(), pipeline.Handle(), color_attachment);
                    skip |=
                        LogError(CreateActionVuid(loc.function, vvl::ActionVUID::COLOR_ATTACHMENT_08963), objlist, loc,
                                 "VkRenderingInfo::pColorAttachments[%" PRIu32
                                 "].imageView is %s, but currently bound graphics pipeline %s was created with "
                                 "VkPipelineRenderingCreateInfo::pColorAttachmentFormats[%" PRIu32 "] equal to VK_FORMAT_UNDEFINED",
                                 i, FormatHandle(color_attachment).c_str(), FormatHandle(pipeline).c_str(), i);
                }
            } else {
                assert(rp_state.use_dynamic_rendering_inherited);
                const VkFormat color_format = rp_state.inheritance_rendering_info.pColorAttachmentFormats[i];
                if (color_format != VK_FORMAT_UNDEFINED) {
                    const LogObjectList objlist(last_bound_state.cb_state.Handle(), pipeline.Handle());
                    skip |=
                        LogError(CreateActionVuid(loc.function, vvl::ActionVUID::COLOR_ATTACHMENT_08963), objlist, loc,
                                 "VkCommandBufferInheritanceRenderingInfo::pColorAttachmentFormats[%" PRIu32
                                 "] is %s, but currently bound graphics pipeline %s was created with "
                                 "VkPipelineRenderingCreateInfo::pColorAttachmentFormats[%" PRIu32 "] equal to VK_FORMAT_UNDEFINED",
                                 i, string_VkFormat(color_format), FormatHandle(pipeline).c_str(), i);
                }
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidateDrawPipelineDynamicRenderingAttachmentFlags(const LastBound& last_bound_state,
                                                                     const vvl::Pipeline& pipeline, const vvl::RenderPass& rp_state,
                                                                     const Location& loc) const {
    bool skip = false;

    if (!(rp_state.dynamic_rendering_begin_rendering_info.flags & VK_RENDERING_LOCAL_READ_CONCURRENT_ACCESS_CONTROL_BIT_KHR)) {
        return skip;
    }

    auto validate_feedback_loop = [&](const vku::safe_VkRenderingAttachmentInfo* attachment, const Location& loc) {
        if (!attachment) {
            return;
        }

        if (attachment->loadOp == VK_ATTACHMENT_LOAD_OP_LOAD && attachment->storeOp == VK_ATTACHMENT_STORE_OP_STORE) {
            if (const auto flags_info = vku::FindStructInPNextChain<VkRenderingAttachmentFlagsInfoKHR>(attachment->pNext);
                !flags_info || !(flags_info->flags & VK_RENDERING_ATTACHMENT_INPUT_ATTACHMENT_FEEDBACK_BIT_KHR)) {
                const LogObjectList objlist(last_bound_state.cb_state.Handle());
                skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_LOCAL_READ_11797), objlist, loc,
                                 "%s is being used as a feedback loop, but it has no VkRenderingAttachmentFlagsInfoKHR or the "
                                 "VK_RENDERING_ATTACHMENT_INPUT_ATTACHMENT_FEEDBACK_BIT_KHR flag is not set.",
                                 loc.Fields().c_str());
            }
        }
    };

    for (uint32_t i = 0; i < rp_state.dynamic_rendering_begin_rendering_info.colorAttachmentCount; ++i) {
        validate_feedback_loop(rp_state.dynamic_rendering_begin_rendering_info.pColorAttachments + i,
                               loc.dot(vvl::Struct::VkRenderingInfo, vvl::Field::pColorAttachments, i));
    }
    validate_feedback_loop(rp_state.dynamic_rendering_begin_rendering_info.pDepthAttachment,
                           loc.dot(vvl::Struct::VkRenderingInfo, vvl::Field::pDepthAttachment));
    validate_feedback_loop(rp_state.dynamic_rendering_begin_rendering_info.pStencilAttachment,
                           loc.dot(vvl::Struct::VkRenderingInfo, vvl::Field::pStencilAttachment));

    return skip;
}

bool CoreChecks::ValidateDrawPipelineDynamicRenderpassUnusedAttachments(const LastBound& last_bound_state,
                                                                        const vvl::Pipeline& pipeline,
                                                                        const vvl::RenderPass& rp_state,
                                                                        const VkPipelineRenderingCreateInfo& pipeline_rendering_ci,
                                                                        const Location& loc) const {
    bool skip = false;
    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;

    if (!enabled_features.dynamicRenderingUnusedAttachments) {
        const auto color_attachment_count = pipeline_rendering_ci.colorAttachmentCount;
        const auto rendering_color_attachment_count = cb_state.GetDynamicRenderingColorAttachmentCount();
        if (color_attachment_count != rendering_color_attachment_count) {
            const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
            skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_COLOR_COUNT_06179), objlist, loc,
                             "Currently bound %s was created with VkPipelineRenderingCreateInfo::colorAttachmentCount (%" PRIu32
                             ") which must be equal to %s::colorAttachmentCount (%" PRIu32
                             ").\nThe dynamicRenderingUnusedAttachments feature allows a way to remove this restriction.",
                             FormatHandle(pipeline).c_str(), pipeline_rendering_ci.colorAttachmentCount,
                             rp_state.use_dynamic_rendering ? "VkRenderingInfo" : "VkCommandBufferInheritanceRenderingInfo",
                             rendering_color_attachment_count);
        }
    }

    const VkRenderingInfo& rendering_info = *(rp_state.dynamic_rendering_begin_rendering_info.ptr());

    for (uint32_t i = 0; i < rendering_info.colorAttachmentCount; ++i) {
        if (enabled_features.dynamicRenderingUnusedAttachments) {
            if (rendering_info.pColorAttachments[i].imageView != VK_NULL_HANDLE) {
                auto view_state = Get<vvl::ImageView>(rendering_info.pColorAttachments[i].imageView);
                ASSERT_AND_CONTINUE(view_state);
                if ((pipeline_rendering_ci.colorAttachmentCount > i) && (view_state->create_info.format != VK_FORMAT_UNDEFINED) &&
                    (pipeline_rendering_ci.pColorAttachmentFormats[i] != VK_FORMAT_UNDEFINED) &&
                    (view_state->create_info.format != pipeline_rendering_ci.pColorAttachmentFormats[i])) {
                    // This VU is hard to word, some extra context for future help
                    // https://gitlab.khronos.org/vulkan/vulkan/-/issues/4379
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_08911),
                                     objlist, loc,
                                     "VkRenderingInfo::pColorAttachments[%" PRIu32
                                     "].imageView format (%s) must match the corresponding format in "
                                     "VkPipelineRenderingCreateInfo::pColorAttachmentFormats[%" PRIu32
                                     "] (%s) "
                                     "when both are not VK_FORMAT_UNDEFINED",
                                     i, string_VkFormat(view_state->create_info.format), i,
                                     string_VkFormat(pipeline_rendering_ci.pColorAttachmentFormats[i]));
                }
            }
        } else {
            if (rendering_info.pColorAttachments[i].imageView == VK_NULL_HANDLE) {
                if ((pipeline_rendering_ci.colorAttachmentCount > i) &&
                    (pipeline_rendering_ci.pColorAttachmentFormats[i] != VK_FORMAT_UNDEFINED)) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |=
                        LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_UNDEFINED_COLOR_FORMATS_08912),
                                 objlist, loc,
                                 "VkRenderingInfo::pColorAttachments[%" PRIu32
                                 "].imageView is VK_NULL_HANDLE, but the corresponding format in "
                                 "VkPipelineRenderingCreateInfo::pColorAttachmentFormats[%" PRIu32
                                 "] is %s (but must be VK_FORMAT_UNDEFINED).",
                                 i, i, string_VkFormat(pipeline_rendering_ci.pColorAttachmentFormats[i]));
                }
            } else {
                auto view_state = Get<vvl::ImageView>(rendering_info.pColorAttachments[i].imageView);
                ASSERT_AND_CONTINUE(view_state);
                if ((pipeline_rendering_ci.colorAttachmentCount > i) &&
                    view_state->create_info.format != pipeline_rendering_ci.pColorAttachmentFormats[i]) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_COLOR_FORMATS_08910),
                                     objlist, loc,
                                     "VkRenderingInfo::pColorAttachments[%" PRIu32
                                     "].imageView format (%s) must match the corresponding format in "
                                     "VkPipelineRenderingCreateInfo::pColorAttachmentFormats[%" PRIu32 "] (%s)",
                                     i, string_VkFormat(view_state->create_info.format), i,
                                     string_VkFormat(pipeline_rendering_ci.pColorAttachmentFormats[i]));
                }
            }
        }
    }

    if (rendering_info.pDepthAttachment) {
        if (enabled_features.dynamicRenderingUnusedAttachments) {
            if (rendering_info.pDepthAttachment->imageView != VK_NULL_HANDLE) {
                auto view_state = Get<vvl::ImageView>(rendering_info.pDepthAttachment->imageView);
                if (view_state && (view_state->create_info.format != VK_FORMAT_UNDEFINED) &&
                    (pipeline_rendering_ci.depthAttachmentFormat != VK_FORMAT_UNDEFINED) &&
                    (view_state->create_info.format != pipeline_rendering_ci.depthAttachmentFormat)) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_08915),
                                     objlist, loc,
                                     "VkRenderingInfo::pDepthAttachment->imageView format (%s) must match the corresponding format "
                                     "in VkPipelineRenderingCreateInfo::depthAttachmentFormat (%s) "
                                     "if both are not VK_FORMAT_UNDEFINED",
                                     string_VkFormat(view_state->create_info.format),
                                     string_VkFormat(pipeline_rendering_ci.depthAttachmentFormat));
                }
            }
        } else {
            if (rendering_info.pDepthAttachment->imageView == VK_NULL_HANDLE) {
                if (pipeline_rendering_ci.depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |=
                        LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_UNDEFINED_DEPTH_FORMAT_08913),
                                 objlist, loc,
                                 "VkRenderingInfo::pDepthAttachment.imageView is VK_NULL_HANDLE, but the corresponding format in "
                                 "VkPipelineRenderingCreateInfo::depthAttachmentFormat is %s.",
                                 string_VkFormat(pipeline_rendering_ci.depthAttachmentFormat));
                }
            } else {
                auto view_state = Get<vvl::ImageView>(rendering_info.pDepthAttachment->imageView);
                if (view_state && view_state->create_info.format != pipeline_rendering_ci.depthAttachmentFormat) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_DEPTH_FORMAT_08914), objlist,
                                     loc,
                                     "VkRenderingInfo::pDepthAttachment->imageView format (%s) must match the corresponding format "
                                     "in VkPipelineRenderingCreateInfo::depthAttachmentFormat (%s)",
                                     string_VkFormat(view_state->create_info.format),
                                     string_VkFormat(pipeline_rendering_ci.depthAttachmentFormat));
                }
            }
        }
    } else if (rp_state.use_dynamic_rendering_inherited) {
        if (rp_state.inheritance_rendering_info.depthAttachmentFormat != pipeline_rendering_ci.depthAttachmentFormat &&
            !enabled_features.dynamicRenderingUnusedAttachments) {
            const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
            skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_DEPTH_FORMAT_08914), objlist, loc,
                             "VkCommandBufferInheritanceRenderingInfo::depthAttachmentFormat (%s) must match the corresponding "
                             "format in VkPipelineRenderingCreateInfo::depthAttachmentFormat (%s)",
                             string_VkFormat(rp_state.inheritance_rendering_info.depthAttachmentFormat),
                             string_VkFormat(pipeline_rendering_ci.depthAttachmentFormat));
        }
    }

    if (rendering_info.pStencilAttachment) {
        if (enabled_features.dynamicRenderingUnusedAttachments) {
            if (rendering_info.pStencilAttachment->imageView != VK_NULL_HANDLE) {
                auto view_state = Get<vvl::ImageView>(rendering_info.pStencilAttachment->imageView);
                if (view_state && (view_state->create_info.format != VK_FORMAT_UNDEFINED) &&
                    (pipeline_rendering_ci.stencilAttachmentFormat != VK_FORMAT_UNDEFINED) &&
                    (view_state->create_info.format != pipeline_rendering_ci.stencilAttachmentFormat)) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_08918),
                                     objlist, loc,
                                     "VkRenderingInfo::pStencilAttachment->imageView format (%s) must match the corresponding "
                                     "format in VkPipelineRenderingCreateInfo::stencilAttachmentFormat (%s) "
                                     "if both are not VK_FORMAT_UNDEFINED",
                                     string_VkFormat(view_state->create_info.format),
                                     string_VkFormat(pipeline_rendering_ci.stencilAttachmentFormat));
                }
            }
        } else {
            if (rendering_info.pStencilAttachment->imageView == VK_NULL_HANDLE) {
                if (pipeline_rendering_ci.stencilAttachmentFormat != VK_FORMAT_UNDEFINED) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |=
                        LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_UNDEFINED_STENCIL_FORMAT_08916),
                                 objlist, loc,
                                 "VkRenderingInfo::pStencilAttachment.imageView is VK_NULL_HANDLE, but the "
                                 "corresponding format in "
                                 "VkPipelineRenderingCreateInfo::stencilAttachmentFormat is %s.",
                                 string_VkFormat(pipeline_rendering_ci.stencilAttachmentFormat));
                }
            } else {
                auto view_state = Get<vvl::ImageView>(rendering_info.pStencilAttachment->imageView);
                if (view_state && view_state->create_info.format != pipeline_rendering_ci.stencilAttachmentFormat) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_STENCIL_FORMAT_08917),
                                     objlist, loc,
                                     "VkRenderingInfo::pStencilAttachment->imageView format (%s) must match the corresponding "
                                     "format in VkPipelineRenderingCreateInfo::stencilAttachmentFormat (%s)",
                                     string_VkFormat(view_state->create_info.format),
                                     string_VkFormat(pipeline_rendering_ci.stencilAttachmentFormat));
                }
            }
        }
    } else if (rp_state.use_dynamic_rendering_inherited) {
        if (rp_state.inheritance_rendering_info.stencilAttachmentFormat != pipeline_rendering_ci.stencilAttachmentFormat &&
            !enabled_features.dynamicRenderingUnusedAttachments) {
            const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
            skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_STENCIL_FORMAT_08917), objlist, loc,
                             "VkCommandBufferInheritanceRenderingInfo::stencilAttachmentFormat (%s) must match the corresponding "
                             "format in VkPipelineRenderingCreateInfo::stencilAttachmentFormat (%s)",
                             string_VkFormat(rp_state.inheritance_rendering_info.stencilAttachmentFormat),
                             string_VkFormat(pipeline_rendering_ci.stencilAttachmentFormat));
        }
    }

    return skip;
}

bool CoreChecks::ValidateDrawPipelineDynamicRenderpassFragmentShadingRate(const LastBound& last_bound_state,
                                                                          const vvl::Pipeline& pipeline,
                                                                          const vvl::RenderPass& rp_state,
                                                                          const Location& loc) const {
    bool skip = false;

    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;
    auto rendering_fragment_shading_rate_attachment_info =
        vku::FindStructInPNextChain<VkRenderingFragmentShadingRateAttachmentInfoKHR>(
            rp_state.dynamic_rendering_begin_rendering_info.pNext);
    if (rendering_fragment_shading_rate_attachment_info &&
        (rendering_fragment_shading_rate_attachment_info->imageView != VK_NULL_HANDLE)) {
        if (!(pipeline.create_flags & VK_PIPELINE_CREATE_2_RENDERING_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR)) {
            const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
            skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_FSR_06183), objlist, loc,
                             "Currently bound graphics pipeline %s must have been created with "
                             "VK_PIPELINE_CREATE_RENDERING_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR",
                             FormatHandle(pipeline).c_str());
        }
    }

    auto rendering_fragment_shading_rate_density_map = vku::FindStructInPNextChain<VkRenderingFragmentDensityMapAttachmentInfoEXT>(
        rp_state.dynamic_rendering_begin_rendering_info.pNext);
    if (rendering_fragment_shading_rate_density_map && (rendering_fragment_shading_rate_density_map->imageView != VK_NULL_HANDLE)) {
        if (!(pipeline.create_flags & VK_PIPELINE_RASTERIZATION_STATE_CREATE_FRAGMENT_DENSITY_MAP_ATTACHMENT_BIT_EXT)) {
            const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
            skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_FDM_06184), objlist, loc,
                             "Currently bound graphics pipeline %s must have been created with "
                             "VK_PIPELINE_RASTERIZATION_STATE_CREATE_FRAGMENT_DENSITY_MAP_ATTACHMENT_BIT_EXT",
                             FormatHandle(pipeline).c_str());
        }
    }

    return skip;
}

bool CoreChecks::ValidateDrawPipelineDynamicRenderpassLegacyDithering(const LastBound& last_bound_state,
                                                                      const vvl::Pipeline& pipeline,
                                                                      const vvl::RenderPass& rp_state, const Location& loc) const {
    bool skip = false;
    if (!enabled_features.legacyDithering) {
        return skip;
    }
    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;

    const VkRenderingFlags render_flags = rp_state.GetRenderingFlags();
    const bool has_legacy_dithering_pipeline =
        ((pipeline.create_flags & VK_PIPELINE_CREATE_2_ENABLE_LEGACY_DITHERING_BIT_EXT) != 0) ||
        (pipeline.fragment_output_state && pipeline.fragment_output_state->legacy_dithering_enabled);
    const bool has_legacy_dithering_rendering = (render_flags & VK_RENDERING_ENABLE_LEGACY_DITHERING_BIT_EXT) != 0;
    if (has_legacy_dithering_pipeline && !has_legacy_dithering_rendering) {
        const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
        skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_DITHERING_09643), objlist, loc,
                         "The bound graphics pipeline was created with VK_PIPELINE_CREATE_2_ENABLE_LEGACY_DITHERING_BIT_EXT "
                         "but the %s::flags was missing "
                         "VK_RENDERING_ENABLE_LEGACY_DITHERING_BIT_EXT (value used %s).",
                         rp_state.use_dynamic_rendering_inherited ? "VkCommandBufferInheritanceRenderingInfo"
                                                                  : "vkCmdBeginRendering::pRenderingInfo",
                         string_VkRenderingFlags(render_flags).c_str());
    } else if (!has_legacy_dithering_pipeline && has_legacy_dithering_rendering) {
        const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
        skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_DITHERING_09642), objlist, loc,
                         "%s::flags was set with VK_RENDERING_ENABLE_LEGACY_DITHERING_BIT_EXT, but the bound graphics "
                         "pipeline was not created with VK_PIPELINE_CREATE_2_ENABLE_LEGACY_DITHERING_BIT_EXT flag (value used %s).",
                         rp_state.use_dynamic_rendering_inherited ? "VkCommandBufferInheritanceRenderingInfo"
                                                                  : "vkCmdBeginRendering::pRenderingInfo",
                         string_VkPipelineCreateFlags2(pipeline.create_flags).c_str());
    }
    return skip;
}

bool CoreChecks::ValidateDrawPipelineDynamicRenderpassSampleCount(const LastBound& last_bound_state, const vvl::Pipeline& pipeline,
                                                                  const vvl::RenderPass& rp_state, const Location& loc) const {
    bool skip = false;
    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;
    auto& cb_sub_state = core::SubState(cb_state);

    // VkAttachmentSampleCountInfoAMD == VkAttachmentSampleCountInfoNV
    if (auto sample_count_info = vku::FindStructInPNextChain<VkAttachmentSampleCountInfoAMD>(pipeline.GetCreateInfoPNext())) {
        for (uint32_t i = 0; i < cb_state.active_attachments.size(); ++i) {
            const auto& attachment_info = cb_state.active_attachments[i];
            const auto* attachment = attachment_info.image_view;
            if (!attachment) {
                continue;
            } else if (attachment_info.IsColor()) {
                const bool has_color_samples = sample_count_info->pColorAttachmentSamples &&
                                               attachment_info.type_index < sample_count_info->colorAttachmentCount;
                if (has_color_samples &&
                    sample_count_info->pColorAttachmentSamples[attachment_info.type_index] != attachment->samples) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), attachment->Handle(),
                                                attachment->create_info.image);
                    skip |= LogError(
                        CreateActionVuid(loc.function, vvl::ActionVUID::DYNAMIC_RENDERING_COLOR_SAMPLE_06185), objlist, loc,
                        "%s was created with an image sample count (%s) which does not match the corresponding "
                        "VkAttachmentSampleCountInfoAMD::pColorAttachmentSamples[%" PRIu32 "] (%s)",
                        attachment_info.Describe(cb_state, i).c_str(), string_VkSampleCountFlagBits(attachment->samples),
                        attachment_info.type_index,
                        string_VkSampleCountFlagBits(sample_count_info->pColorAttachmentSamples[attachment_info.type_index]));
                }
            } else if (attachment_info.IsDepthOrStencil() &&
                       sample_count_info->depthStencilAttachmentSamples != attachment->samples) {
                const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), attachment->Handle(),
                                            attachment->create_info.image);
                vvl::ActionVUID vuid = attachment_info.IsDepth() ? vvl::ActionVUID::DYNAMIC_RENDERING_DEPTH_SAMPLE_06186
                                                                 : vvl::ActionVUID::DYNAMIC_RENDERING_STENCIL_SAMPLE_06187;
                skip |= LogError(CreateActionVuid(loc.function, vuid), objlist, loc,
                                 "%s was created with an image sample count (%s) which does not match the corresponding "
                                 "VkAttachmentSampleCountInfoAMD::depthStencilAttachmentSamples (%s)",
                                 attachment_info.Describe(cb_state, i).c_str(), string_VkSampleCountFlagBits(attachment->samples),
                                 string_VkSampleCountFlagBits(sample_count_info->depthStencilAttachmentSamples));
            }
        }
    } else if (!enabled_features.multisampledRenderToSingleSampled && !enabled_features.externalFormatResolve &&
               !cb_sub_state.custom_resolve.started && !last_bound_state.IsDynamic(CB_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT)) {
        // this will only be for static pipeline values, dynamic is caught elsewhere for shader object as well
        const VkSampleCountFlagBits rasterization_samples = last_bound_state.GetRasterizationSamples();

        for (uint32_t i = 0; i < cb_state.active_attachments.size(); ++i) {
            const auto& attachment_info = cb_state.active_attachments[i];
            const auto* attachment = attachment_info.image_view;
            if (attachment && rasterization_samples != attachment->samples &&
                (attachment_info.IsColor() || attachment_info.IsDepthOrStencil())) {
                vvl::ActionVUID vuid = attachment_info.IsColor()   ? vvl::ActionVUID::DYNAMIC_RENDERING_07285
                                       : attachment_info.IsDepth() ? vvl::ActionVUID::DYNAMIC_RENDERING_07286
                                                                   : vvl::ActionVUID::DYNAMIC_RENDERING_07287;
                const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), attachment->Handle(),
                                            attachment->create_info.image);
                skip |= LogError(CreateActionVuid(loc.function, vuid), objlist, loc,
                                 "%s was created with an image sample count (%s) which does not match the corresponding "
                                 "VkPipelineMultisampleStateCreateInfo::rasterizationSamples (%s)",
                                 attachment_info.Describe(cb_state, i).c_str(), string_VkSampleCountFlagBits(attachment->samples),
                                 string_VkSampleCountFlagBits(rasterization_samples));
            }
        }
    }
    return skip;
}

bool CoreChecks::ValidatePipelineVertexDivisors(const vvl::Pipeline& pipeline, const Location& create_info_loc) const {
    bool skip = false;
    const auto* input_state = pipeline.InputState();
    if (!input_state) {
        return skip;
    }
    const auto divisor_state_info = vku::FindStructInPNextChain<VkPipelineVertexInputDivisorStateCreateInfo>(input_state->pNext);
    if (!divisor_state_info) {
        return skip;
    }

    const Location vertex_input_loc = create_info_loc.dot(Field::pVertexInputState);
    // Can use raw Pipeline state values because not using the stride (which can be dynamic with
    // VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE)
    const auto& binding_descriptions = pipeline.GraphicsCreateInfo().pVertexInputState->pVertexBindingDescriptions;
    const auto& binding_desc_count = pipeline.GraphicsCreateInfo().pVertexInputState->vertexBindingDescriptionCount;
    for (uint32_t j = 0; j < divisor_state_info->vertexBindingDivisorCount; j++) {
        const Location divisor_loc =
            vertex_input_loc.pNext(Struct::VkVertexInputBindingDivisorDescription, Field::pVertexBindingDivisors, j);
        const auto* vibdd = &(divisor_state_info->pVertexBindingDivisors[j]);
        if (vibdd->binding >= phys_dev_props.limits.maxVertexInputBindings) {
            skip |= LogError("VUID-VkVertexInputBindingDivisorDescription-binding-01869", device, divisor_loc.dot(Field::binding),
                             "(%" PRIu32 ") exceeds device maxVertexInputBindings (%" PRIu32 ").", vibdd->binding,
                             phys_dev_props.limits.maxVertexInputBindings);
        }
        if (vibdd->divisor > phys_dev_props_core14.maxVertexAttribDivisor) {
            skip |= LogError("VUID-VkVertexInputBindingDivisorDescription-divisor-01870", device, divisor_loc.dot(Field::divisor),
                             "(%" PRIu32 ") exceeds device maxVertexAttribDivisor (%" PRIu32 ").", vibdd->divisor,
                             phys_dev_props_core14.maxVertexAttribDivisor);
        }
        if ((0 == vibdd->divisor) && !enabled_features.vertexAttributeInstanceRateZeroDivisor) {
            skip |=
                LogError("VUID-VkVertexInputBindingDivisorDescription-vertexAttributeInstanceRateZeroDivisor-02228", device,
                         divisor_loc.dot(Field::divisor),
                         "is (%" PRIu32 ") but vertexAttributeInstanceRateZeroDivisor feature was not enabled.", vibdd->divisor);
        }
        if ((1 != vibdd->divisor) && !enabled_features.vertexAttributeInstanceRateDivisor) {
            skip |= LogError("VUID-VkVertexInputBindingDivisorDescription-vertexAttributeInstanceRateDivisor-02229", device,
                             divisor_loc.dot(Field::divisor),
                             "is (%" PRIu32 ") but vertexAttributeInstanceRateDivisor feature was not enabled.", vibdd->divisor);
        }

        // Find the corresponding binding description and validate input rate setting
        bool found_input_rate = false;
        for (size_t k = 0; k < binding_desc_count; k++) {
            if ((vibdd->binding == binding_descriptions[k].binding) &&
                (VK_VERTEX_INPUT_RATE_INSTANCE == binding_descriptions[k].inputRate)) {
                found_input_rate = true;
                break;
            }
        }
        if (!found_input_rate) {  // Description not found, or has incorrect inputRate value
            skip |= LogError("VUID-VkVertexInputBindingDivisorDescription-inputRate-01871", device, divisor_loc.dot(Field::binding),
                             "is %" PRIu32 ", but inputRate is not VK_VERTEX_INPUT_RATE_INSTANCE.", vibdd->binding);
        }
    }
    return skip;
}

bool CoreChecks::ValidateGraphicsPipelineDerivatives(PipelineStates& pipeline_states, uint32_t pipe_index,
                                                     const Location& loc) const {
    bool skip = false;
    const auto& pipeline = *pipeline_states[pipe_index].get();
    // If create derivative bit is set, check that we've specified a base
    // pipeline correctly, and that the base pipeline was created to allow
    // derivatives.
    if (pipeline.create_flags & VK_PIPELINE_CREATE_2_DERIVATIVE_BIT) {
        std::shared_ptr<const vvl::Pipeline> base_pipeline;
        const auto& pipeline_ci = pipeline.GraphicsCreateInfo();
        const VkPipeline base_handle = pipeline_ci.basePipelineHandle;
        const int32_t base_index = pipeline_ci.basePipelineIndex;
        if (base_index != -1 && base_index < static_cast<int32_t>(pipeline_states.size())) {
            if (static_cast<uint32_t>(base_index) >= pipe_index) {
                skip |= LogError("VUID-vkCreateGraphicsPipelines-flags-00720", base_handle, loc,
                                 "base pipeline (index %" PRId32
                                 ") must occur earlier in array than derivative pipeline (index %" PRIu32 ").",
                                 base_index, pipe_index);
            } else {
                base_pipeline = pipeline_states[base_index];
            }
        } else if (base_handle != VK_NULL_HANDLE) {
            base_pipeline = Get<vvl::Pipeline>(base_handle);
        }

        if (base_pipeline && !(base_pipeline->create_flags & VK_PIPELINE_CREATE_2_ALLOW_DERIVATIVES_BIT)) {
            skip |= LogError("VUID-vkCreateGraphicsPipelines-flags-00721", base_pipeline->Handle(), loc,
                             "base pipeline does not allow derivatives.");
        }
    }
    return skip;
}

bool CoreChecks::ValidateMultiViewShaders(const vvl::Pipeline& pipeline, const Location& multiview_loc, uint32_t view_mask,
                                          bool dynamic_rendering) const {
    bool skip = false;
    const VkShaderStageFlags stages = pipeline.create_info_shaders;
    if (!enabled_features.multiviewTessellationShader &&
        (stages & (VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT))) {
        const char* vuid = dynamic_rendering ? "VUID-VkGraphicsPipelineCreateInfo-renderPass-06057"
                                             : "VUID-VkGraphicsPipelineCreateInfo-renderPass-06047";
        skip |= LogError(vuid, device, multiview_loc,
                         "is 0x%" PRIx32
                         " and pStages contains tessellation shaders, but the multiviewTessellationShader feature was not enabled.",
                         view_mask);
    }

    if (!enabled_features.multiviewGeometryShader && (stages & VK_SHADER_STAGE_GEOMETRY_BIT)) {
        const char* vuid = dynamic_rendering ? "VUID-VkGraphicsPipelineCreateInfo-renderPass-06058"
                                             : "VUID-VkGraphicsPipelineCreateInfo-renderPass-06048";
        skip |= LogError(vuid, device, multiview_loc,
                         "is 0x%" PRIx32
                         " and pStages contains geometry shader, but the multiviewGeometryShader feature was not enabled.",
                         view_mask);
    }

    if (stages & VK_SHADER_STAGE_MESH_BIT_EXT) {
        if (!enabled_features.multiviewMeshShader) {
            const char* vuid = dynamic_rendering ? "VUID-VkGraphicsPipelineCreateInfo-renderPass-07720"
                                                 : "VUID-VkGraphicsPipelineCreateInfo-renderPass-07064";
            skip |=
                LogError(vuid, device, multiview_loc,
                         "is 0x%" PRIx32 " and pStages contains mesh shader, but the multiviewMeshShader feature was not enabled.",
                         view_mask);
        }

        uint32_t highest_view_bit = static_cast<uint32_t>(MostSignificantBit(view_mask));
        if (highest_view_bit > 0 && highest_view_bit >= phys_dev_ext_props.mesh_shader_props_ext.maxMeshMultiviewViewCount) {
            const char* vuid = dynamic_rendering ? "VUID-VkGraphicsPipelineCreateInfo-renderPass-12326"
                                                 : "VUID-VkGraphicsPipelineCreateInfo-renderPass-12325";
            skip |= LogError(vuid, device, multiview_loc,
                             "is 0x%" PRIx32 ", its highest bit (%" PRIu32
                             ") is not less than VkPhysicalDeviceMeshShaderPropertiesEXT::maxMeshMultiviewViewCount (%" PRIu32 ").",
                             view_mask, highest_view_bit, phys_dev_ext_props.mesh_shader_props_ext.maxMeshMultiviewViewCount);
        }
    }

    for (const ShaderStageState& stage : pipeline.stage_states) {
        if (!stage.HasSpirv()) {
            continue;
        }

        if (stage.spirv_state->static_data_.has_built_in_layer) {
            // Special case for GLSL and Mesh Shading discussed in https://gitlab.khronos.org/vulkan/vulkan/-/issues/4194
            const char* vuid = dynamic_rendering ? "VUID-VkGraphicsPipelineCreateInfo-renderPass-06059"
                                                 : "VUID-VkGraphicsPipelineCreateInfo-renderPass-06050";
            skip |= LogError(vuid, device, multiview_loc, "is 0x%" PRIx32 " but %s stage contains a Layer decorated OpVariable.%s",
                             view_mask, string_VkShaderStageFlagBits(stage.GetStage()),
                             stage.GetStage() == VK_SHADER_STAGE_MESH_BIT_EXT
                                 ? "(If hitting this with Mesh Shading using GLSL you can explicitly leave out Layer, see "
                                   "https://godbolt.org/z/av9zsxT8G as an example)"
                                 : "");
        }
    }

    return skip;
}

bool CoreChecks::ValidateMultiviewPerViewViewports(const vvl::Pipeline& pipeline, const vvl::RenderPass& rp_state,
                                                   const Location& create_info_loc) const {
    bool skip = false;
    const auto* viewport_state = pipeline.ViewportState();
    if (!viewport_state) {
        return skip;
    }

    if (rp_state.UsesDynamicRendering()) {
        const uint32_t view_mask = rp_state.dynamic_pipeline_rendering_create_info.viewMask;
        if (view_mask == 0) {
            return skip;
        }
        const uint32_t msb = (uint32_t)MostSignificantBit(view_mask);
        if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_VIEWPORT_WITH_COUNT) && msb >= viewport_state->viewportCount) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-multiviewPerViewViewports-12249", rp_state.Handle(),
                             create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::viewMask),
                             "(0x%" PRIx32 ") most significant bit index (%" PRIu32
                             ") is not less than pViewportState->viewportCount (%" PRIu32 ")",
                             view_mask, msb, viewport_state->viewportCount);
        }
        if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_SCISSOR_WITH_COUNT) && msb >= viewport_state->scissorCount) {
            skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-multiviewPerViewViewports-12250", rp_state.Handle(),
                             create_info_loc.pNext(Struct::VkPipelineRenderingCreateInfo, Field::viewMask),
                             "(0x%" PRIx32 ") most significant bit index (%" PRIu32
                             ") is not less than pViewportState->scissorCount (%" PRIu32 ")",
                             view_mask, msb, viewport_state->scissorCount);
        }
    } else {
        for (uint32_t i = 0; i < rp_state.create_info.subpassCount; i++) {
            const uint32_t view_mask = rp_state.create_info.pSubpasses[i].viewMask;
            if (view_mask == 0) {
                continue;
            }
            const uint32_t msb = (uint32_t)MostSignificantBit(view_mask);

            if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_VIEWPORT_WITH_COUNT) && msb >= viewport_state->viewportCount) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07730", rp_state.Handle(),
                                 create_info_loc.dot(Field::renderPass),
                                 "was created with VkRenderPassMultiviewCreateInfo::pViewMask[%" PRIu32 "] of 0x%" PRIx32
                                 " which most significant bit index (%" PRIu32
                                 ") is not less than pViewportState->viewportCount (%" PRIu32 ")",
                                 i, view_mask, msb, viewport_state->viewportCount);
            }
            if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_SCISSOR_WITH_COUNT) && msb >= viewport_state->scissorCount) {
                skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07731", rp_state.Handle(),
                                 create_info_loc.dot(Field::renderPass),
                                 "was created with VkRenderPassMultiviewCreateInfo::pViewMask[%" PRIu32 "] of 0x%" PRIx32
                                 " which most significant bit index (%" PRIu32
                                 ") is not less than pViewportState->scissorCount (%" PRIu32 ")",
                                 i, view_mask, msb, viewport_state->scissorCount);
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidateDrawPipelineFramebuffer(const vvl::CommandBuffer& cb_state, const vvl::Pipeline& pipeline,
                                                 const Location& loc) const {
    bool skip = false;
    if (!cb_state.active_framebuffer) return skip;

    // Verify attachments for unprotected/protected command buffer.
    if (enabled_features.protectedMemory == VK_TRUE) {
        for (uint32_t i = 0; i < cb_state.active_attachments.size(); i++) {
            const auto* view_state = cb_state.active_attachments[i].image_view;
            const auto& subpass = cb_state.active_subpasses[i];
            if (subpass.used && view_state && !view_state->Destroyed()) {
                std::string image_desc = " Image is ";
                image_desc.append(string_VkImageUsageFlagBits(subpass.usage));
                // Because inputAttachment is read only, it doesn't need to care protected command buffer case.
                // Some Functions could not be protected. See VUID 02711.

                std::string temp_vuid = CreateActionVuid(loc.function, vvl::ActionVUID::CB_UNPROTECTED_02707);
                skip |= ValidateProtectedImage(cb_state, *view_state->image_state, loc, temp_vuid.c_str(), image_desc.c_str());
                // Indirect commands already have 02711 which will be triggered already
                if (subpass.usage != VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT && !IsCommandIndirect(loc.function)) {
                    temp_vuid = CreateActionVuid(loc.function, vvl::ActionVUID::CB_PROTECTED_02712);
                    skip |=
                        ValidateUnprotectedImage(cb_state, *view_state->image_state, loc, temp_vuid.c_str(), image_desc.c_str());
                }
            }
        }
    }

    for (auto& stage_state : pipeline.stage_states) {
        const VkShaderStageFlagBits stage = stage_state.GetStage();
        if (stage_state.entrypoint && stage_state.entrypoint->written_built_in_layer &&
            cb_state.active_framebuffer->create_info.layers == 1) {
            if (cb_state.active_render_pass && cb_state.active_render_pass->has_multiview_enabled) {
                // If using MultiView, you should already have hit an error that Framebuffer Layer must be 1, but due to things like
                // https://gitlab.khronos.org/vulkan/vulkan/-/issues/4194 we should check here and ignore if things are invalid
                // already
                break;
            }
            LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
            skip |= LogUndefinedValue("Undefined-Value-Layer-Written", objlist, loc,
                                      "Shader stage %s writes to Layer (gl_Layer) but the framebuffer was created with "
                                      "VkFramebufferCreateInfo::layer of 1, this write will have an undefined value set to it.",
                                      string_VkShaderStageFlags(stage).c_str());
        }
    }
    return skip;
}

bool CoreChecks::ValidateDrawPipelineFragmentDensityMapLayered(const vvl::CommandBuffer& cb_state, const vvl::Pipeline& pipeline,
                                                               const vvl::RenderPass& rp_state, const Location& loc) const {
    bool skip = false;
    if (!(pipeline.create_flags & VK_PIPELINE_CREATE_2_PER_LAYER_FRAGMENT_DENSITY_BIT_VALVE)) {
        return skip;
    }

    if (const auto* fragment_density_map_layered =
            vku::FindStructInPNextChain<VkPipelineFragmentDensityMapLayeredCreateInfoVALVE>(pipeline.GetCreateInfoPNext())) {
        if (rp_state.UsesDynamicRendering()) {
            if (rp_state.GetRenderingFlags() & VK_RENDERING_PER_LAYER_FRAGMENT_DENSITY_BIT_VALVE) {
                if (rp_state.dynamic_rendering_begin_rendering_info.layerCount >
                    fragment_density_map_layered->maxFragmentDensityMapLayers) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::FDM_LAYERED_10831), objlist, loc,
                                     "the vkCmdBeginRendering set layerCount to %" PRIu32
                                     " which is greater than the last bound pipeline maxFragmentDensityMapLayers %" PRIu32 ".",
                                     rp_state.dynamic_rendering_begin_rendering_info.layerCount,
                                     fragment_density_map_layered->maxFragmentDensityMapLayers);
                }
            }
        } else if (cb_state.active_framebuffer) {
            if (rp_state.create_info.flags & VK_RENDER_PASS_CREATE_PER_LAYER_FRAGMENT_DENSITY_BIT_VALVE) {
                if (cb_state.active_framebuffer->create_info.layers > fragment_density_map_layered->maxFragmentDensityMapLayers) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), rp_state.Handle(),
                                                cb_state.active_framebuffer->Handle());
                    skip |= LogError(
                        CreateActionVuid(loc.function, vvl::ActionVUID::FDM_LAYERED_10831), objlist, loc,
                        "the bound VkFramebuffer was created with %" PRIu32
                        " layers which is greater than the last bound pipeline maxFragmentDensityMapLayers %" PRIu32 ".",
                        cb_state.active_framebuffer->create_info.layers, fragment_density_map_layered->maxFragmentDensityMapLayers);
                }
            }
        }
    }
    return skip;
}

bool CoreChecks::ValidateDrawPipelineRasterizationState(const LastBound& last_bound_state, const vvl::Pipeline& pipeline,
                                                        const Location& loc) const {
    bool skip = false;
    const vvl::CommandBuffer& cb_state = last_bound_state.cb_state;
    const vvl::RenderPass* rp_state = cb_state.active_render_pass.get();
    ASSERT_AND_RETURN_SKIP(rp_state);

    // Verify that any MSAA request in PSO matches sample# in bound FB
    // Verify that blend is enabled only if supported by subpasses image views format features
    // Skip the check if rasterization is disabled.
    if (pipeline.RasterizationDisabled()) return skip;

    if (rp_state->UsesDynamicRendering()) {
        // TODO: Mirror the below VUs but using dynamic rendering
        const auto dynamic_rendering_info = rp_state->dynamic_rendering_begin_rendering_info;
    } else {
        const bool dynamic_line_raster_mode = pipeline.IsDynamic(CB_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT);
        const bool dynamic_line_stipple_enable = pipeline.IsDynamic(CB_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT);
        if (dynamic_line_stipple_enable || dynamic_line_raster_mode) {
            const auto raster_line_state =
                vku::FindStructInPNextChain<VkPipelineRasterizationLineStateCreateInfo>(pipeline.RasterizationStatePNext());

            const VkLineRasterizationMode line_rasterization_mode = (dynamic_line_raster_mode)
                                                                        ? cb_state.dynamic_state_value.line_rasterization_mode
                                                                        : raster_line_state->lineRasterizationMode;
            const bool stippled_line_enable = (dynamic_line_stipple_enable) ? cb_state.dynamic_state_value.stippled_line_enable
                                                                            : raster_line_state->stippledLineEnable;

            if (stippled_line_enable) {
                if (line_rasterization_mode == VK_LINE_RASTERIZATION_MODE_RECTANGULAR &&
                    (!enabled_features.stippledRectangularLines)) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), rp_state->Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::STIPPLED_RECTANGULAR_07495), objlist, loc,
                                     "lineRasterizationMode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR (set %s) with "
                                     "stippledLineEnable (set %s) but the stippledRectangularLines feature is not enabled.",
                                     dynamic_line_raster_mode ? "dynamically" : "in pipeline",
                                     dynamic_line_stipple_enable ? "dynamically" : "in pipeline");
                }
                if (line_rasterization_mode == VK_LINE_RASTERIZATION_MODE_BRESENHAM && (!enabled_features.stippledBresenhamLines)) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), rp_state->Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::STIPPLED_BRESENHAM_07496), objlist, loc,
                                     "lineRasterizationMode = VK_LINE_RASTERIZATION_MODE_BRESENHAM (set %s) with "
                                     "stippledLineEnable (set %s) but the stippledBresenhamLines feature is not enabled.",
                                     dynamic_line_raster_mode ? "dynamically" : "in pipeline",
                                     dynamic_line_stipple_enable ? "dynamically" : "in pipeline");
                }
                if (line_rasterization_mode == VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH &&
                    (!enabled_features.stippledSmoothLines)) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), rp_state->Handle());
                    skip |= LogError(CreateActionVuid(loc.function, vvl::ActionVUID::STIPPLED_SMOOTH_07497), objlist, loc,
                                     "lineRasterizationMode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH (set %s) with "
                                     "stippledLineEnable (set %s) but the stippledSmoothLines feature is not enabled.",
                                     dynamic_line_raster_mode ? "dynamically" : "in pipeline",
                                     dynamic_line_stipple_enable ? "dynamically" : "in pipeline");
                }
                if (line_rasterization_mode == VK_LINE_RASTERIZATION_MODE_DEFAULT &&
                    (!enabled_features.stippledRectangularLines || !phys_dev_props.limits.strictLines)) {
                    const LogObjectList objlist(cb_state.Handle(), pipeline.Handle(), rp_state->Handle());
                    skip |=
                        LogError(CreateActionVuid(loc.function, vvl::ActionVUID::STIPPLED_STRICT_07498), objlist, loc,
                                 "lineRasterizationMode = VK_LINE_RASTERIZATION_MODE_DEFAULT (set %s) with "
                                 "stippledLineEnable (set %s), the stippledRectangularLines features is %s and strictLines is %s.",
                                 dynamic_line_raster_mode ? "dynamically" : "in pipeline",
                                 dynamic_line_stipple_enable ? "dynamically" : "in pipeline",
                                 enabled_features.stippledRectangularLines ? "enabled" : "not enabled",
                                 string_VkBool32(phys_dev_props.limits.strictLines).c_str());
                }
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidatePipelineDiscardRectangleStateCreateInfo(
    const vvl::Pipeline& pipeline, const VkPipelineDiscardRectangleStateCreateInfoEXT& discard_rectangle_state,
    const Location& create_info_loc) const {
    bool skip = false;
    if (!pipeline.IsDynamic(CB_DYNAMIC_STATE_DISCARD_RECTANGLE_ENABLE_EXT) &&
        discard_rectangle_state.discardRectangleCount > phys_dev_ext_props.discard_rectangle_props.maxDiscardRectangles) {
        skip |= LogError("VUID-VkPipelineDiscardRectangleStateCreateInfoEXT-discardRectangleCount-00582", device,
                         create_info_loc.pNext(Struct::VkPipelineDiscardRectangleStateCreateInfoEXT, Field::discardRectangleCount),
                         "(%" PRIu32 ") is not less than maxDiscardRectangles (%" PRIu32 ").",
                         discard_rectangle_state.discardRectangleCount,
                         phys_dev_ext_props.discard_rectangle_props.maxDiscardRectangles);
    }
    return skip;
}

bool CoreChecks::ValidatePipelineAttachmentSampleCountInfo(const vvl::Pipeline& pipeline,
                                                           const VkAttachmentSampleCountInfoAMD& attachment_sample_count_info,
                                                           const Location& create_info_loc) const {
    bool skip = false;
    const uint32_t bits = CountSetBits(static_cast<uint32_t>(attachment_sample_count_info.depthStencilAttachmentSamples));
    if (pipeline.fragment_output_state && attachment_sample_count_info.depthStencilAttachmentSamples != 0 &&
        ((attachment_sample_count_info.depthStencilAttachmentSamples & AllVkSampleCountFlagBits) == 0 || bits > 1)) {
        skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-depthStencilAttachmentSamples-06593", device,
                         create_info_loc.pNext(Struct::VkAttachmentSampleCountInfoAMD, Field::depthStencilAttachmentSamples),
                         "(0x%" PRIx32 ") is invalid.", attachment_sample_count_info.depthStencilAttachmentSamples);
    }
    return skip;
}
