/* Copyright (c) 2023-2024 The Khronos Group Inc.
 * Copyright (c) 2023-2024 Valve Corporation
 * Copyright (c) 2023-2024 LunarG, Inc.
 *
 * 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 "generated/spirv_grammar_helper.h"
#include "state_tracker/shader_stage_state.h"
#include "error_message/error_strings.h"
#include "state_tracker/descriptor_sets.h"
#include "state_tracker/cmd_buffer_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 "drawdispatch/drawdispatch_vuids.h"
#include "utils/vk_layer_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) {
    switch (command) {
        case Func::vkCmdDispatch:
        case Func::vkCmdDispatchIndirect:
        case Func::vkCmdDispatchBase:
        case Func::vkCmdDispatchBaseKHR:
        case Func::vkCmdDispatchGraphAMDX:
        case Func::vkCmdDispatchGraphIndirectAMDX:
        case Func::vkCmdDispatchGraphIndirectCountAMDX:
            return "dispatch";
        case Func::vkCmdTraceRaysNV:
        case Func::vkCmdTraceRaysKHR:
        case Func::vkCmdTraceRaysIndirectKHR:
        case Func::vkCmdTraceRaysIndirect2KHR:
            return "trace rays";
        default:
            return "draw";
    }
}

std::string DescriptorValidator::DescribeDescriptor(const spirv::ResourceInterfaceVariable &resource_variable,
                                                    uint32_t index) const {
    std::stringstream ss;
    ss << dev_state.FormatHandle(descriptor_set.Handle()) << " [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();
}

DescriptorValidator::DescriptorValidator(ValidationStateTracker &dev, CommandBuffer &cb_state, DescriptorSet &descriptor_set,
                                         uint32_t set_index, VkFramebuffer framebuffer, const Location &loc)
    : dev_state(dev),
      cb_state(cb_state),
      descriptor_set(descriptor_set),
      set_index(set_index),
      framebuffer(framebuffer),
      loc(loc),
      vuids(GetDrawDispatchVuid(loc.function)) {}

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

        if (!binding.updated[index]) {
            auto set = descriptor_set.Handle();
            return dev_state.LogError(
                vuids.descriptor_buffer_bit_set_08114, set, loc,
                "the descriptor %s is being used in %s but has never been updated via vkUpdateDescriptorSets() or a similar call.",
                DescribeDescriptor(resource_variable, index).c_str(), GetActionType(loc.function));
        }
        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::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]) {
        auto set = descriptor_set.Handle();
        return dev_state.LogError(
            vuids.descriptor_buffer_bit_set_08114, set, loc,
            "the descriptor %s is being used in %s but has never been updated via vkUpdateDescriptorSets() or a similar call.",
            DescribeDescriptor(resource_variable, index).c_str(), GetActionType(loc.function));
    }
    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 &imgs_binding = static_cast<ImageSamplerBinding &>(binding);
            auto &descriptor = imgs_binding.descriptors[index];
            descriptor.UpdateDrawState(cb_state);
            skip |= ValidateDescriptorsDynamic(resource_variable, imgs_binding, index);
            break;
        }
        case DescriptorClass::Image: {
            auto &img_binding = static_cast<ImageBinding &>(binding);
            auto &descriptor = img_binding.descriptors[index];
            descriptor.UpdateDrawState(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::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 {
    // Verify that buffers are valid
    const VkBuffer buffer = descriptor.GetBuffer();
    auto buffer_node = descriptor.GetBufferState();
    if ((!buffer_node && !dev_state.enabled_features.nullDescriptor) || (buffer_node && buffer_node->Destroyed())) {
        auto set = descriptor_set.Handle();
        return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                  "the descriptor %s is using buffer %s that is invalid or has been destroyed.",
                                  DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(buffer).c_str());
    }

    // Buffer could be null via nullDescriptor and accessing it is legal
    if (buffer == VK_NULL_HANDLE) {
        return false;
    }
    if (buffer_node /* && !buffer_node->sparse*/) {
        for (const auto &binding : buffer_node->GetInvalidMemory()) {
            auto set = descriptor_set.Handle();
            return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                      "the descriptor %s is using buffer %s that references invalid memory %s.",
                                      DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(buffer).c_str(),
                                      dev_state.FormatHandle(binding->Handle()).c_str());
        }
    }
    if (dev_state.enabled_features.protectedMemory == VK_TRUE) {
        if (dev_state.ValidateProtectedBuffer(cb_state, *buffer_node, loc, vuids.unprotected_command_buffer_02707,
                                              " (Buffer is in a descriptorSet)")) {
            return true;
        }
        if (resource_variable.IsWrittenTo() &&
            dev_state.ValidateUnprotectedBuffer(cb_state, *buffer_node, loc, vuids.protected_command_buffer_02712,
                                                " (Buffer is in a descriptorSet)")) {
            return true;
        }
    }
    return false;
}

// '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;
    const bool is_gpu_av = dev_state.container_type == LayerObjectTypeGpuAssisted;
    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.
                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_state.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

        auto set = descriptor_set.Handle();
        return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                  "the descriptor %s is using imageView %s that is invalid or has been destroyed.",
                                  DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_view).c_str());
    }

    // 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;

    const spv::Dim dim = resource_variable.info.image_dim;
    const bool is_image_array = resource_variable.info.is_image_array;

    // if combined sampler, this variable might not be a OpTypeImage
    // SubpassData gets validated elsewhere
    if (resource_variable.IsImage() && dim != spv::DimSubpassData) {
        bool valid_dim = true;
        // 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;
                break;
            case VK_IMAGE_VIEW_TYPE_2D:
                valid_dim = (dim == spv::Dim2D) && !is_image_array;
                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;
                break;
            case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
                valid_dim = (dim == spv::Dim2D) && is_image_array;
                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) {
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, image_view);
            return dev_state.LogError(
                vuids.image_view_dim_07752, objlist, loc,
                "the descriptor %s ImageView type is %s but the OpTypeImage has (Dim = %s) and (Arrayed = %" PRIu32 ").",
                DescribeDescriptor(resource_variable, index).c_str(), string_VkImageViewType(image_view_ci.viewType),
                string_SpvDim(dim), is_image_array);
        }

        // Because you can have a runtime array with different types in it, without extensive GPU-AV tracking, we have no way to
        // detect if the types match up in a given index
        const bool is_descriptor_potentially_aliased =
            resource_variable.array_length == spirv::kRuntimeArray || (is_gpu_av && resource_variable.array_length > 1);
        if (!is_descriptor_potentially_aliased &&
            ((resource_variable.info.image_format_type & image_view_state->descriptor_format_bits) == 0)) {
            const bool signed_override =
                ((resource_variable.info.image_format_type & spirv::NumericTypeUint) && resource_variable.info.is_sign_extended);
            const bool unsigned_override =
                ((resource_variable.info.image_format_type & spirv::NumericTypeSint) && resource_variable.info.is_zero_extended);
            if (!signed_override && !unsigned_override) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view);
                return dev_state.LogError(vuids.image_view_numeric_format_07753, objlist, loc,
                                          "the descriptor %s requires %s component type, but bound descriptor format is %s.",
                                          DescribeDescriptor(resource_variable, index).c_str(),
                                          spirv::string_NumericType(resource_variable.info.image_format_type),
                                          string_VkFormat(image_view_ci.format));
            }
        }

        const bool image_format_width_64 = vkuFormatHasComponentSize(image_view_ci.format, 64);
        if (image_format_width_64) {
            if (resource_variable.image_sampled_type_width != 64) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view);
                return dev_state.LogError(vuids.image_view_access_64_04470, objlist, loc,
                                          "the descriptor %s has a 64-bit component ImageView format (%s) but the OpTypeImage's "
                                          "Sampled Type has a width of %" PRIu32 ".",
                                          DescribeDescriptor(resource_variable, index).c_str(),
                                          string_VkFormat(image_view_ci.format), resource_variable.image_sampled_type_width);
            } else if (!dev_state.enabled_features.sparseImageInt64Atomics && image_state->sparse_residency) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view, image_state->Handle());
                return dev_state.LogError(
                    vuids.image_view_sparse_64_04474, objlist, loc,
                    "the descriptor %s has a OpTypeImage's Sampled Type has a width of 64 backed by a sparse Image, but "
                    "sparseImageInt64Atomics is not enabled.",
                    DescribeDescriptor(resource_variable, index).c_str());
            }
        } else if (!image_format_width_64 && resource_variable.image_sampled_type_width != 32) {
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, image_view);
            return dev_state.LogError(vuids.image_view_access_32_04471, objlist, loc,
                                      "the descriptor %s has a 32-bit component ImageView format (%s) but the OpTypeImage's "
                                      "Sampled Type has a width of %" PRIu32 ".",
                                      DescribeDescriptor(resource_variable, index).c_str(), string_VkFormat(image_view_ci.format),
                                      resource_variable.image_sampled_type_width);
        }
    }

    if (!dev_state.disabled[image_layout_validation]) {
        VkImageLayout image_layout = image_descriptor.GetImageLayout();
        // Verify Image Layout
        // No "invalid layout" VUID required for this call, since the optimal_layout parameter is UNDEFINED.
        bool hit_error = false;
        dev_state.VerifyImageLayout(cb_state, *image_view_state, image_layout, loc,
                          "VUID-VkDescriptorImageInfo-imageLayout-00344", &hit_error);
        if (hit_error) {
            auto set = descriptor_set.Handle();
            std::stringstream msg;
            if (!descriptor_set.IsPushDescriptor()) {
                msg << "Descriptor set " << dev_state.FormatHandle(set)
                    << " Image layout specified by vkCmdBindDescriptorSets doesn't match actual image layout at time "
                       "descriptor is used";
            } else {
                msg << "Image layout specified by vkCmdPushDescriptorSet doesn't match actual image layout at time "
                       "descriptor is used";
            }
            return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                            "%s. See previous error callback for specific details.", msg.str().c_str());
        }
    }

    // Verify Sample counts
    if (resource_variable.IsImage()) {
        if (!resource_variable.info.is_multisampled && image_view_state->samples != VK_SAMPLE_COUNT_1_BIT) {
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, image_view);
            return dev_state.LogError("VUID-RuntimeSpirv-samples-08725", objlist, loc, "the descriptor %s has %s created with %s.",
                                      DescribeDescriptor(resource_variable, index).c_str(),
                                      dev_state.FormatHandle(image_state->Handle()).c_str(),
                                      string_VkSampleCountFlagBits(image_view_state->samples));
        }
        if (resource_variable.info.is_multisampled && image_view_state->samples == VK_SAMPLE_COUNT_1_BIT) {
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, image_view);
            return dev_state.LogError(
                "VUID-RuntimeSpirv-samples-08726", objlist, loc, "the descriptor %s has %s created with VK_SAMPLE_COUNT_1_BIT.",
                DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_state->Handle()).c_str());
        }
    }

    if (image_view_state->samplerConversion) {
        if (resource_variable.info.is_not_sampler_sampled) {
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, image_view);
            return dev_state.LogError(vuids.image_ycbcr_sampled_06550, objlist, loc,
                                      "the image descriptor %s was created with a sampler Ycbcr conversion, but was accessed with "
                                      "a non OpImage*Sample* command.",
                                      DescribeDescriptor(resource_variable, index).c_str());
        }
        if (resource_variable.info.is_sampler_offset) {
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, image_view);
            return dev_state.LogError(vuids.image_ycbcr_offset_06551, objlist, loc,
                                      "the image descriptor %s was created with a sampler Ycbcr conversion, but was accessed with "
                                      "ConstOffset/Offset image operands.",
                                      DescribeDescriptor(resource_variable, index).c_str());
        }
    }

    // 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)) {
        auto set = descriptor_set.Handle();
        const LogObjectList objlist(set, image_view);
        return dev_state.LogError(
            vuids.imageview_atomic_02691, objlist, loc,
            "the descriptor %s has %s with format of %s which is missing VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT.\n"
            "(supported features: %s).",
            DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_view).c_str(),
            string_VkFormat(image_view_ci.format), string_VkFormatFeatureFlags2(image_view_state->format_features).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_state.has_format_feature2) {
        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)) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view);
                return dev_state.LogError(vuids.storage_image_read_without_format_07028, objlist, loc,
                                          "the descriptor %s has %s with format of %s which doesn't support "
                                          "VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT.\n"
                                          "(supported features: %s).",
                                          DescribeDescriptor(resource_variable, index).c_str(),
                                          dev_state.FormatHandle(image_view).c_str(), string_VkFormat(image_view_ci.format),
                                          string_VkFormatFeatureFlags2(format_features).c_str());
            }

            if ((resource_variable.info.is_write_without_format) &&
                !(format_features & VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT)) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view);
                return dev_state.LogError(vuids.storage_image_write_without_format_07027, objlist, loc,
                                          "the descriptor %s has %s with format of %s which doesn't support "
                                          "VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT.\n"
                                          "(supported features: %s).",
                                          DescribeDescriptor(resource_variable, index).c_str(),
                                          dev_state.FormatHandle(image_view).c_str(), string_VkFormat(image_view_ci.format),
                                          string_VkFormatFeatureFlags2(format_features).c_str());
            }
        }

        if ((resource_variable.info.is_dref) && !(format_features & VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT)) {
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, image_view);
            return dev_state.LogError(vuids.depth_compare_sample_06479, objlist, loc,
                                      "the descriptor %s has %s with format of %s which doesn't support "
                                      "VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT.\n"
                                      "(supported features: %s).",
                                      DescribeDescriptor(resource_variable, index).c_str(),
                                      dev_state.FormatHandle(image_view).c_str(), string_VkFormat(image_view_ci.format),
                                      string_VkFormatFeatureFlags2(format_features).c_str());
        }
    }

    const uint32_t binding_index = resource_variable.decorations.binding;
    // Verify if attachments are used in DescriptorSet
    if (!cb_state.active_attachments.empty() && !cb_state.active_subpasses.empty() &&
        (descriptor_type != VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT)) {
        for (uint32_t att_index = 0; att_index < cb_state.active_attachments.size(); ++att_index) {
            const auto *view_state = cb_state.active_attachments[att_index].image_view;
            const SubpassInfo &subpass = cb_state.active_subpasses[att_index];
            if (!view_state || view_state->Destroyed()) {
                continue;
            }
            const bool same_view = view_state->VkHandle() == image_view;
            const bool overlapping_view = image_view_state->OverlapSubresource(*view_state);
            if (!same_view && !overlapping_view) {
                continue;
            }

            bool descriptor_written_to = false;
            const auto pipeline = cb_state.GetCurrentPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS);
            for (const auto &stage : pipeline->stage_states) {
                if (!stage.entrypoint) 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 bool layout_read_only = IsImageLayoutReadOnly(subpass.layout);
            const bool read_attachment = (subpass.usage & (VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT)) != 0;
            if (read_attachment && descriptor_written_to) {
                if (same_view) {
                    auto set = descriptor_set.Handle();
                    const LogObjectList objlist(set, image_view, framebuffer);
                    return dev_state.LogError(vuids.image_subresources_subpass_write_06539, objlist, loc,
                                              "the descriptor %s has %s which will be read from as %s attachment %" PRIu32 ".",
                                              DescribeDescriptor(resource_variable, index).c_str(),
                                              dev_state.FormatHandle(image_view).c_str(),
                                              dev_state.FormatHandle(framebuffer).c_str(), att_index);
                } else if (overlapping_view) {
                    auto set = descriptor_set.Handle();
                    const LogObjectList objlist(set, image_view, framebuffer, view_state->Handle());
                    return dev_state.LogError(
                        vuids.image_subresources_subpass_write_06539, objlist, loc,
                        "the descriptor %s has %s which will be overlap read from as %s in %s attachment %" PRIu32 " overlap.",
                        DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_view).c_str(),
                        dev_state.FormatHandle(view_state->Handle()).c_str(), dev_state.FormatHandle(framebuffer).c_str(),
                        att_index);
                }
            }

            if (descriptor_written_to && !layout_read_only) {
                if (same_view) {
                    auto set = descriptor_set.Handle();
                    const LogObjectList objlist(set, image_view, framebuffer);
                    return dev_state.LogError(vuids.image_subresources_render_pass_write_06537, objlist, loc,
                                              "the descriptor %s has %s which is written to but is also %s attachment %" PRIu32 ".",
                                              DescribeDescriptor(resource_variable, index).c_str(),
                                              dev_state.FormatHandle(image_view).c_str(),
                                              dev_state.FormatHandle(framebuffer).c_str(), att_index);
                } else if (overlapping_view) {
                    auto set = descriptor_set.Handle();
                    const LogObjectList objlist(set, image_view, framebuffer, view_state->Handle());
                    return dev_state.LogError(
                        vuids.image_subresources_render_pass_write_06537, objlist, loc,
                        "the descriptor %s has %s which overlaps writes to %s but is also %s attachment %" PRIu32 ".",
                        DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_view).c_str(),
                        dev_state.FormatHandle(view_state->Handle()).c_str(), dev_state.FormatHandle(framebuffer).c_str(),
                        att_index);
                }
            }
        }
    }

    if (dev_state.enabled_features.protectedMemory == VK_TRUE) {
        if (dev_state.ValidateProtectedImage(cb_state, *image_state, loc, vuids.unprotected_command_buffer_02707,
                                             " (Image is in a descriptorSet)")) {
            return true;
        }
        if (resource_variable.IsWrittenTo() &&
            dev_state.ValidateUnprotectedImage(cb_state, *image_state, loc, vuids.protected_command_buffer_02712,
                                               " (Image is in a descriptorSet)")) {
            return true;
        }
    }

    const VkFormat image_view_format = image_view_state->create_info.format;
    for (const auto *sampler_state : sampler_states) {
        if (!sampler_state || sampler_state->Destroyed()) {
            continue;
        }

        // TODO: Validate 04015 for DescriptorClass::PlainSampler
        if ((sampler_state->create_info.borderColor == VK_BORDER_COLOR_INT_CUSTOM_EXT ||
             sampler_state->create_info.borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT) &&
            (sampler_state->customCreateInfo.format == VK_FORMAT_UNDEFINED)) {
            if (image_view_format == VK_FORMAT_B4G4R4A4_UNORM_PACK16 || image_view_format == VK_FORMAT_B5G6R5_UNORM_PACK16 ||
                image_view_format == VK_FORMAT_B5G5R5A1_UNORM_PACK16 || image_view_format == VK_FORMAT_A1B5G5R5_UNORM_PACK16) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, sampler_state->Handle(), image_view_state->Handle());
                return dev_state.LogError(
                    "VUID-VkSamplerCustomBorderColorCreateInfoEXT-format-04015", objlist, loc,
                    "the descriptor %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",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                    dev_state.FormatHandle(image_view_state->Handle()).c_str(), string_VkFormat(image_view_format));
            }
        }
        const VkFilter sampler_mag_filter = sampler_state->create_info.magFilter;
        const VkFilter sampler_min_filter = sampler_state->create_info.minFilter;
        const bool sampler_compare_enable = sampler_state->create_info.compareEnable == VK_TRUE;
        const auto sampler_reduction =
            vku::FindStructInPNextChain<VkSamplerReductionModeCreateInfo>(sampler_state->create_info.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) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, sampler_state->Handle(), image_view_state->Handle());
                return dev_state.LogError(
                    vuids.linear_filter_sampler_04553, objlist, loc,
                    "the descriptor %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.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                    dev_state.FormatHandle(image_view_state->Handle()).c_str(), string_VkFormat(image_view_format));
            }
            if (sampler_state->create_info.mipmapMode == VK_SAMPLER_MIPMAP_MODE_LINEAR) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, sampler_state->Handle(), image_view_state->Handle());
                return dev_state.LogError(
                    vuids.linear_mipmap_sampler_04770, objlist, loc,
                    "the descriptor %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.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                    dev_state.FormatHandle(image_view_state->Handle()).c_str(), string_VkFormat(image_view_format));
            }
        }

        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) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, sampler_state->Handle(), image_view_state->Handle());
                return dev_state.LogError(
                    vuids.linear_filter_sampler_09598, objlist, loc,
                    "the descriptor %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.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                    string_VkSamplerReductionMode(sampler_reduction->reductionMode),
                    dev_state.FormatHandle(image_view_state->Handle()).c_str(), string_VkFormat(image_view_format));
            }
            if (sampler_state->create_info.mipmapMode == VK_SAMPLER_MIPMAP_MODE_LINEAR) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, sampler_state->Handle(), image_view_state->Handle());
                return dev_state.LogError(
                    vuids.linear_mipmap_sampler_09599, objlist, loc,
                    "the descriptor %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.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                    string_VkSamplerReductionMode(sampler_reduction->reductionMode),
                    dev_state.FormatHandle(image_view_state->Handle()).c_str(), string_VkFormat(image_view_format));
            }
        }

        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)) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, sampler_state->Handle(), image_view_state->Handle());
                return dev_state.LogError(
                    vuids.cubic_sampler_02692, objlist, loc,
                    "the descriptor %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.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                    dev_state.FormatHandle(image_view_state->Handle()).c_str(),
                    string_VkFormat(image_view_state->create_info.format));
            }

            if (IsExtEnabled(dev_state.device_extensions.vk_ext_filter_cubic)) {
                const auto reduction_mode_info =
                    vku::FindStructInPNextChain<VkSamplerReductionModeCreateInfo>(sampler_state->create_info.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) {
                    auto set = descriptor_set.Handle();
                    const LogObjectList objlist(set, sampler_state->Handle(), image_view_state->Handle());
                    return dev_state.LogError(
                        vuids.filter_cubic_min_max_02695, objlist, loc,
                        "the descriptor %s has %s which is set to use VK_FILTER_CUBIC_EXT & %s, but image view "
                        "(%s) doesn't support filterCubicMinmax.",
                        DescribeDescriptor(resource_variable, index).c_str(),
                        dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                        string_VkSamplerReductionMode(reduction_mode_info->reductionMode),
                        dev_state.FormatHandle(image_view_state->Handle()).c_str());
                }

                if (!image_view_state->filter_cubic_props.filterCubic) {
                    auto set = descriptor_set.Handle();
                    const LogObjectList objlist(set, sampler_state->Handle(), image_view_state->Handle());
                    return dev_state.LogError(
                        vuids.filter_cubic_02694, objlist, loc,
                        "the descriptor %s has %s which is set to use VK_FILTER_CUBIC_EXT, but image view (%s) "
                        "doesn't support filterCubic.",
                        DescribeDescriptor(resource_variable, index).c_str(),
                        dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                        dev_state.FormatHandle(image_view_state->Handle()).c_str());
                }
            }

            if (IsExtEnabled(dev_state.device_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) {
                    auto set = descriptor_set.Handle();
                    const LogObjectList objlist(set, sampler_state->Handle(), image_view_state->Handle());
                    return dev_state.LogError(
                        vuids.img_filter_cubic_02693, objlist, loc,
                        "the descriptor %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).",
                        DescribeDescriptor(resource_variable, index).c_str(),
                        dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                        dev_state.FormatHandle(image_view_state->Handle()).c_str(),
                        string_VkImageViewType(image_view_state->create_info.viewType));
                }
            }
        }
        if ((image_state->create_info.flags & VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV) &&
            (sampler_state->create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ||
             sampler_state->create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ||
             sampler_state->create_info.addressModeW != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)) {
            std::string address_mode_letter =
                (sampler_state->create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)   ? "U"
                : (sampler_state->create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) ? "V"
                                                                                                     : "W";
            VkSamplerAddressMode address_mode = (sampler_state->create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)
                                                    ? sampler_state->create_info.addressModeU
                                                : (sampler_state->create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)
                                                    ? sampler_state->create_info.addressModeV
                                                    : sampler_state->create_info.addressModeW;
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, sampler_state->Handle(), image_state->Handle(), image_view_state->Handle());
            return dev_state.LogError(
                vuids.corner_sampled_address_mode_02696, objlist, loc,
                "the descriptor %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.",
                DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_state->Handle()).c_str(),
                dev_state.FormatHandle(image_view_state->Handle()).c_str(), dev_state.FormatHandle(sampler_state->Handle()).c_str(),
                address_mode_letter.c_str(), string_VkSamplerAddressMode(address_mode));
        }

        // UnnormalizedCoordinates sampler validations
        // only check if sampled as could have a texelFetch on a combined image sampler
        if (sampler_state->create_info.unnormalizedCoordinates && resource_variable.info.is_sampler_sampled) {
            // If ImageView is used by a unnormalizedCoordinates sampler, it needs to check ImageView type
            if (image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_3D || image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_CUBE ||
                image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY || image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY ||
                image_view_ci.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view, sampler_state->Handle());
                return dev_state.LogError(
                    vuids.sampler_imageview_type_08609, objlist, loc, "the descriptor %s Image View %s, type %s, is used by %s.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_view).c_str(),
                    string_VkImageViewType(image_view_ci.viewType), dev_state.FormatHandle(sampler_state->Handle()).c_str());
            }

            const auto &subresource_range = image_view_state->normalized_subresource_range;
            if (subresource_range.levelCount != 1) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view, sampler_state->Handle());
                return dev_state.LogError(
                    vuids.unnormalized_coordinates_09635, objlist, loc,
                    "the descriptor %s Image View %s was created with levelCount of %s, but the sampler (%s) was created with "
                    "unnormalizedCoordinates.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_view).c_str(),
                    string_LevelCount(image_state->create_info, image_view_ci.subresourceRange).c_str(),
                    dev_state.FormatHandle(sampler_state->Handle()).c_str());
            }

            if (subresource_range.layerCount != 1) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view, sampler_state->Handle());
                return dev_state.LogError(
                    vuids.unnormalized_coordinates_09635, objlist, loc,
                    "the descriptor %s Image View %s was created with layerCount of %s, but the sampler (%s) was created with "
                    "unnormalizedCoordinates.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_view).c_str(),
                    string_LayerCount(image_state->create_info, image_view_ci.subresourceRange).c_str(),
                    dev_state.FormatHandle(sampler_state->Handle()).c_str());
            }

            // sampler must not be used with any of the SPIR-V OpImageSample* or OpImageSparseSample*
            // instructions with ImplicitLod, Dref or Proj in their name
            if (resource_variable.info.is_sampler_implicitLod_dref_proj) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view, sampler_state->Handle());
                return dev_state.LogError(vuids.sampler_implicitLod_dref_proj_08610, objlist, loc,
                                          "the descriptor %s Image View %s is used by %s that uses invalid operator.",
                                          DescribeDescriptor(resource_variable, index).c_str(),
                                          dev_state.FormatHandle(image_view).c_str(),
                                          dev_state.FormatHandle(sampler_state->Handle()).c_str());
            }

            // sampler must not be used with any of the SPIR-V OpImageSample* or OpImageSparseSample*
            // instructions that includes a LOD bias or any offset values
            if (resource_variable.info.is_sampler_bias_offset) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view, sampler_state->Handle());
                return dev_state.LogError(
                    vuids.sampler_bias_offset_08611, objlist, loc,
                    "the descriptor %s Image View %s is used by %s that uses invalid bias or offset operator.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(image_view).c_str(),
                    dev_state.FormatHandle(sampler_state->Handle()).c_str());
            }
        }

        if (sampler_state->samplerConversion) {
            if (resource_variable.info.is_not_sampler_sampled) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view, sampler_state->Handle());
                return dev_state.LogError(vuids.image_ycbcr_sampled_06550, set, loc,
                                          "the sampler descriptor %s was created with a sampler Ycbcr conversion, but was accessed "
                                          "with a non OpImage*Sample* command.",
                                          DescribeDescriptor(resource_variable, index).c_str());
            }
            if (resource_variable.info.is_sampler_offset) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view, sampler_state->Handle());
                return dev_state.LogError(vuids.image_ycbcr_offset_06551, set, loc,
                                          "the sampler descriptor %s was created with a sampler Ycbcr conversion, but was accessed "
                                          "with ConstOffset/Offset image operands.",
                                          DescribeDescriptor(resource_variable, index).c_str());
            }
        }
    }

    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) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, image_view);
                return dev_state.LogError(vuids.storage_image_write_texel_count_08796, objlist, loc,
                                          "the descriptor %s VkImageView is mapped to a OpImage format of VK_FORMAT_A8_UNORM, "
                                          "but the OpImageWrite Texel "
                                          "operand only contains %" PRIu32 " components.",
                                          DescribeDescriptor(resource_variable, index).c_str(), texel_component_count);
            }
        } else if (texel_component_count < format_component_count) {
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, image_view);
            return dev_state.LogError(vuids.storage_image_write_texel_count_08795, objlist, loc,
                                      "the descriptor %s VkImageView is mapped to a OpImage format of %s which has %" PRIu32
                                      " components, but the OpImageWrite Texel operand only contains %" PRIu32 " components.",
                                      DescribeDescriptor(resource_variable, index).c_str(), string_VkFormat(image_view_format),
                                      format_component_count, texel_component_count);
        }
    }

    return false;
}

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 {
    const VkBufferView buffer_view = texel_descriptor.GetBufferView();
    auto buffer_view_state = texel_descriptor.GetBufferViewState();
    if ((!buffer_view_state && !dev_state.enabled_features.nullDescriptor) || (buffer_view_state && buffer_view_state->Destroyed())) {
        auto set = descriptor_set.Handle();
        return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                  "the descriptor %s is using bufferView %s that is invalid or has been destroyed.",
                                  DescribeDescriptor(resource_variable, index).c_str(),
                                  dev_state.FormatHandle(buffer_view).c_str());
    }

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

    auto buffer = buffer_view_state->create_info.buffer;
    const auto *buffer_state = buffer_view_state->buffer_state.get();
    if (!buffer_state || buffer_state->Destroyed()) {
        auto set = descriptor_set.Handle();
        return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                  "the descriptor %s is using buffer %s that has been destroyed.",
                                  DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(buffer).c_str());
    }
    const VkFormat buffer_view_format = buffer_view_state->create_info.format;
    const uint32_t format_bits = spirv::GetFormatType(buffer_view_format);

    if ((resource_variable.info.image_format_type & format_bits) == 0) {
        const bool signed_override =
            ((resource_variable.info.image_format_type & spirv::NumericTypeUint) && resource_variable.info.is_sign_extended);
        const bool unsigned_override =
            ((resource_variable.info.image_format_type & spirv::NumericTypeSint) && resource_variable.info.is_zero_extended);
        if (!signed_override && !unsigned_override) {
            auto set = descriptor_set.Handle();
            return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                      "the descriptor %s requires %s component type, but bound descriptor format is %s.",
                                      DescribeDescriptor(resource_variable, index).c_str(),
                                      spirv::string_NumericType(resource_variable.info.image_format_type),
                                      string_VkFormat(buffer_view_format));
        }
    }

    const bool buffer_format_width_64 = vkuFormatHasComponentSize(buffer_view_format, 64);
    if (buffer_format_width_64 && resource_variable.image_sampled_type_width != 64) {
        auto set = descriptor_set.Handle();
        const LogObjectList objlist(set, buffer_view);
        return dev_state.LogError(vuids.buffer_view_access_64_04472, objlist, loc,
                                  "the descriptor %s has a 64-bit component BufferView format (%s) but the OpTypeImage's Sampled "
                                  "Type has a width of %" PRIu32 ".",
                                  DescribeDescriptor(resource_variable, index).c_str(), string_VkFormat(buffer_view_format),
                                  resource_variable.image_sampled_type_width);
    } else if (!buffer_format_width_64 && resource_variable.image_sampled_type_width != 32) {
        auto set = descriptor_set.Handle();
        const LogObjectList objlist(set, buffer_view);
        return dev_state.LogError(vuids.buffer_view_access_32_04473, objlist, loc,
                                  "the descripto %s has a 32-bit component BufferView format (%s) but the OpTypeImage's Sampled "
                                  "Type has a width of %" PRIu32 ".",
                                  DescribeDescriptor(resource_variable, index).c_str(), string_VkFormat(buffer_view_format),
                                  resource_variable.image_sampled_type_width);
    }

    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)) {
        auto set = descriptor_set.Handle();
        const LogObjectList objlist(set, buffer_view);
        return dev_state.LogError(
            vuids.bufferview_atomic_07888, objlist, loc,
            "the descriptor %s has %s with format of %s which is missing VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT.\n"
            "(supported features: %s).",
            DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(buffer_view).c_str(),
            string_VkFormat(buffer_view_format), string_VkFormatFeatureFlags2(buffer_format_features).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_state.has_format_feature2) {
        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)) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, buffer_view);
                return dev_state.LogError(vuids.storage_texel_buffer_read_without_format_07030, objlist, loc,
                                          "the descriptor %s has %s with format of %s which is missing "
                                          "VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT_KHR.\n"
                                          "(supported features: %s).",
                                          DescribeDescriptor(resource_variable, index).c_str(),
                                          dev_state.FormatHandle(buffer_view).c_str(), string_VkFormat(buffer_view_format),
                                          string_VkFormatFeatureFlags2(buffer_format_features).c_str());
            }

            if ((resource_variable.info.is_write_without_format) &&
                !(buffer_format_features & VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT)) {
                auto set = descriptor_set.Handle();
                const LogObjectList objlist(set, buffer_view);
                return dev_state.LogError(vuids.storage_texel_buffer_write_without_format_07029, objlist, loc,
                                          "the descriptor %s has %s with format of %s which is missing "
                                          "VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT.\n"
                                          "(supported features: %s).",
                                          DescribeDescriptor(resource_variable, index).c_str(),
                                          dev_state.FormatHandle(buffer_view).c_str(), string_VkFormat(buffer_view_format),
                                          string_VkFormatFeatureFlags2(buffer_format_features).c_str());
            }
        }
    }

    if (dev_state.enabled_features.protectedMemory == VK_TRUE && buffer_view_state->buffer_state) {
        if (dev_state.ValidateProtectedBuffer(cb_state, *buffer_view_state->buffer_state, loc,
                                              vuids.unprotected_command_buffer_02707, " (Buffer is in a descriptorSet)")) {
            return true;
        }
        if (resource_variable.IsWrittenTo() &&
            dev_state.ValidateUnprotectedBuffer(cb_state, *buffer_view_state->buffer_state, loc,
                                                vuids.protected_command_buffer_02712, " (Buffer is in a descriptorSet)")) {
            return true;
        }
    }

    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) {
            auto set = descriptor_set.Handle();
            const LogObjectList objlist(set, buffer_view);
            return dev_state.LogError(vuids.storage_texel_buffer_write_texel_count_04469, objlist, loc,
                                      "the descriptor %s VkImageView is mapped to a OpImage format of %s which has %" PRIu32
                                      " components, but the OpImageWrite Texel operand only contains %" PRIu32 " components.",
                                      DescribeDescriptor(resource_variable, index).c_str(), string_VkFormat(buffer_view_format),
                                      format_component_count, texel_component_count);
        }
    }

    return false;
}

bool DescriptorValidator::ValidateDescriptor(const spirv::ResourceInterfaceVariable &resource_variable, const uint32_t index,
                                             VkDescriptorType descriptor_type,
                                             const AccelerationStructureDescriptor &descriptor) const {
    // 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_state.enabled_features.nullDescriptor) {
                auto set = descriptor_set.Handle();
                return dev_state.LogError(
                    vuids.descriptor_buffer_bit_set_08114, set, loc,
                    "the descriptor %s is using acceleration structure %s that is invalid or has been destroyed.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(acc).c_str());
            }
        } else if (acc_node->buffer_state) {
            for (const auto &mem_binding : acc_node->buffer_state->GetInvalidMemory()) {
                auto set = descriptor_set.Handle();
                return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                          "the descriptor %s is using acceleration structure %s that references invalid memory %s.",
                                          DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(acc).c_str(),
                                          dev_state.FormatHandle(mem_binding->Handle()).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_state.enabled_features.nullDescriptor) {
                auto set = descriptor_set.Handle();
                return dev_state.LogError(
                    vuids.descriptor_buffer_bit_set_08114, set, loc,
                    "the descriptor %s is using acceleration structure %s that is invalid or has been destroyed.",
                    DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(acc).c_str());
            }
        } else {
            for (const auto &mem_binding : acc_node->GetInvalidMemory()) {
                auto set = descriptor_set.Handle();
                return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                          "the descriptor %s is using acceleration structure %s that references invalid memory %s.",
                                          DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(acc).c_str(),
                                          dev_state.FormatHandle(mem_binding->Handle()).c_str());
            }
        }
    }
    return false;
}

// 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 {
    // Verify Sampler still valid
    if (!sampler_state || sampler_state->Destroyed()) {
        auto set = descriptor_set.Handle();
        return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                  "the descriptor %s is using sampler %s that is invalid or has been destroyed.",
                                  DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(sampler).c_str());
    } else {
        if (sampler_state->samplerConversion && !is_immutable) {
            auto set = descriptor_set.Handle();
            return dev_state.LogError(vuids.descriptor_buffer_bit_set_08114, set, loc,
                                      "the descriptor %s sampler (%s) contains a YCBCR conversion (%s), but the sampler is not an "
                                      "immutable sampler.",
                                      DescribeDescriptor(resource_variable, index).c_str(), dev_state.FormatHandle(sampler).c_str(),
                                      dev_state.FormatHandle(sampler_state->samplerConversion).c_str());
        }
    }
    return false;
}

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

}  // namespace vvl