/* Copyright (c) 2023-2026 The Khronos Group Inc.
 * Copyright (c) 2023-2026 Valve Corporation
 * Copyright (c) 2023-2026 LunarG, Inc.
 * Copyright (c) 2025 Arm Limited.
 *
 * 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 "descriptor_validator.h"
#include <vulkan/vulkan_core.h>
#include <vulkan/utility/vk_format_utils.h>
#include <sstream>
#include "containers/container_utils.h"
#include "generated/spirv_grammar_helper.h"
#include "generated/spirv_validation_helper.h"
#include "state_tracker/shader_instruction.h"
#include "state_tracker/shader_stage_state.h"
#include "error_message/error_strings.h"
#include "error_message/spirv_logging.h"
#include "state_tracker/descriptor_sets.h"
#include "state_tracker/cmd_buffer_state.h"
#include "state_tracker/pipeline_state.h"
#include "state_tracker/image_state.h"
#include "state_tracker/buffer_state.h"
#include "state_tracker/render_pass_state.h"
#include "state_tracker/ray_tracing_state.h"
#include "state_tracker/shader_module.h"
#include "state_tracker/tensor_state.h"
#include "drawdispatch/drawdispatch_vuids.h"
#include "utils/action_command_utils.h"
#include "utils/image_utils.h"

namespace vvl {

// This seems like it could be useful elsewhere, but until find another spot, just keep here.
static const char* GetActionType(Func command) {
    if (IsCommandDispatch(command)) {
        return "dispatch";
    } else if (IsCommandTraceRays(command)) {
        return "trace rays";
    } else {
        return "draw";
    }
}

std::string DescriptorValidator::DescribeDescriptor(const spirv::ResourceInterfaceVariable& resource_variable, uint32_t index,
                                                    VkDescriptorType type) const {
    std::ostringstream ss;
    switch (type) {
        case VK_DESCRIPTOR_TYPE_SAMPLER:
            ss << "sampler ";
            break;
        case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
            ss << "combined image sampler ";
            break;
        case VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM:
            ss << "sampled weight image ";
            break;
        case VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM:
            ss << "block match image ";
            break;
        case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
            ss << "sampled image ";
            break;
        case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
            ss << "storage image ";
            break;
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
            ss << "uniform buffer ";
            break;
        case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
            ss << "uniform texel buffer ";
            break;
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
            ss << "storage buffer ";
            break;
        case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
            ss << "storage texel buffer ";
            break;
        case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
            ss << "input attachment ";
            break;
        case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK:
            ss << "inline buffer ";
            break;
        case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR:
        case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV:
            ss << "acceleration structure ";
            break;
        case VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV:
            ss << "partitioned acceleration structure ";
            break;
        case VK_DESCRIPTOR_TYPE_TENSOR_ARM:
            ss << "tensor ";
            break;
        case VK_DESCRIPTOR_TYPE_MUTABLE_EXT:
        case VK_DESCRIPTOR_TYPE_MAX_ENUM:
            break;
    }

    ss << "descriptor [";
    if (!descriptor_set.IsPushDescriptor()) {
        ss << FormatHandle(descriptor_set.Handle()) << ", ";
    }
    ss << "Set " << set_index << ", Binding " << resource_variable.decorations.binding << ", Index " << index;

    // If multiple variables tied to a binding, don't attempt to detect which one
    if (!resource_variable.debug_name.empty()) {
        ss << ", variable \"" << resource_variable.debug_name << "\"";
    }
    ss << "]";
    return ss.str();
}

std::string DescriptorValidator::DescribeInstruction() const {
    std::ostringstream ss;
    // This will not work for the static version because there we care there is an OpVariable defined (not if it is used).
    if (original_spirv) {
        ss << '\n';
        ::spirv::FindShaderSource(ss, *original_spirv, instruction_position_offset, false);
    }
    return ss.str();
}

DescriptorValidator::DescriptorValidator(vvl::DeviceProxy& dev, CommandBuffer& cb_state, DescriptorSet& descriptor_set,
                                         uint32_t set_index, VkFramebuffer framebuffer, const LogObjectList* objlist,
                                         const Location& loc)
    : Logger(dev.debug_report),
      dev_proxy(dev),
      is_gpu_av(dev.container_type == LayerObjectTypeGpuAssisted),
      cb_state(cb_state),
      descriptor_set(descriptor_set),
      framebuffer(framebuffer),
      loc(loc),
      original_spirv(nullptr),  // chance might not find
      instruction_position_offset(0),
      set_index(set_index),
      objlist(objlist) {}

void DescriptorValidator::SetLocationForGpuAv(const Location& gpuav_loc) { loc = LocationCapture(gpuav_loc); }

template <typename T>
bool DescriptorValidator::ValidateDescriptorsStatic(const spirv::ResourceInterfaceVariable& resource_variable,
                                                    const T& binding) const {
    bool skip = false;

    // If there is a descriptor array, we care about the size of the array statically used in the shader, not what was declared in
    // the pipeline layout more info https://gitlab.khronos.org/vulkan/vulkan/-/issues/4383
    uint32_t count = binding.count;
    // if using VARIABLE_DESCRIPTOR_COUNT, we will still use binding.count as that will be the adjusted value
    if (resource_variable.IsArray() && descriptor_set.GetVariableDescriptorCount() == 0) {
        count = resource_variable.array_length;
    }

    if (count > binding.descriptors.size()) {
        // An out-of-range binding index requires an invalid pipeline to reach
        // draw-time validation. Just skip all validation from here as things are already bad.
        return skip;
    }

    for (uint32_t index = 0; !skip && index < count; index++) {
        const auto& descriptor = binding.descriptors[index];

        if (!binding.updated[index]) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle());
            skip |=
                LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s is being used in %s but has never been updated via vkUpdateDescriptorSets() or a similar call.",
                         DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_MAX_ENUM).c_str(),
                         GetActionType(loc.Get().function));
            return skip;  // early return if invalid
        }
        skip |= ValidateDescriptor(resource_variable, index, binding.type, descriptor);
    }
    return skip;
}

bool DescriptorValidator::ValidateBindingStatic(const spirv::ResourceInterfaceVariable& resource_variable,
                                                const DescriptorBinding& binding) const {
    bool skip = false;
    switch (binding.descriptor_class) {
        case DescriptorClass::GeneralBuffer:
            skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const BufferBinding&>(binding));
            break;
        case DescriptorClass::ImageSampler:
            skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const ImageSamplerBinding&>(binding));
            break;
        case DescriptorClass::Image:
            skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const ImageBinding&>(binding));
            break;
        case DescriptorClass::PlainSampler:
            skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const SamplerBinding&>(binding));
            break;
        case DescriptorClass::TexelBuffer:
            skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const TexelBinding&>(binding));
            break;
        case DescriptorClass::AccelerationStructure:
            skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const AccelerationStructureBinding&>(binding));
            break;
        case DescriptorClass::Tensor:
            skip |= ValidateDescriptorsStatic(resource_variable, static_cast<const TensorBinding&>(binding));
            break;
        case DescriptorClass::InlineUniform:
            break;  // Can't validate the descriptor because it may not have been updated.
        case DescriptorClass::Mutable:
            break;  // TODO - is there anything that can be checked here?
        case DescriptorClass::Invalid:
            assert(false);
            break;
    }
    return skip;
}

template <typename T>
bool DescriptorValidator::ValidateDescriptorsDynamic(const spirv::ResourceInterfaceVariable& resource_variable, const T& binding,
                                                     const uint32_t index) {
    bool skip = false;
    const auto& descriptor = binding.descriptors[index];

    if (!binding.updated[index]) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s is being used in %s but has never been updated via vkUpdateDescriptorSets() or a similar call.%s",
                         DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_MAX_ENUM).c_str(),
                         GetActionType(loc.Get().function), DescribeInstruction().c_str());
        return skip;  // early return if invalid
    }
    skip |= ValidateDescriptor(resource_variable, index, binding.type, descriptor);
    return skip;
}

bool DescriptorValidator::ValidateBindingDynamic(const spirv::ResourceInterfaceVariable& resource_variable,
                                                 DescriptorBinding& binding, const uint32_t index) {
    bool skip = false;

    switch (binding.descriptor_class) {
        case DescriptorClass::GeneralBuffer:
            skip |= ValidateDescriptorsDynamic(resource_variable, static_cast<const BufferBinding&>(binding), index);
            break;
        case DescriptorClass::ImageSampler: {
            auto& img_sampler_binding = static_cast<ImageSamplerBinding&>(binding);
            if (dev_proxy.gpuav_settings.validate_image_layout) {
                auto& descriptor = img_sampler_binding.descriptors[index];
                descriptor.UpdateImageLayoutDrawState(cb_state);
            }
            skip |= ValidateDescriptorsDynamic(resource_variable, img_sampler_binding, index);
            break;
        }
        case DescriptorClass::Image: {
            auto& img_binding = static_cast<ImageBinding&>(binding);
            if (dev_proxy.gpuav_settings.validate_image_layout) {
                auto& descriptor = img_binding.descriptors[index];
                descriptor.UpdateImageLayoutDrawState(cb_state);
            }
            skip |= ValidateDescriptorsDynamic(resource_variable, img_binding, index);
            break;
        }
        case DescriptorClass::PlainSampler:
            skip |= ValidateDescriptorsDynamic(resource_variable, static_cast<const SamplerBinding&>(binding), index);
            break;
        case DescriptorClass::TexelBuffer:
            skip |= ValidateDescriptorsDynamic(resource_variable, static_cast<const TexelBinding&>(binding), index);
            break;
        case DescriptorClass::AccelerationStructure:
            skip |= ValidateDescriptorsDynamic(resource_variable, static_cast<const AccelerationStructureBinding&>(binding), index);
            break;
        case DescriptorClass::Tensor:
            skip |= ValidateDescriptorsDynamic(resource_variable, static_cast<const TensorBinding&>(binding), index);
            break;
        case DescriptorClass::InlineUniform:
            break;  // TODO - Can give warning if reading uninitialized data
        case DescriptorClass::Mutable:
            break;  // TODO - is there anything that can be checked here?
        case DescriptorClass::Invalid:
            assert(false);
            break;
    }
    return skip;
}

bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable& resource_variable, const uint32_t index,
                                             VkDescriptorType descriptor_type, const BufferDescriptor& descriptor) const {
    bool skip = false;
    // Verify that buffers are valid
    const VkBuffer buffer = descriptor.GetBuffer();
    auto buffer_node = descriptor.GetBufferState();
    if ((!buffer_node && !dev_proxy.enabled_features.nullDescriptor) || (buffer_node && buffer_node->Destroyed())) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s is using buffer %s that is invalid or has been destroyed.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer).c_str(),
                         DescribeInstruction().c_str());
        // early return if no valid
        return skip;
    }

    // Buffer could be null via nullDescriptor and accessing it is legal
    if (buffer == VK_NULL_HANDLE) {
        return skip;
    }
    if (buffer_node /* && !buffer_node->sparse*/) {
        for (const auto& binding : buffer_node->GetInvalidMemory()) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                             "the %s is using buffer %s that references invalid memory %s.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer).c_str(),
                             FormatHandle(binding->Handle()).c_str(), DescribeInstruction().c_str());
        }
        if (dev_proxy.enabled_features.tileMemoryHeap && resource_variable.IsAccessed()) {
            skip |= dev_proxy.ValidateBoundTileMemory(*descriptor.GetBufferState(), cb_state, loc.Get());
        }
    }
    if (dev_proxy.enabled_features.protectedMemory == VK_TRUE) {
        std::string temp_vuid = CreateActionVuid(loc.Get().function, ActionVUID::CB_UNPROTECTED_02707);
        skip |= dev_proxy.ValidateProtectedBuffer(cb_state, *buffer_node, loc.Get(), temp_vuid.c_str(),
                                                  " (Buffer is in a descriptorSet)");
        if (resource_variable.IsWrittenTo()) {
            temp_vuid = CreateActionVuid(loc.Get().function, ActionVUID::CB_PROTECTED_02712);
            skip |= dev_proxy.ValidateUnprotectedBuffer(cb_state, *buffer_node, loc.Get(), temp_vuid.c_str(),
                                                        " (Buffer is in a descriptorSet)");
        }
    }
    return skip;
}

static const char* SuggestImageViewType(spv::Dim dim, bool is_image_array) {
    VkImageViewType suggest = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
    if (dim == spv::Dim1D) {
        suggest = is_image_array ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D;
    } else if (dim == spv::Dim2D) {
        suggest = is_image_array ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
    } else if (dim == spv::Dim3D) {
        suggest = VK_IMAGE_VIEW_TYPE_3D;
    } else if (dim == spv::DimCube) {
        suggest = is_image_array ? VK_IMAGE_VIEW_TYPE_CUBE_ARRAY : VK_IMAGE_VIEW_TYPE_CUBE;
    } else {
        return "(No matching VkImageViewTyp)";
    }

    return string_VkImageViewType(suggest);
}

bool DescriptorValidator::ValidateImageDescriptorQCOM(const spirv::ResourceInterfaceVariable& resource_variable,
                                                      const uint32_t index, VkDescriptorType descriptor_type,
                                                      const ImageDescriptor& image_descriptor, bool has_sampler) const {
    bool skip = false;
    const VkImageView image_view = image_descriptor.GetImageView();
    const ImageView* image_view_state = image_descriptor.GetImageViewState();

    if ((resource_variable.info.image_insn.image_proc_usage_mask & spirv::ImageProcUsageBit::kImageSampled) != 0) {
        if (!dev_proxy.enabled_features.tileShadingImageProcessing && has_sampler &&
            resource_variable.storage_class == spv::StorageClassTileAttachmentQCOM) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
            skip |= LogError("VUID-RuntimeSpirv-tileShadingImageProcessing-10712", objlist, loc.Get(),
                             "the %s is TileAttachmentQCOM storage class, it is backed by %s which is "
                             "consumed by image-processing instruction, but "
                             "VkPhysicalDeviceTileShadingFeaturesQCOM::tileShadingImageProcessing isn't enabled.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view).c_str(), DescribeInstruction().c_str());
        }
    }

    if ((resource_variable.info.image_insn.image_proc_usage_mask & spirv::ImageProcUsageBit::kSampleWeighted) != 0) {
        if (descriptor_type == VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM &&
            (image_view_state->format_features & VK_FORMAT_FEATURE_2_WEIGHT_IMAGE_BIT_QCOM) == 0) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_SAMPLE_WEIGHTED_QCOM_06972),
                             objlist, loc.Get(),
                             "the %s has %s with format of %s which doesn't support "
                             "VK_FORMAT_FEATURE_2_WEIGHT_IMAGE_BIT_QCOM.\n"
                             "(supported features: %s).%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view).c_str(), string_VkFormat(image_view_state->create_info.format),
                             string_VkFormatFeatureFlags2(image_view_state->format_features).c_str(),
                             DescribeInstruction().c_str());
        } else if (descriptor_type != VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM &&
                   (image_view_state->format_features & VK_FORMAT_FEATURE_2_WEIGHT_SAMPLED_IMAGE_BIT_QCOM) == 0) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_SAMPLE_WEIGHTED_QCOM_06971),
                             objlist, loc.Get(),
                             "the %s has %s with format of %s which doesn't support "
                             "VK_FORMAT_FEATURE_2_WEIGHT_SAMPLED_IMAGE_BIT_QCOM.\n"
                             "(supported features: %s).%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view).c_str(), string_VkFormat(image_view_state->create_info.format),
                             string_VkFormatFeatureFlags2(image_view_state->format_features).c_str(),
                             DescribeInstruction().c_str());
        }
    }

    if ((resource_variable.info.image_insn.image_proc_usage_mask & spirv::ImageProcUsageBit::kBoxFilter) != 0 &&
        (image_view_state->format_features & VK_FORMAT_FEATURE_2_BOX_FILTER_SAMPLED_BIT_QCOM) == 0) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
        skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_BOX_FILTER_QCOM_06973),
                         objlist, loc.Get(),
                         "the %s has %s with format of %s which doesn't support "
                         "VK_FORMAT_FEATURE_2_BOX_FILTER_SAMPLED_BIT_QCOM.\n"
                         "(supported features: %s).%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(image_view).c_str(), string_VkFormat(image_view_state->create_info.format),
                         string_VkFormatFeatureFlags2(image_view_state->format_features).c_str(),
                         DescribeInstruction().c_str());
    }

    if ((resource_variable.info.image_insn.image_proc_usage_mask & spirv::ImageProcUsageBit::kBlockMatchSsd) != 0 &&
        (image_view_state->format_features & VK_FORMAT_FEATURE_2_BLOCK_MATCHING_BIT_QCOM) == 0 &&
        (image_view_state->format_features & VK_FORMAT_FEATURE_2_BLOCK_MATCHING_SXD_BIT_QCOM) == 0) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
        skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_BLOCK_MATCH_SSD_QCOM_06974),
                         objlist, loc.Get(),
                         "the %s has %s with format of %s which doesn't support "
                         "VK_FORMAT_FEATURE_2_BLOCK_MATCHING_BIT_QCOM or VK_FORMAT_FEATURE_2_BLOCK_MATCHING_SXD_BIT_QCOM.\n"
                         "(supported features: %s).%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(image_view).c_str(), string_VkFormat(image_view_state->create_info.format),
                         string_VkFormatFeatureFlags2(image_view_state->format_features).c_str(),
                         DescribeInstruction().c_str());
    }

    if ((resource_variable.info.image_insn.image_proc_usage_mask & spirv::ImageProcUsageBit::kBlockMatchSad) != 0 &&
        (image_view_state->format_features & VK_FORMAT_FEATURE_2_BLOCK_MATCHING_BIT_QCOM) == 0 &&
        (image_view_state->format_features & VK_FORMAT_FEATURE_2_BLOCK_MATCHING_SXD_BIT_QCOM) == 0) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
        skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_BLOCK_MATCH_SAD_QCOM_12420),
                         objlist, loc.Get(),
                         "the %s has %s with format of %s which doesn't support "
                         "VK_FORMAT_FEATURE_2_BLOCK_MATCHING_BIT_QCOM or VK_FORMAT_FEATURE_2_BLOCK_MATCHING_SXD_BIT_QCOM.\n"
                         "(supported features: %s).%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(image_view).c_str(), string_VkFormat(image_view_state->create_info.format),
                         string_VkFormatFeatureFlags2(image_view_state->format_features).c_str(),
                         DescribeInstruction().c_str());
    }

    if ((resource_variable.info.image_insn.image_proc_usage_mask & spirv::ImageProcUsageBit::kBlockMatchWindow) != 0) {
        if ((image_view_state->format_features & VK_FORMAT_FEATURE_2_BLOCK_MATCHING_BIT_QCOM) == 0) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_BLOCK_MATCH_WINDOW_QCOM_09215),
                             objlist, loc.Get(),
                             "the %s has %s with format of %s which doesn't support "
                             "VK_FORMAT_FEATURE_2_BLOCK_MATCHING_BIT_QCOM.\n"
                             "(supported features: %s).%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view).c_str(), string_VkFormat(image_view_state->create_info.format),
                             string_VkFormatFeatureFlags2(image_view_state->format_features).c_str(),
                             DescribeInstruction().c_str());
        }
        const uint32_t component_count = vkuFormatComponentCount(image_view_state->create_info.format);
        if (component_count != 1) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_BLOCK_MATCH_WINDOW_QCOM_09216),
                             objlist, loc.Get(),
                             "the %s has %s with format of %s which has %" PRIu32
                             " components, but OpImageBlockMatchWindow*QCOM and OpImageBlockMatchGather*QCOM "
                             "require a single-component format.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view).c_str(),
                             string_VkFormat(image_view_state->create_info.format),
                             component_count,
                             DescribeInstruction().c_str());
        }
    }
    return skip;
}

bool DescriptorValidator::ValidateImageAttachmentDescriptor(const spirv::ResourceInterfaceVariable& resource_variable,
                                                            const uint32_t index, VkDescriptorType descriptor_type,
                                                            const vvl::ImageView& image_view_state) const {
    bool skip = false;

    const uint32_t binding_index = resource_variable.decorations.binding;
    const bool use_tile_attachment = cb_state.active_render_pass && cb_state.active_render_pass->has_tile_shading_enabled &&
                                     resource_variable.storage_class == spv::StorageClassTileAttachmentQCOM;
    for (uint32_t att_index = 0; att_index < cb_state.active_attachments.size(); ++att_index) {
        const auto& attachment_info = cb_state.active_attachments[att_index];
        const auto* view_state = attachment_info.image_view;
        if (!view_state || view_state->Destroyed()) {
            continue;
        }
        const bool same_view = view_state->VkHandle() == image_view_state.VkHandle();
        const bool overlapping_view = image_view_state.OverlapSubresource(*view_state);
        if (!same_view && !overlapping_view) {
            continue;
        }

        if (!cb_state.active_subpasses.empty()) {
            bool descriptor_written_to = false;

            // Note: In a tile-shading render pass, compute dispatch can run without a bound graphics pipeline.
            if (const auto pipeline = cb_state.GetLastBoundGraphics().pipeline_state) {
                for (const ShaderStageState& stage : pipeline->stage_states) {
                    if (!stage.HasSpirv()) {
                        continue;
                    }
                    for (const auto& interface_variable : stage.entrypoint->resource_interface_variables) {
                        if (interface_variable.decorations.set == set_index &&
                            interface_variable.decorations.binding == binding_index) {
                            descriptor_written_to |= interface_variable.IsWrittenTo();
                            break;  // only one set/binding will match
                        }
                    }
                }
            }

            const SubpassInfo& subpass = cb_state.active_subpasses[att_index];
            const bool layout_read_only = IsImageLayoutReadOnly(attachment_info.layout);
            const bool read_attachment = (subpass.usage & (VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT)) != 0;
            if (read_attachment && descriptor_written_to) {
                const vvl::ActionVUID vuid = attachment_info.IsDepth()     ? vvl::ActionVUID::SUBRESOURCE_SUBPASS_12339
                                             : attachment_info.IsStencil() ? vvl::ActionVUID::SUBRESOURCE_SUBPASS_12340
                                                                           : vvl::ActionVUID::SUBRESOURCE_SUBPASS_12338;
                if (same_view) {
                    const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), framebuffer);
                    skip |= LogError(CreateActionVuid(loc.Get().function, vuid), objlist, loc.Get(),
                                     "the %s has %s which will be read from as %s attachment %" PRIu32 ".%s",
                                     DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                     FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(framebuffer).c_str(), att_index,
                                     DescribeInstruction().c_str());
                } else if (overlapping_view) {
                    const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), framebuffer,
                                                view_state->Handle());
                    skip |= LogError(CreateActionVuid(loc.Get().function, vuid), objlist, loc.Get(),
                                     "the %s has %s which will be overlap read from as %s in %s attachment %" PRIu32 " overlap.%s",
                                     DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                     FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(view_state->Handle()).c_str(),
                                     FormatHandle(framebuffer).c_str(), att_index, DescribeInstruction().c_str());
                }
            }

            if (descriptor_written_to && !layout_read_only) {
                if (same_view) {
                    const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), framebuffer);
                    skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::SUBRESOURCE_RP_WRTIE_06537), objlist,
                                     loc.Get(), "the %s has %s which is written to but is also %s attachment %" PRIu32 ".%s",
                                     DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                     FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(framebuffer).c_str(), att_index,
                                     DescribeInstruction().c_str());
                } else if (overlapping_view) {
                    const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), framebuffer,
                                                view_state->Handle());
                    skip |=
                        LogError(CreateActionVuid(loc.Get().function, ActionVUID::SUBRESOURCE_RP_WRTIE_06537), objlist, loc.Get(),
                                 "the %s has %s which overlaps writes to %s but is also %s attachment %" PRIu32 ".%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(view_state->Handle()).c_str(),
                                 FormatHandle(framebuffer).c_str(), att_index, DescribeInstruction().c_str());
                }
            }
        }

        if (!use_tile_attachment) {
            continue;
        }

        const uint32_t base_type_opcode = resource_variable.base_type.Opcode();
        struct TileShadingRuntimeSpirvInfo {
            const char* vuid = nullptr;
            vvl::Field feature_field = vvl::Field::Empty;
        } ts_spirv_info;
        if (!dev_proxy.enabled_features.tileShadingColorAttachments && attachment_info.IsColor() &&
            base_type_opcode == spv::OpTypeImage) {
            ts_spirv_info = {"VUID-RuntimeSpirv-OpTypeImage-10707", vvl::Field::tileShadingColorAttachments};
        } else if (!dev_proxy.enabled_features.tileShadingDepthAttachments && attachment_info.IsDepth() &&
                   base_type_opcode == spv::OpTypeImage) {
            ts_spirv_info = {"VUID-RuntimeSpirv-OpTypeImage-10708", vvl::Field::tileShadingDepthAttachments};
        } else if (!dev_proxy.enabled_features.tileShadingStencilAttachments && attachment_info.IsStencil() &&
                   base_type_opcode == spv::OpTypeImage) {
            ts_spirv_info = {"VUID-RuntimeSpirv-OpTypeImage-10709", vvl::Field::tileShadingStencilAttachments};
        } else if (!dev_proxy.enabled_features.tileShadingInputAttachments && attachment_info.IsInput() &&
                   base_type_opcode == spv::OpTypeImage) {
            ts_spirv_info = {"VUID-RuntimeSpirv-OpTypeImage-10710", vvl::Field::tileShadingInputAttachments};
        } else if (!dev_proxy.enabled_features.tileShadingSampledAttachments && base_type_opcode == spv::OpTypeSampledImage) {
            ts_spirv_info = {"VUID-RuntimeSpirv-OpTypeSampledImage-10711", vvl::Field::tileShadingSampledAttachments};
        }
        if (ts_spirv_info.vuid) {
            const uint32_t attachment_desc_index =
                (cb_state.attachment_source == AttachmentSource::DynamicRendering) ? attachment_info.type_index : att_index;
            const std::string attachment_desc = attachment_info.Describe(cb_state, attachment_desc_index);
            if (same_view) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle());
                skip |= LogError(ts_spirv_info.vuid, objlist, loc.Get(),
                                 "the %s is TileAttachmentQCOM storage class, it is backed by %s which is "
                                 "bound as %s, but VkPhysicalDeviceTileShadingFeaturesQCOM::%s isn't enabled.%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(image_view_state.Handle()).c_str(), attachment_desc.c_str(),
                                 String(ts_spirv_info.feature_field), DescribeInstruction().c_str());
            } else if (overlapping_view) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(),
                                            view_state->Handle());
                skip |=
                    LogError(ts_spirv_info.vuid, objlist, loc.Get(),
                             "the %s is TileAttachmentQCOM storage class, it is backed by %s which "
                             "overlaps %s that is bound as %s, but VkPhysicalDeviceTileShadingFeaturesQCOM::%s isn't enabled.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(view_state->Handle()).c_str(),
                             attachment_desc.c_str(), String(ts_spirv_info.feature_field), DescribeInstruction().c_str());
            }
        }
    }
    return skip;
}

bool DescriptorValidator::ValidateSampledImageDescriptor(const spirv::ResourceInterfaceVariable& resource_variable,
                                                         const uint32_t index, VkDescriptorType descriptor_type,
                                                         const spirv::ImageInstruction& image_insn,
                                                         const vvl::ImageView& image_view_state,
                                                         const vvl::Image& image_state) const {
    bool skip = false;

    // SubpassData gets validated elsewhere
    const spv::Dim dim = resource_variable.info.image_dim;
    if (dim == spv::DimSubpassData) {
        return skip;
    }

    const VkImageViewCreateInfo& image_view_ci = image_view_state.create_info;

    bool valid_dim = true;
    const bool is_image_array = resource_variable.info.is_image_array;
    const bool alias_single_layer = (image_state.create_flags & VK_IMAGE_CREATE_ALIAS_SINGLE_LAYER_DESCRIPTOR_BIT_KHR) != 0;
    // From vkspec.html#textures-operation-validation
    switch (image_view_ci.viewType) {
        case VK_IMAGE_VIEW_TYPE_1D:
            valid_dim = (dim == spv::Dim1D) && (!is_image_array || alias_single_layer);
            break;
        case VK_IMAGE_VIEW_TYPE_2D:
            valid_dim = (dim == spv::Dim2D) && (!is_image_array || alias_single_layer);
            break;
        case VK_IMAGE_VIEW_TYPE_3D:
            valid_dim = (dim == spv::Dim3D) && !is_image_array;
            break;
        case VK_IMAGE_VIEW_TYPE_CUBE:
            valid_dim = (dim == spv::DimCube) && !is_image_array;
            break;
        case VK_IMAGE_VIEW_TYPE_1D_ARRAY:
            valid_dim = (dim == spv::Dim1D) && (is_image_array || alias_single_layer);
            break;
        case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
            valid_dim = (dim == spv::Dim2D) && (is_image_array || alias_single_layer);
            break;
        case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY:
            valid_dim = (dim == spv::DimCube) && is_image_array;
            break;
        default:
            break;  // incase a new VkImageViewType is added, let it be valid by default
    }
    if (!valid_dim) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.VkHandle());
        std::ostringstream ss;
        ss << "the " << DescribeDescriptor(resource_variable, index, descriptor_type) << " VkImageViewType is "
           << string_VkImageViewType(image_view_ci.viewType) << " but the OpTypeImage has (Dim = " << string_SpvDim(dim)
           << ") and (Arrayed = " << is_image_array << ").\nEither fix in the shader or update the VkImageViewType to "
           << SuggestImageViewType(dim, is_image_array);
        if (((image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_1D || image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY) &&
             dim == spv::Dim1D) ||
            ((image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_2D || image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY) &&
             dim == spv::Dim2D)) {
            ss << "\nHint: With VK_KHR_maintenance11 there is VK_IMAGE_CREATE_ALIAS_SINGLE_LAYER_DESCRIPTOR_BIT_KHR which "
                  "would make this legal.";
        }
        if (is_gpu_av && resource_variable.IsArray()) {
            ss << "\nAdvice: The dimension is tied to the descriptor variable, so for descriptor indexing, you might need to "
                  "express two different arrays of different types that share the same descriptor binding.";
        }
        ss << DescribeInstruction();
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::IMAGE_VIEW_DIM_07752), objlist, loc.Get(), "%s",
                         ss.str().c_str());
    }

    const uint32_t view_numeric_type = spirv::GetFormatNumericType(image_view_ci.format);
    const uint32_t variable_numeric_type = resource_variable.info.numeric_type;
    // When the image has a external format the views format must be VK_FORMAT_UNDEFINED and it is required to use a sampler
    // Ycbcr conversion. Thus we can't extract any meaningful information from the format parameter.
    if (image_view_ci.format != VK_FORMAT_UNDEFINED && ((variable_numeric_type & view_numeric_type) == 0)) {
        const bool signed_override = ((variable_numeric_type & spirv::NumericTypeUint) && image_insn.is_sign_extended);
        const bool unsigned_override = ((variable_numeric_type & spirv::NumericTypeSint) && image_insn.is_zero_extended);
        if (!signed_override && !unsigned_override) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.VkHandle());
            skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::IMAGE_VIEW_NUMERIC_07753), objlist, loc.Get(),
                             "the %s requires %s component type, but bound descriptor format is %s.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             spirv::string_NumericType(variable_numeric_type), string_VkFormat(image_view_ci.format),
                             DescribeInstruction().c_str());
        }
    }

    // Check for only_report_errors last (to save a nesting), this string building is expensive if being spammed constantly
    if (image_view_ci.format != VK_FORMAT_UNDEFINED && resource_variable.info.vk_format != VK_FORMAT_UNDEFINED &&
        image_view_ci.format != resource_variable.info.vk_format && !dev_proxy.global_settings.only_report_errors) {
        // This warning was added after being discussed in https://gitlab.khronos.org/vulkan/vulkan/-/issues/4128
        auto set = descriptor_set.Handle();
        const LogObjectList objlist(this->objlist, set, image_view_state.VkHandle());
        std::ostringstream msg;
        msg << "the " << DescribeDescriptor(resource_variable, index, descriptor_type)
            << " is accessed by a OpTypeImage that has a Format operand "
            << string_SpirvImageFormat(resource_variable.info.vk_format) << " (equivalent to "
            << string_VkFormat(resource_variable.info.vk_format) << ") which doesn't match the "
            << FormatHandle(image_view_state.VkHandle()) << " format (" << string_VkFormat(image_view_ci.format)
            << "). Any loads or stores with the variable will produce undefined values to the whole image (not just the texel "
               "being accessed).";
        if (vkuFormatCompatibilityClass(image_view_ci.format) == vkuFormatCompatibilityClass(resource_variable.info.vk_format)) {
            msg << " While the formats are compatible, Storage Images must exactly match. Few ways to resolve this are\n";
            if (vkuFormatComponentCount(image_view_ci.format) == vkuFormatComponentCount(resource_variable.info.vk_format)) {
                msg << "1. Set your ImageView to " << string_VkFormat(resource_variable.info.vk_format)
                    << " and swizzle the values in the shader to match the desired results.\n";
            } else {
                const char* suggested_format = string_SpirvImageFormat(image_view_ci.format);
                if (strncmp(suggested_format, "Unknown", 7) != 0) {
                    msg << "1. Change your shader to use " << suggested_format << " instead as that matches "
                        << string_VkFormat(image_view_ci.format) << "\n";
                } else {
                    msg << "1. Find an SPIR-V Image format that can be mapped to a desired VkImageView format "
                           "https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#spirvenv-image-formats\n";
                }
            }
            msg << "2. Use the Unknown format in your shader (will need the widely supported "
                   "shaderStorageImageWriteWithoutFormat feature)";
        }
        msg << "\nSpec information at https://docs.vulkan.org/spec/latest/chapters/textures.html#textures-format-validation";
        msg << DescribeInstruction();
        skip |=
            LogUndefinedValue("Undefined-Value-StorageImage-FormatMismatch-ImageView", objlist, loc.Get(), "%s", msg.str().c_str());
    }

    const bool image_format_width_64 = vkuFormatHasComponentSize(image_view_ci.format, 64);
    if (image_format_width_64) {
        if (resource_variable.info.bit_width != 64) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.VkHandle());
            skip |=
                LogError(CreateActionVuid(loc.Get().function, ActionVUID::IMAGE_VIEW_ACCESS_04470), objlist, loc.Get(),
                         "the %s has a 64-bit component ImageView format (%s) but the OpTypeImage's "
                         "Sampled Type has a width of %" PRIu32 ".%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         string_VkFormat(image_view_ci.format), resource_variable.info.bit_width, DescribeInstruction().c_str());
        } else if (!dev_proxy.enabled_features.sparseImageInt64Atomics && image_state.sparse_residency) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.VkHandle(), image_state.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::IMAGE_VIEW_SPARSE_04474), objlist, loc.Get(),
                             "the %s has a OpTypeImage's Sampled Type has a width of 64 backed by a sparse Image, but "
                             "sparseImageInt64Atomics is not enabled.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), DescribeInstruction().c_str());
        }
    } else if (!image_format_width_64 && resource_variable.info.bit_width != 32) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.VkHandle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::IMAGE_VIEW_ACCESS_04471), objlist, loc.Get(),
                         "the %s has a 32-bit component ImageView format (%s) but the OpTypeImage's "
                         "Sampled Type has a width of %" PRIu32 ".%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         string_VkFormat(image_view_ci.format), resource_variable.info.bit_width, DescribeInstruction().c_str());
    }

    // Verify Sample counts
    if (!resource_variable.info.is_multisampled && image_view_state.samples != VK_SAMPLE_COUNT_1_BIT) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.VkHandle());
        skip |= LogError("VUID-RuntimeSpirv-samples-08725", objlist, loc.Get(),
                         "the %s has %s created with %s, but OpTypeImage has marked it as single-sampled.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(image_state.Handle()).c_str(), string_VkSampleCountFlagBits(image_view_state.samples),
                         DescribeInstruction().c_str());
    } else if (resource_variable.info.is_multisampled && image_view_state.samples == VK_SAMPLE_COUNT_1_BIT) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.VkHandle());
        skip |= LogError("VUID-RuntimeSpirv-samples-08726", objlist, loc.Get(),
                         "the %s has %s created with VK_SAMPLE_COUNT_1_BIT, but OpTypeImage has marked it as multisampled.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(image_state.Handle()).c_str(), DescribeInstruction().c_str());
    }

    return skip;
}

bool DescriptorValidator::ValidateImageSamplerDescriptor(const spirv::ResourceInterfaceVariable& resource_variable,
                                                         const uint32_t index, VkDescriptorType descriptor_type,
                                                         const spirv::ImageInstruction& image_insn,
                                                         const vvl::ImageView& image_view_state,
                                                         const vvl::Sampler& sampler_state) const {
    bool skip = false;

    const VkSamplerCreateInfo& sampler_ci = sampler_state.create_info;
    const VkImageViewCreateInfo& image_view_ci = image_view_state.create_info;

    // TODO: Validate 04015 for DescriptorClass::PlainSampler
    if ((sampler_ci.borderColor == VK_BORDER_COLOR_INT_CUSTOM_EXT || sampler_ci.borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT) &&
        (sampler_state.custom_border_color_format == VK_FORMAT_UNDEFINED)) {
        if (IsValueIn(image_view_ci.format, {VK_FORMAT_B4G4R4A4_UNORM_PACK16, VK_FORMAT_B5G6R5_UNORM_PACK16,
                                             VK_FORMAT_B5G5R5A1_UNORM_PACK16, VK_FORMAT_A1B5G5R5_UNORM_PACK16})) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(), image_view_state.Handle());
            skip |= LogError("VUID-VkSamplerCustomBorderColorCreateInfoEXT-format-04015", objlist, loc.Get(),
                             "the %s has %s which has a custom border color with format = "
                             "VK_FORMAT_UNDEFINED and is used to sample an image "
                             "view %s with format %s.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(sampler_state.Handle()).c_str(), FormatHandle(image_view_state.Handle()).c_str(),
                             string_VkFormat(image_view_ci.format), DescribeInstruction().c_str());
        }
    }

    const VkFilter sampler_mag_filter = sampler_ci.magFilter;
    const VkFilter sampler_min_filter = sampler_ci.minFilter;
    const bool sampler_compare_enable = sampler_ci.compareEnable == VK_TRUE;
    const auto sampler_reduction = vku::FindStructInPNextChain<VkSamplerReductionModeCreateInfo>(sampler_ci.pNext);
    // The VU is wording is a bit misleading, if there is no VkSamplerReductionModeCreateInfo we still need to check for linear
    // tiling feature
    const bool is_weighted_average =
        !sampler_reduction || sampler_reduction->reductionMode == VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE;
    if (!sampler_compare_enable && is_weighted_average &&
        !(image_view_state.format_features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {
        if (sampler_mag_filter == VK_FILTER_LINEAR || sampler_min_filter == VK_FILTER_LINEAR) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(), image_view_state.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::LINEAR_FILTER_04553), objlist, loc.Get(),
                             "the %s has %s which is set to use VK_FILTER_LINEAR with compareEnable is set "
                             "to VK_FALSE, but image view's (%s) format (%s) does not contain "
                             "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT in its format features.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(sampler_state.Handle()).c_str(), FormatHandle(image_view_state.Handle()).c_str(),
                             string_VkFormat(image_view_ci.format), DescribeInstruction().c_str());
        } else if (sampler_ci.mipmapMode == VK_SAMPLER_MIPMAP_MODE_LINEAR) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(), image_view_state.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::LINEAR_MIPMAP_04770), objlist, loc.Get(),
                             "the %s has %s which is set to use VK_SAMPLER_MIPMAP_MODE_LINEAR with "
                             "compareEnable is set to VK_FALSE, but image view's (%s) format (%s) does not contain "
                             "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT in its format features.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(sampler_state.Handle()).c_str(), FormatHandle(image_view_state.Handle()).c_str(),
                             string_VkFormat(image_view_ci.format), DescribeInstruction().c_str());
        }
    }

    const bool is_minmax = sampler_reduction && (sampler_reduction->reductionMode == VK_SAMPLER_REDUCTION_MODE_MIN ||
                                                 sampler_reduction->reductionMode == VK_SAMPLER_REDUCTION_MODE_MAX);
    if (is_minmax && !(image_view_state.format_features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT)) {
        if (sampler_mag_filter == VK_FILTER_LINEAR || sampler_min_filter == VK_FILTER_LINEAR) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(), image_view_state.Handle());
            skip |= LogError(
                CreateActionVuid(loc.Get().function, vvl::ActionVUID::LINEAR_FILTER_09598), objlist, loc.Get(),
                "the %s has %s which is set to use VK_FILTER_LINEAR with reductionMode is set "
                "to %s, but image view's (%s) format (%s) does not contain "
                "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT in its format features.%s",
                DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(sampler_state.Handle()).c_str(),
                string_VkSamplerReductionMode(sampler_reduction->reductionMode), FormatHandle(image_view_state.Handle()).c_str(),
                string_VkFormat(image_view_ci.format), DescribeInstruction().c_str());
        } else if (sampler_ci.mipmapMode == VK_SAMPLER_MIPMAP_MODE_LINEAR) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(), image_view_state.Handle());
            skip |= LogError(
                CreateActionVuid(loc.Get().function, vvl::ActionVUID::LINEAR_MIPMAP_09599), objlist, loc.Get(),
                "the %s has %s which is set to use VK_SAMPLER_MIPMAP_MODE_LINEAR with "
                "reductionMode is set to %s, but image view's (%s) format (%s) does not contain "
                "VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT in its format features.%s",
                DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(sampler_state.Handle()).c_str(),
                string_VkSamplerReductionMode(sampler_reduction->reductionMode), FormatHandle(image_view_state.Handle()).c_str(),
                string_VkFormat(image_view_ci.format), DescribeInstruction().c_str());
        }
    }

    if (sampler_mag_filter == VK_FILTER_CUBIC_EXT || sampler_min_filter == VK_FILTER_CUBIC_EXT) {
        if (!(image_view_state.format_features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_EXT)) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(), image_view_state.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::SAMPLER_CUBIC_02692), objlist, loc.Get(),
                             "the %s has %s which is set to use VK_FILTER_CUBIC_EXT, then image view's (%s) format (%s) "
                             "MUST contain VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_EXT in its format features.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(sampler_state.Handle()).c_str(), FormatHandle(image_view_state.Handle()).c_str(),
                             string_VkFormat(image_view_state.create_info.format), DescribeInstruction().c_str());
        }

        if (IsExtEnabled(dev_proxy.extensions.vk_ext_filter_cubic)) {
            const auto reduction_mode_info = vku::FindStructInPNextChain<VkSamplerReductionModeCreateInfo>(sampler_ci.pNext);
            if (reduction_mode_info &&
                (reduction_mode_info->reductionMode == VK_SAMPLER_REDUCTION_MODE_MIN ||
                 reduction_mode_info->reductionMode == VK_SAMPLER_REDUCTION_MODE_MAX) &&
                !image_view_state.filter_cubic_props.filterCubicMinmax) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(),
                                            image_view_state.Handle());
                skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::FILTER_CUBIC_02695), objlist, loc.Get(),
                                 "the %s has %s which is set to use VK_FILTER_CUBIC_EXT & %s, but image view "
                                 "(%s) doesn't support filterCubicMinmax.%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(sampler_state.Handle()).c_str(),
                                 string_VkSamplerReductionMode(reduction_mode_info->reductionMode),
                                 FormatHandle(image_view_state.Handle()).c_str(), DescribeInstruction().c_str());
            } else if (!image_view_state.filter_cubic_props.filterCubic) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(),
                                            image_view_state.Handle());
                skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::FILTER_CUBIC_02694), objlist, loc.Get(),
                                 "the %s has %s which is set to use VK_FILTER_CUBIC_EXT, but image view (%s) "
                                 "doesn't support filterCubic.%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(sampler_state.Handle()).c_str(), FormatHandle(image_view_state.Handle()).c_str(),
                                 DescribeInstruction().c_str());
            }
        }

        if (IsExtEnabled(dev_proxy.extensions.vk_img_filter_cubic)) {
            if (image_view_state.create_info.viewType == VK_IMAGE_VIEW_TYPE_3D ||
                image_view_state.create_info.viewType == VK_IMAGE_VIEW_TYPE_CUBE ||
                image_view_state.create_info.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(),
                                            image_view_state.Handle());
                skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::FILTER_CUBIC_02693), objlist, loc.Get(),
                                 "the %s has %s which is set to use VK_FILTER_CUBIC_EXT while the VK_IMG_filter_cubic "
                                 "extension is enabled, but image view (%s) has an invalid imageViewType (%s).%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(sampler_state.Handle()).c_str(), FormatHandle(image_view_state.Handle()).c_str(),
                                 string_VkImageViewType(image_view_state.create_info.viewType), DescribeInstruction().c_str());
            }
        }
    }

    if ((image_view_state.image_state->create_flags & VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV) &&
        (sampler_ci.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ||
         sampler_ci.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ||
         sampler_ci.addressModeW != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)) {
        std::string address_mode_letter = (sampler_ci.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)   ? "U"
                                          : (sampler_ci.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) ? "V"
                                                                                                               : "W";
        VkSamplerAddressMode address_mode =
            (sampler_ci.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)   ? sampler_ci.addressModeU
            : (sampler_ci.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) ? sampler_ci.addressModeV
                                                                                 : sampler_ci.addressModeW;
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), sampler_state.Handle(),
                                    image_view_state.image_state->Handle(), image_view_state.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::SAMPLER_CORNER_02696), objlist, loc.Get(),
                         "the %s image (%s) in image view (%s) is created with flag "
                         "VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV and can only be sampled using "
                         "VK_SAMPLER_ADDRESS_MODE_CLAMP_EDGE, but sampler (%s) has "
                         "pCreateInfo->addressMode%s set to %s.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(image_view_state.image_state->Handle()).c_str(),
                         FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(sampler_state.Handle()).c_str(),
                         address_mode_letter.c_str(), string_VkSamplerAddressMode(address_mode), DescribeInstruction().c_str());
    }

    // UnnormalizedCoordinates sampler validations
    // only check if sampled as could have a texelFetch on a combined image sampler
    if (sampler_ci.unnormalizedCoordinates && image_insn.is_sampler_sampled) {
        const auto& subresource_range = image_view_state.normalized_subresource_range;

        // If ImageView is used by a unnormalizedCoordinates sampler, it needs to check ImageView type
        if (IsValueIn(image_view_ci.viewType, {VK_IMAGE_VIEW_TYPE_3D, VK_IMAGE_VIEW_TYPE_CUBE, VK_IMAGE_VIEW_TYPE_1D_ARRAY,
                                               VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_VIEW_TYPE_CUBE_ARRAY})) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), sampler_state.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::SAMPLER_TYPE_08609), objlist, loc.Get(),
                             "the %s (%s) was created with %s, but %s was created with unnormalizedCoordinates.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view_state.Handle()).c_str(), string_VkImageViewType(image_view_ci.viewType),
                             FormatHandle(sampler_state.Handle()).c_str(), DescribeInstruction().c_str());
        } else if (subresource_range.levelCount != 1) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), sampler_state.Handle());
            skip |=
                LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::UNNORMALIZED_COORDINATES_09635), objlist, loc.Get(),
                         "the %s (%s) was created with levelCount of %s, but %s was created with "
                         "unnormalizedCoordinates.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(image_view_state.Handle()).c_str(),
                         string_LevelCount(image_view_state.image_state->GetMipLevels(), image_view_ci.subresourceRange).c_str(),
                         FormatHandle(sampler_state.Handle()).c_str(), DescribeInstruction().c_str());
        } else if (subresource_range.layerCount != 1) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), sampler_state.Handle());
            skip |=
                LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::UNNORMALIZED_COORDINATES_09635), objlist, loc.Get(),
                         "the %s (%s) was created with layerCount of %s, but %s was created with "
                         "unnormalizedCoordinates.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(image_view_state.Handle()).c_str(),
                         string_LayerCount(image_view_state.image_state->GetArrayLayers(), image_view_ci.subresourceRange).c_str(),
                         FormatHandle(sampler_state.Handle()).c_str(), DescribeInstruction().c_str());
        } else if (image_insn.is_sampler_implicitLod_dref_proj) {
            // sampler must not be used with any of the SPIR-V OpImageSample* or OpImageSparseSample*
            // instructions with ImplicitLod, Dref or Proj in their name
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), sampler_state.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::SAMPLER_DREF_PROJ_08610), objlist, loc.Get(),
                             "the %s (%s) is used by %s that uses invalid operator.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(sampler_state.Handle()).c_str(),
                             DescribeInstruction().c_str());
        } else if (image_insn.is_sampler_bias_offset) {
            // sampler must not be used with any of the SPIR-V OpImageSample* or OpImageSparseSample*
            // instructions that includes a LOD bias or any offset values
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), sampler_state.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::SAMPLER_BIAS_OFFSET_08611), objlist, loc.Get(),
                             "the %s (%s) is used by %s that uses invalid bias or offset operator.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(sampler_state.Handle()).c_str(),
                             DescribeInstruction().c_str());
        }
    }

    if (resource_variable.info.image_insn.image_proc_usage_mask != spirv::ImageProcUsageBit::kNone &&
        (sampler_ci.flags & VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM) == 0) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), sampler_state.Handle());
        skip |=
            LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_PROCESSING_SAMPLE_QCOM_06977), objlist, loc.Get(),
                     "the %s has %s paired with sampler %s (flags: %s), but "
                     "VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM is required for image-processing instructions.%s",
                     DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                     FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(sampler_state.Handle()).c_str(),
                     string_VkSamplerCreateFlags(sampler_ci.flags).c_str(), DescribeInstruction().c_str());
    } else if (resource_variable.info.image_insn.image_proc_usage_mask == spirv::ImageProcUsageBit::kNone &&
               (sampler_ci.flags & VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM) != 0) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), sampler_state.Handle());
        skip |=
            LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_PROCESSING_SAMPLE_QCOM_06978), objlist, loc.Get(),
                     "the %s has %s paired with sampler %s (flags: %s), but "
                     "VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM is only valid with image-processing instructions.%s",
                     DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                     FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(sampler_state.Handle()).c_str(),
                     string_VkSamplerCreateFlags(sampler_ci.flags).c_str(), DescribeInstruction().c_str());
    }

    if ((resource_variable.info.image_insn.image_proc_usage_mask & spirv::ImageProcUsageBit::kBlockMatchWindow) != 0) {
        if (!dev_proxy.enabled_features.blockMatchExtendedClampToEdge &&
            (sampler_ci.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ||
             sampler_ci.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), sampler_state.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_BLOCK_MATCH_WINDOW_QCOM_12421), objlist,
                             loc.Get(),
                             "the %s has %s paired with sampler %s (addressModeU: %s, addressModeV: %s), but "
                             "VkPhysicalDeviceImageProcessing3FeaturesQCOM::blockMatchExtendedClampToEdge is required "
                             "when addressModeU or addressModeV is VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(sampler_state.Handle()).c_str(),
                             string_VkSamplerAddressMode(sampler_ci.addressModeU),
                             string_VkSamplerAddressMode(sampler_ci.addressModeV), DescribeInstruction().c_str());
        }
        if ((sampler_ci.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ||
             sampler_ci.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) &&
            sampler_ci.addressModeU != sampler_ci.addressModeV) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view_state.Handle(), sampler_state.Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::IMAGE_BLOCK_MATCH_WINDOW_QCOM_12422), objlist,
                             loc.Get(),
                             "the %s has %s paired with sampler %s (addressModeU: %s, addressModeV: %s), but "
                             "when either addressModeU or addressModeV is VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE "
                             "both must be VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view_state.Handle()).c_str(), FormatHandle(sampler_state.Handle()).c_str(),
                             string_VkSamplerAddressMode(sampler_ci.addressModeU),
                             string_VkSamplerAddressMode(sampler_ci.addressModeV), DescribeInstruction().c_str());
        }
    }

    return skip;
}

// 'index' is the index into the descriptor
bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable& resource_variable, const uint32_t index,
                                             VkDescriptorType descriptor_type, const ImageDescriptor& image_descriptor) const {
    // We skip various parts of checks for core check to prevent false positive when we don't know the index
    bool skip = false;
    std::vector<const Sampler*> sampler_states;
    const VkImageView image_view = image_descriptor.GetImageView();
    const ImageView* image_view_state = image_descriptor.GetImageViewState();

    if (image_descriptor.GetClass() == DescriptorClass::ImageSampler) {
        sampler_states.emplace_back(static_cast<const ImageSamplerDescriptor&>(image_descriptor).GetSamplerState());
    } else if (is_gpu_av) {
        // TODO - This will skip for GPU-AV because we don't currently capture array of samplers with array of sampled images
        // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8922
        // To not give false positve, we will skip all Sampler related checks since sampler_states will be empty
    } else {
        if (index < resource_variable.samplers_used_by_image.size()) {
            for (const auto& sampler_used : resource_variable.samplers_used_by_image[index]) {
                const auto* descriptor =
                    descriptor_set.GetDescriptorFromBinding(sampler_used.sampler_slot.binding, sampler_used.sampler_index);
                // TODO: This check _shouldn't_ be necessary due to the checks made in ResourceInterfaceVariable() in
                //       shader_validation.cpp. However, without this check some traces still crash.
                // The issue is we set dynamic image index to zero in samplers_used_by_image so will fail GPU-AV case
                if (descriptor && (descriptor->GetClass() == DescriptorClass::PlainSampler)) {
                    const auto* sampler_state = static_cast<const SamplerDescriptor*>(descriptor)->GetSamplerState();
                    if (sampler_state) sampler_states.emplace_back(sampler_state);
                }
            }
        }
    }

    if ((!image_view_state && !dev_proxy.enabled_features.nullDescriptor) || (image_view_state && image_view_state->Destroyed())) {
        // Image view must have been destroyed since initial update. Could potentially flag the descriptor
        //  as "invalid" (updated = false) at DestroyImageView() time and detect this error at bind time
        const LogObjectList objlist(this->objlist, descriptor_set.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s is using imageView %s that is invalid or has been destroyed.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(image_view).c_str(),
                         DescribeInstruction().c_str());
        return skip;  // if invalid, end early
    }

    // ImageView could be null via nullDescriptor and accessing it is legal
    if (image_view == VK_NULL_HANDLE) {
        return skip;
    }
    if (!resource_variable.IsAccessed()) {
        return skip;
    }

    // If there is an non-null imageView, the image inside should be valid
    const auto image_state = image_view_state->image_state.get();
    ASSERT_AND_RETURN_SKIP(image_state);

    const auto& image_view_ci = image_view_state->create_info;

    // For Descriptor Indexing, we want the exact image access, not the combination of all accesses to the OpVariable
    spirv::ImageInstruction image_insn = (is_gpu_av && original_spirv)
                                             ? spirv::ImageInstruction(&(*original_spirv)[instruction_position_offset])
                                             : resource_variable.info.image_insn;

    // way to ignore storage images
    if (resource_variable.IsImage()) {
        skip |=
            ValidateSampledImageDescriptor(resource_variable, index, descriptor_type, image_insn, *image_view_state, *image_state);
    }

    if (!dev_proxy.disabled[image_layout_validation]) {
        if (const auto image_layout_map = cb_state.GetImageLayoutMap(image_state->VkHandle())) {
            auto describe_descriptor_callback = [this, &resource_variable, index, descriptor_type]() {
                std::ostringstream ss;
                ss << DescribeDescriptor(resource_variable, index, descriptor_type);
                if (descriptor_set.IsPushDescriptor()) {
                    ss << " updated by vkCmdPushDescriptorSet";
                }
                return ss.str();
            };
            const VkImageLayout image_layout = image_descriptor.GetImageLayout();
            const VkImageAspectFlags aspect_flags = image_view_state->create_info.subresourceRange.aspectMask;
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_state->Handle(),
                                        VulkanTypedHandle(image_view, kVulkanObjectTypeImageView));

            skip |= dev_proxy.ValidateDescriptorImageLayout(objlist, *image_state, aspect_flags, image_layout, *image_layout_map,
                                                            subresource_adapter::RangeGenerator(image_view_state->range_generator),
                                                            loc.Get(), describe_descriptor_callback);
        }
    }

    // Verify VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT
    if (resource_variable.IsAtomic() && (descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) &&
        !(image_view_state->format_features & VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT)) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::IMAGE_VIEW_ATOMIC_02691), objlist, loc.Get(),
                         "the %s has %s with format of %s which doesn't support VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT.\n"
                         "(supported features: %s).%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(image_view).c_str(),
                         string_VkFormat(image_view_ci.format),
                         string_VkFormatFeatureFlags2(image_view_state->format_features).c_str(), DescribeInstruction().c_str());
    }

    // When KHR_format_feature_flags2 is supported, the read/write without
    // format support is reported per format rather than a single physical
    // device feature.
    if (dev_proxy.device_state->special_supported.vk_khr_format_feature_flags2) {
        const VkFormatFeatureFlags2 format_features = image_view_state->format_features;

        if (descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) {
            if ((resource_variable.info.is_read_without_format) &&
                !(format_features & VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT)) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
                skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::STORAGE_IMAGE_FORMAT_07028), objlist, loc.Get(),
                                 "the %s has %s with format of %s which doesn't support "
                                 "VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT.\n"
                                 "(supported features: %s).%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(image_view).c_str(), string_VkFormat(image_view_ci.format),
                                 string_VkFormatFeatureFlags2(format_features).c_str(), DescribeInstruction().c_str());
            } else if ((resource_variable.info.is_write_without_format) &&
                       !(format_features & VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT)) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
                skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::STORAGE_IMAGE_FORMAT_07027), objlist, loc.Get(),
                                 "the %s has %s with format of %s which doesn't support "
                                 "VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT.\n"
                                 "(supported features: %s).%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(image_view).c_str(), string_VkFormat(image_view_ci.format),
                                 string_VkFormatFeatureFlags2(format_features).c_str(), DescribeInstruction().c_str());
            }
        }

        if ((image_insn.is_dref) && !(format_features & VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT)) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
            skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DEPTH_COMPARE_SAMPLE_06479), objlist, loc.Get(),
                             "the %s has %s with format of %s which doesn't support "
                             "VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT.\n"
                             "(supported features: %s).%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view).c_str(), string_VkFormat(image_view_ci.format),
                             string_VkFormatFeatureFlags2(format_features).c_str(), DescribeInstruction().c_str());
        }
    }

    // Verify if attachments are used in DescriptorSet
    if (!cb_state.active_attachments.empty() && (descriptor_type != VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT)) {
        skip |= ValidateImageAttachmentDescriptor(resource_variable, index, descriptor_type, *image_view_state);
    }

    if (dev_proxy.enabled_features.protectedMemory == VK_TRUE) {
        std::string temp_vuid = CreateActionVuid(loc.Get().function, ActionVUID::CB_UNPROTECTED_02707);
        skip |= dev_proxy.ValidateProtectedImage(cb_state, *image_state, loc.Get(), temp_vuid.c_str(),
                                                 " (Image is in a descriptorSet)");
        if (resource_variable.IsWrittenTo()) {
            temp_vuid = CreateActionVuid(loc.Get().function, ActionVUID::CB_PROTECTED_02712);
            skip |= dev_proxy.ValidateUnprotectedImage(cb_state, *image_state, loc.Get(), temp_vuid.c_str(),
                                                       " (Image is in a descriptorSet)");
        }
    }

    const VkFormat image_view_format = image_view_state->create_info.format;
    for (const uint32_t texel_component_count : resource_variable.write_without_formats_component_count_list) {
        const uint32_t format_component_count = vkuFormatComponentCount(image_view_format);
        if (image_view_format == VK_FORMAT_A8_UNORM) {
            if (texel_component_count != 4) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
                skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::STORAGE_IMAGE_TEXEL_08796), objlist, loc.Get(),
                                 "the %s (%s) is mapped to a OpImage format of VK_FORMAT_A8_UNORM, "
                                 "but the OpImageWrite Texel "
                                 "operand only contains %" PRIu32 " components.%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(image_view).c_str(), texel_component_count, DescribeInstruction().c_str());
            }
        } else if (texel_component_count < format_component_count) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
            skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::STORAGE_IMAGE_TEXEL_08795), objlist, loc.Get(),
                             "the %s (%s) is mapped to a OpImage format of %s which has %" PRIu32
                             " components, but the OpImageWrite Texel operand only contains %" PRIu32 " components.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view).c_str(), string_VkFormat(image_view_format), format_component_count,
                             texel_component_count, DescribeInstruction().c_str());
        }
    }

    // VK_QCOM_tile_memory_heap
    if (dev_proxy.enabled_features.tileMemoryHeap) {
        skip |= dev_proxy.ValidateBoundTileMemory(*image_view_state->image_state, cb_state, loc.Get());
    }

    // VK_QCOM_tile_shading
    if (cb_state.per_tile_execution_model_enabled) {
        if (image_descriptor.GetImageLayout() == VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), image_view);
            skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::PER_TILE_MODEL_FEEDBACK_LOOP_IMAGE_ACCESS_10679),
                             objlist, loc.Get(),
                             "the %s has %s which has been transitioned to VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT "
                             "layout and but is also accessed in shader.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(image_view).c_str(), DescribeInstruction().c_str());
        }
    }

    // VK_QCOM_image_processing
    if (resource_variable.info.image_insn.image_proc_usage_mask != spirv::ImageProcUsageBit::kNone) {
        skip |= ValidateImageDescriptorQCOM(resource_variable, index, descriptor_type, image_descriptor, !sampler_states.empty());
    }

    // If the Image View is invalid, the combined sampler maybe have the same issue
    if (skip) {
        return skip;
    }

    for (const auto* sampler_state : sampler_states) {
        if (sampler_state && !sampler_state->Destroyed()) {
            skip |= ValidateImageSamplerDescriptor(resource_variable, index, descriptor_type, image_insn, *image_view_state,
                                                   *sampler_state);
        }
    }

    return skip;
}

bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable& resource_variable, const uint32_t index,
                                             VkDescriptorType descriptor_type, const ImageSamplerDescriptor& descriptor) const {
    bool skip = false;
    skip |= ValidateDescriptor(resource_variable, index, descriptor_type, static_cast<const ImageDescriptor&>(descriptor));
    if (skip) {
        return skip;
    }
    skip |= ValidateSamplerDescriptor(resource_variable, index, descriptor.GetSampler(), descriptor.IsImmutableSampler(),
                                      descriptor.GetSamplerState());
    return skip;
}

bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable& resource_variable, const uint32_t index,
                                             VkDescriptorType descriptor_type, const TexelDescriptor& texel_descriptor) const {
    bool skip = false;
    const VkBufferView buffer_view = texel_descriptor.GetBufferView();
    auto buffer_view_state = texel_descriptor.GetBufferViewState();
    if ((!buffer_view_state && !dev_proxy.enabled_features.nullDescriptor) ||
        (buffer_view_state && buffer_view_state->Destroyed())) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s is using bufferView %s that is invalid or has been destroyed.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer_view).c_str(),
                         DescribeInstruction().c_str());
        return skip;  // early return if invalid
    }

    // BufferView could be null via nullDescriptor and accessing it is legal
    if (buffer_view == VK_NULL_HANDLE) {
        return skip;
    }
    if (!resource_variable.IsAccessed()) return skip;

    auto buffer = buffer_view_state->create_info.buffer;
    const auto* buffer_state = buffer_view_state->buffer_state.get();
    if (!buffer_state || buffer_state->Destroyed()) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s is using buffer %s that has been destroyed.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer).c_str(),
                         DescribeInstruction().c_str());
        return skip;  // early return if invalid
    }

    const VkFormat buffer_view_format = buffer_view_state->create_info.format;
    const uint32_t view_numeric_type = spirv::GetFormatNumericType(buffer_view_format);
    const uint32_t variable_numeric_type = resource_variable.info.numeric_type;
    if ((variable_numeric_type & view_numeric_type) == 0) {
        spirv::ImageInstruction image_insn = (is_gpu_av && original_spirv)
                                                 ? spirv::ImageInstruction(&(*original_spirv)[instruction_position_offset])
                                                 : resource_variable.info.image_insn;

        const bool signed_override = ((variable_numeric_type & spirv::NumericTypeUint) && image_insn.is_sign_extended);
        const bool unsigned_override = ((variable_numeric_type & spirv::NumericTypeSint) && image_insn.is_zero_extended);
        if (!signed_override && !unsigned_override) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view);
            skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::IMAGE_VIEW_NUMERIC_07753), objlist, loc.Get(),
                             "the %s requires %s component type, but bound descriptor format is %s.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             spirv::string_NumericType(variable_numeric_type), string_VkFormat(buffer_view_format),
                             DescribeInstruction().c_str());
        }
    }

    // Check for only_report_errors last (to save a nesting), this string building is expensive if being spammed constantly
    if (buffer_view_format != VK_FORMAT_UNDEFINED && resource_variable.info.vk_format != VK_FORMAT_UNDEFINED &&
        buffer_view_format != resource_variable.info.vk_format && !dev_proxy.global_settings.only_report_errors) {
        // This warning was added after being discussed in https://gitlab.khronos.org/vulkan/vulkan/-/issues/4128
        auto set = descriptor_set.Handle();
        const LogObjectList objlist(this->objlist, set, buffer_view);
        std::ostringstream msg;
        msg << "the " << DescribeDescriptor(resource_variable, index, descriptor_type)
            << " is accessed by a OpTypeImage that has a Format operand "
            << string_SpirvImageFormat(resource_variable.info.vk_format) << " (equivalent to "
            << string_VkFormat(resource_variable.info.vk_format) << ") which doesn't match the " << FormatHandle(buffer_view)
            << " format (" << string_VkFormat(buffer_view_format)
            << "). Any loads or stores with the variable will produce undefined values to the whole image (not just the texel "
               "being accessed).";
        if (vkuFormatCompatibilityClass(buffer_view_format) == vkuFormatCompatibilityClass(resource_variable.info.vk_format)) {
            msg << " While the formats are compatible, Texel Buffers must exactly match. Few ways to resolve this are\n";
            if (vkuFormatComponentCount(buffer_view_format) == vkuFormatComponentCount(resource_variable.info.vk_format)) {
                msg << "1. Set your BuffereView to " << string_VkFormat(resource_variable.info.vk_format)
                    << " and swizzle the values in the shader to match the desired results.\n";
            } else {
                const char* suggested_format = string_SpirvImageFormat(buffer_view_format);
                if (strncmp(suggested_format, "Unknown", 7) != 0) {
                    msg << "1. Change your shader to use " << suggested_format << " instead as that matches "
                        << string_VkFormat(buffer_view_format) << "\n";
                } else {
                    msg << "1. Find an SPIR-V Image format that can be mapped to a desired VkBuffereView format "
                           "https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#spirvenv-image-formats\n";
                }
            }
            msg << "2. Use the Unknown format in your shader";
        }
        msg << "\nSpec information at https://docs.vulkan.org/spec/latest/chapters/textures.html#textures-format-validation";
        msg << DescribeInstruction();
        skip |= LogUndefinedValue("Undefined-Value-StorageImage-FormatMismatch-BufferView", objlist, loc.Get(), "%s",
                                  msg.str().c_str());
    }

    const bool buffer_format_width_64 = vkuFormatHasComponentSize(buffer_view_format, 64);
    if (buffer_format_width_64 && resource_variable.info.bit_width != 64) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view);
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::BUFFER_VIEW_ACCESS_04472), objlist, loc.Get(),
                         "the %s has a 64-bit component BufferView format (%s) but the OpTypeImage's Sampled "
                         "Type has a width of %" PRIu32 ".%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), string_VkFormat(buffer_view_format),
                         resource_variable.info.bit_width, DescribeInstruction().c_str());
    } else if (!buffer_format_width_64 && resource_variable.info.bit_width != 32) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view);
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::BUFFER_VIEW_ACCESS_04473), objlist, loc.Get(),
                         "the %s has a 32-bit component BufferView format (%s) but the OpTypeImage's Sampled "
                         "Type has a width of %" PRIu32 ".%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), string_VkFormat(buffer_view_format),
                         resource_variable.info.bit_width, DescribeInstruction().c_str());
    }

    const VkFormatFeatureFlags2 buffer_format_features = buffer_view_state->buffer_format_features;

    // Verify VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT
    if ((resource_variable.IsAtomic()) && (descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) &&
        !(buffer_format_features & VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT)) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view);
        skip |=
            LogError(CreateActionVuid(loc.Get().function, ActionVUID::BUFFER_VIEW_ATOMIC_07888), objlist, loc.Get(),
                     "the %s has %s with format of %s which doesn't support VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT.\n"
                     "(supported features: %s).%s",
                     DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(buffer_view).c_str(),
                     string_VkFormat(buffer_view_format), string_VkFormatFeatureFlags2(buffer_format_features).c_str(),
                     DescribeInstruction().c_str());
    }

    // When KHR_format_feature_flags2 is supported, the read/write without
    // format support is reported per format rather than a single physical
    // device feature.
    if (dev_proxy.device_state->special_supported.vk_khr_format_feature_flags2) {
        if (descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) {
            if ((resource_variable.info.is_read_without_format) &&
                !(buffer_format_features & VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT_KHR)) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view);
                skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::STORAGE_TEXEL_FORMAT_07030), objlist, loc.Get(),
                                 "the %s has %s with format of %s which doesn't support "
                                 "VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT_KHR.\n"
                                 "(supported features: %s).%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(buffer_view).c_str(), string_VkFormat(buffer_view_format),
                                 string_VkFormatFeatureFlags2(buffer_format_features).c_str(), DescribeInstruction().c_str());
            } else if ((resource_variable.info.is_write_without_format) &&
                       !(buffer_format_features & VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT)) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view);
                skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::STORAGE_TEXEL_FORMAT_07029), objlist, loc.Get(),
                                 "the %s has %s with format of %s which doesn't support "
                                 "VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT.\n"
                                 "(supported features: %s).%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                                 FormatHandle(buffer_view).c_str(), string_VkFormat(buffer_view_format),
                                 string_VkFormatFeatureFlags2(buffer_format_features).c_str(), DescribeInstruction().c_str());
            }
        }
    }

    if (dev_proxy.enabled_features.protectedMemory == VK_TRUE && buffer_view_state->buffer_state) {
        std::string temp_vuid = CreateActionVuid(loc.Get().function, ActionVUID::CB_UNPROTECTED_02707);
        skip |= dev_proxy.ValidateProtectedBuffer(cb_state, *buffer_view_state->buffer_state, loc.Get(), temp_vuid.c_str(),
                                                  " (Buffer is in a descriptorSet)");
        if (resource_variable.IsWrittenTo()) {
            temp_vuid = CreateActionVuid(loc.Get().function, ActionVUID::CB_PROTECTED_02712);
            skip |= dev_proxy.ValidateUnprotectedBuffer(cb_state, *buffer_view_state->buffer_state, loc.Get(), temp_vuid.c_str(),
                                                        " (Buffer is in a descriptorSet)");
        }
    }

    for (const uint32_t texel_component_count : resource_variable.write_without_formats_component_count_list) {
        const uint32_t format_component_count = vkuFormatComponentCount(buffer_view_format);
        if (texel_component_count < format_component_count) {
            const LogObjectList objlist(this->objlist, descriptor_set.Handle(), buffer_view);
            skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::STORAGE_TEXEL_04469), objlist, loc.Get(),
                             "the %s (%s) is mapped to a OpImage format of %s which has %" PRIu32
                             " components, but the OpImageWrite Texel operand only contains %" PRIu32 " components.%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(buffer_view).c_str(), string_VkFormat(buffer_view_format), format_component_count,
                             texel_component_count, DescribeInstruction().c_str());
        }
    }

    if (dev_proxy.enabled_features.tileMemoryHeap) {
        skip |= dev_proxy.ValidateBoundTileMemory(*buffer_view_state->buffer_state, cb_state, loc.Get());
    }

    return skip;
}

bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable& resource_variable, const uint32_t index,
                                             VkDescriptorType descriptor_type,
                                             const AccelerationStructureDescriptor& descriptor) const {
    bool skip = false;
    // Verify that acceleration structures are valid
    if (descriptor.IsKHR()) {
        auto acc = descriptor.GetAccelerationStructure();
        auto acc_node = descriptor.GetAccelerationStructureStateKHR();
        if (!acc_node || acc_node->Destroyed()) {
            // the AccelerationStructure could be null via nullDescriptor and accessing it is legal
            if (acc != VK_NULL_HANDLE || !dev_proxy.enabled_features.nullDescriptor) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle());
                skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                                 "the %s is using acceleration structure %s that is invalid or has been destroyed.%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(acc).c_str(),
                                 DescribeInstruction().c_str());
            }
        } else if (auto as_buffer = acc_node->GetFirstValidBuffer(*dev_proxy.device_state)) {
            for (const auto& mem_binding : as_buffer.state->GetInvalidMemory()) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle());
                skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                                 "the %s is using acceleration structure %s that references invalid memory %s.%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(acc).c_str(),
                                 FormatHandle(mem_binding->Handle()).c_str(), DescribeInstruction().c_str());
            }
        }
    } else {
        auto acc = descriptor.GetAccelerationStructureNV();
        auto acc_node = descriptor.GetAccelerationStructureStateNV();
        if (!acc_node || acc_node->Destroyed()) {
            // the AccelerationStructure could be null via nullDescriptor and accessing it is legal
            if (acc != VK_NULL_HANDLE || !dev_proxy.enabled_features.nullDescriptor) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle());
                skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                                 "the %s is using acceleration structure %s that is invalid or has been destroyed.%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(acc).c_str(),
                                 DescribeInstruction().c_str());
            }
        } else {
            for (const auto& mem_binding : acc_node->GetInvalidMemory()) {
                const LogObjectList objlist(this->objlist, descriptor_set.Handle());
                skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                                 "the %s is using acceleration structure %s that references invalid memory %s.%s",
                                 DescribeDescriptor(resource_variable, index, descriptor_type).c_str(), FormatHandle(acc).c_str(),
                                 FormatHandle(mem_binding->Handle()).c_str(), DescribeInstruction().c_str());
            }
        }
    }
    return skip;
}

// If the validation is related to both of image and sampler,
// please leave it in (descriptor_class == DescriptorClass::ImageSampler || descriptor_class ==
// DescriptorClass::Image) Here is to validate for only sampler.
bool DescriptorValidator::ValidateSamplerDescriptor(const spirv::ResourceInterfaceVariable& resource_variable, uint32_t index,
                                                    VkSampler sampler, bool is_immutable, const Sampler* sampler_state) const {
    bool skip = false;

    // maintenance4 specifies that pipeline layout and its children (set layout, immutable samplers)
    // are not accessed after they were used to create another object and can be destroyed.
    const bool can_be_destroyed = is_immutable && dev_proxy.enabled_features.maintenance4;

    // Verify Sampler still valid
    if (!sampler_state || (sampler_state->Destroyed() && !can_be_destroyed)) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s is using sampler %s that is invalid or has been destroyed.%s",
                         DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_SAMPLER).c_str(),
                         FormatHandle(sampler).c_str(), DescribeInstruction().c_str());
    } else if (sampler_state->sampler_conversion && !is_immutable) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s sampler (%s) contains a YCBCR conversion (%s), but the sampler is not an "
                         "immutable sampler.%s",
                         DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_SAMPLER).c_str(),
                         FormatHandle(sampler).c_str(), FormatHandle(sampler_state->sampler_conversion).c_str(),
                         DescribeInstruction().c_str());
    }
    return skip;
}

bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable& resource_variable, const uint32_t index,
                                             VkDescriptorType descriptor_type, const SamplerDescriptor& descriptor) const {
    return ValidateSamplerDescriptor(resource_variable, index, descriptor.GetSampler(), descriptor.IsImmutableSampler(),
                                     descriptor.GetSamplerState());
}

bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable& resource_variable, uint32_t index,
                                             VkDescriptorType descriptor_type, const vvl::TensorDescriptor& descriptor) const {
    bool skip = false;

    const vvl::TensorView* tensor_view_state = descriptor.GetTensorViewState();
    ASSERT_AND_RETURN_SKIP(tensor_view_state);
    if (tensor_view_state->Destroyed()) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s is using tensor view %s that is invalid or has been destroyed.%s",
                         DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_TENSOR_ARM).c_str(),
                         FormatHandle(tensor_view_state->Handle()).c_str(), DescribeInstruction().c_str());
        return skip;
    }
    const vvl::Tensor* tensor_state = descriptor.GetTensorState();
    ASSERT_AND_RETURN_SKIP(tensor_state);
    if (tensor_state->Destroyed()) {
        const LogObjectList objlist(this->objlist, descriptor_set.Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, ActionVUID::DESCRIPTOR_08114), objlist, loc.Get(),
                         "the %s is using tensor %s that is invalid or has been destroyed.%s",
                         DescribeDescriptor(resource_variable, index, VK_DESCRIPTOR_TYPE_TENSOR_ARM).c_str(),
                         FormatHandle(tensor_state->Handle()).c_str(), DescribeInstruction().c_str());
        return skip;
    }

    if (tensor_state->unprotected) {
        skip |= dev_proxy.ValidateUnprotectedTensor(cb_state, *tensor_state, loc.Get());
    } else {
        skip |= dev_proxy.ValidateProtectedTensor(cb_state, *tensor_state, loc.Get());
    }

    // These VUs are only for tensors used in _shaders_. For use in _datagraphs_, VU 9923 applies.
    // https://gitlab.khronos.org/vulkan/vulkan/-/issues/4482#note_564658
    if (loc.Get().function != Func::vkCmdDispatchDataGraphARM) {
        if (resource_variable.info.tensor_rank != tensor_state->create_info.pDescription->dimensionCount) {
            const LogObjectList objlist(cb_state.Handle(), this->objlist, descriptor_set.Handle(), tensor_state->Handle());
            skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::TENSOR_09905), objlist, loc.Get(),
                             "the %s is using tensor %s created with dimensionCount %" PRIu32
                             ", but the corresponding OpTypeTensorARM has rank %" PRIu32 ".%s",
                             DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                             FormatHandle(tensor_state->Handle()).c_str(), tensor_state->create_info.pDescription->dimensionCount,
                             resource_variable.info.tensor_rank, DescribeInstruction().c_str());
        }
        if (resource_variable.info.vk_format != tensor_view_state->create_info.format) {
            const LogObjectList objlist(cb_state.Handle(), this->objlist, descriptor_set.Handle(), tensor_view_state->Handle());
            skip |=
                LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::SPIRV_OPTYPETENSORARM_09906), objlist, loc.Get(),
                         "the %s is using tensor %s that was created with %s but doesn't match the OpTypeTensorARM of type %s and "
                         "width %" PRIu32 " (equivalent to %s).%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(tensor_view_state->Handle()).c_str(), string_VkFormat(tensor_view_state->create_info.format),
                         spirv::string_NumericType(resource_variable.info.numeric_type), resource_variable.info.bit_width,
                         string_VkFormat(resource_variable.info.vk_format), DescribeInstruction().c_str());
        }
    }

    VkTensorUsageFlagBitsARM usage_flag = (loc.Get().function == Func::vkCmdDispatchDataGraphARM)
                                              ? VK_TENSOR_USAGE_DATA_GRAPH_BIT_ARM
                                              : VK_TENSOR_USAGE_SHADER_BIT_ARM;
    if ((tensor_state->create_info.pDescription->usage & usage_flag) == 0) {
        const LogObjectList objlist(cb_state.Handle(), this->objlist, descriptor_set.Handle(), tensor_state->Handle());
        skip |= LogError(CreateActionVuid(loc.Get().function, vvl::ActionVUID::TENSOR_09900), objlist, loc.Get(),
                         "the %s is using tensor %s created with usage %s, which doesn't include %s.%s",
                         DescribeDescriptor(resource_variable, index, descriptor_type).c_str(),
                         FormatHandle(tensor_state->Handle()).c_str(),
                         string_VkTensorUsageFlagBitsARM(tensor_state->create_info.pDescription->usage),
                         string_VkTensorUsageFlagBitsARM(usage_flag), DescribeInstruction().c_str());
    }

    return skip;
}

}  // namespace vvl
