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

#include <vulkan/utility/vk_format_utils.h>
#include "error_message/error_location.h"
#include <vulkan/vk_enum_string_helper.h>
#include "error_message/logging.h"
#include "stateless/stateless_validation.h"
#include "generated/enum_flag_bits.h"
#include "error_message/error_strings.h"
#include "containers/span.h"
#include "containers/container_utils.h"
#include "utils/image_utils.h"
#include "utils/math_utils.h"
#include "utils/descriptor_utils.h"

namespace stateless {
bool Device::ValidateCoarseSampleOrderCustomNV(const VkCoarseSampleOrderCustomNV& order, const Location& order_loc) const {
    bool skip = false;

    struct SampleOrderInfo {
        VkShadingRatePaletteEntryNV shadingRate;
        uint32_t width;
        uint32_t height;
    };

    // All palette entries with more than one pixel per fragment
    constexpr std::array sample_order_infos = {
        SampleOrderInfo{VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_1X2_PIXELS_NV, 1, 2},
        SampleOrderInfo{VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X1_PIXELS_NV, 2, 1},
        SampleOrderInfo{VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X2_PIXELS_NV, 2, 2},
        SampleOrderInfo{VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_4X2_PIXELS_NV, 4, 2},
        SampleOrderInfo{VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X4_PIXELS_NV, 2, 4},
        SampleOrderInfo{VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_4X4_PIXELS_NV, 4, 4},
    };

    const SampleOrderInfo* sample_order_info;
    uint32_t info_idx = 0;
    for (sample_order_info = nullptr; info_idx < sample_order_infos.size(); ++info_idx) {
        if (sample_order_infos[info_idx].shadingRate == order.shadingRate) {
            sample_order_info = &sample_order_infos[info_idx];
            break;
        }
    }

    if (sample_order_info == nullptr) {
        skip |= LogError("VUID-VkCoarseSampleOrderCustomNV-shadingRate-02073", device, order_loc,
                         "shadingRate must be a shading rate "
                         "that generates fragments with more than one pixel.");
        return skip;
    }

    if (order.sampleCount == 0 || (order.sampleCount & (order.sampleCount - 1)) ||
        !(order.sampleCount & phys_dev_props.limits.framebufferNoAttachmentsSampleCounts)) {
        skip |= LogError("VUID-VkCoarseSampleOrderCustomNV-sampleCount-02074", device, order_loc.dot(Field::sampleCount),
                         "(%" PRIu32
                         ") must correspond to a sample count enumerated in VkSampleCountFlags whose corresponding bit "
                         "is set in framebufferNoAttachmentsSampleCounts.",
                         order.sampleCount);
    }

    if (order.sampleLocationCount != order.sampleCount * sample_order_info->width * sample_order_info->height) {
        skip |= LogError("VUID-VkCoarseSampleOrderCustomNV-sampleLocationCount-02075", device,
                         order_loc.dot(Field::sampleLocationCount),
                         "(%" PRIu32 ") must be equal to the product of sampleCount (%" PRIu32
                         "), the fragment width for shadingRate "
                         "(%" PRIu32 "), and the fragment height for shadingRate (%" PRIu32 ").",
                         order.sampleLocationCount, order.sampleCount, sample_order_info->width, sample_order_info->height);
    }

    if (order.sampleLocationCount > phys_dev_ext_props.shading_rate_image_props.shadingRateMaxCoarseSamples) {
        skip |= LogError("VUID-VkCoarseSampleOrderCustomNV-sampleLocationCount-02076", device,
                         order_loc.dot(Field::sampleLocationCount),
                         "(%" PRIu32 ") must be less than or equal to shadingRateMaxCoarseSamples (%" PRIu32 ").",
                         order.sampleLocationCount, phys_dev_ext_props.shading_rate_image_props.shadingRateMaxCoarseSamples);
    }

    // Accumulate a bitmask tracking which (x,y,sample) tuples are seen. Expect
    // the first width*height*sampleCount bits to all be set. Note: There is no
    // guarantee that 64 bits is enough, but practically it's unlikely for an
    // implementation to support more than 32 bits for samplemask.
    assert(phys_dev_ext_props.shading_rate_image_props.shadingRateMaxCoarseSamples <= 64);
    std::bitset<64> sample_locations_mask = 0;
    for (uint32_t i = 0; i < order.sampleLocationCount; ++i) {
        const VkCoarseSampleLocationNV* sample_loc = &order.pSampleLocations[i];
        if (sample_loc->pixelX >= sample_order_info->width) {
            skip |= LogError("VUID-VkCoarseSampleLocationNV-pixelX-02078", device, order_loc,
                             "pixelX (%" PRIu32 ") must be less than the width (in pixels) of the fragment (%" PRIu32 ").",
                             sample_loc->pixelX, sample_order_info->width);
        }
        if (sample_loc->pixelY >= sample_order_info->height) {
            skip |= LogError("VUID-VkCoarseSampleLocationNV-pixelY-02079", device, order_loc,
                             "pixelY (%" PRIu32 ") must be less than the height (in pixels) of the fragment (%" PRIu32 ").",
                             sample_loc->pixelY, sample_order_info->height);
        }
        if (sample_loc->sample >= order.sampleCount) {
            skip |= LogError("VUID-VkCoarseSampleLocationNV-sample-02080", device, order_loc,
                             "sample (%" PRIu32
                             ") must be less than the number of coverage samples in each pixel belonging to the fragment (%" PRIu32
                             ").",
                             sample_loc->sample, order.sampleCount);
        }
        uint32_t idx =
            sample_loc->sample + order.sampleCount * (sample_loc->pixelX + sample_order_info->width * sample_loc->pixelY);
        // Account for idx being greater than or equal to 64 to prevent undefined behavior
        if (idx < sample_locations_mask.size()) {
            sample_locations_mask[idx] = 1;
        }
    }

    std::bitset<64> expected_mask = (order.sampleLocationCount == 64) ? ~0ULL : ((1ULL << order.sampleLocationCount) - 1);
    if (sample_locations_mask != expected_mask) {
        skip |= LogError(
            "VUID-VkCoarseSampleOrderCustomNV-pSampleLocations-02077", device, order_loc,
            "The array pSampleLocations must contain exactly one entry for "
            "every combination of valid values for pixelX, pixelY, and sample in the structure VkCoarseSampleOrderCustomNV.");
    }

    return skip;
}

// VK_EXT_sampler_filter_minmax
bool Device::ValidateSamplerFilterMinMax(const VkSamplerCreateInfo& create_info, const Location& create_info_loc) const {
    bool skip = false;
    const auto* sampler_reduction = vku::FindStructInPNextChain<VkSamplerReductionModeCreateInfo>(create_info.pNext);
    if (!sampler_reduction) return skip;

    if (sampler_reduction->reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE) {
        if ((api_version >= VK_API_VERSION_1_2) && !enabled_features.samplerFilterMinmax) {
            skip |= LogError("VUID-VkSamplerCreateInfo-pNext-06726", device,
                             create_info_loc.pNext(Struct::VkSamplerReductionModeCreateInfo, Field::reductionMode),
                             "is %s but samplerFilterMinmax feature was not enabled.",
                             string_VkSamplerReductionMode(sampler_reduction->reductionMode));
        } else if ((api_version < VK_API_VERSION_1_2) && !IsExtEnabled(extensions.vk_ext_sampler_filter_minmax)) {
            // NOTE: technically this VUID is only if the corresponding _feature_ is not enabled, and only if on api_version
            // >= 1.2, but there doesn't appear to be a similar VUID for when api_version < 1.2
            skip |=
                LogError("VUID-VkSamplerCreateInfo-pNext-06726", device,
                         create_info_loc.pNext(Struct::VkSamplerReductionModeCreateInfo, Field::reductionMode),
                         "is %s, but extension %s is not enabled.", string_VkSamplerReductionMode(sampler_reduction->reductionMode),
                         VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME);
        }

        if (vku::FindStructInPNextChain<VkSamplerYcbcrConversionInfo>(create_info.pNext)) {
            skip |= LogError("VUID-VkSamplerCreateInfo-None-01647", device,
                             create_info_loc.pNext(Struct::VkSamplerReductionModeCreateInfo, Field::reductionMode),
                             "is %s but a VkSamplerYcbcrConversionInfo structure is also linked in the pNext chain of the "
                             "VkSamplerCreateInfo structure.",
                             string_VkSamplerReductionMode(sampler_reduction->reductionMode));
        }

        if (create_info.compareEnable == VK_TRUE) {
            skip |=
                LogError("VUID-VkSamplerCreateInfo-compareEnable-01423", device,
                         create_info_loc.pNext(Struct::VkSamplerReductionModeCreateInfo, Field::reductionMode),
                         "is %s but compareEnable is VK_TRUE.", string_VkSamplerReductionMode(sampler_reduction->reductionMode));
        }

        // This VU is the one feature difference between the IMG and EXT version of the extension
        if (create_info.magFilter == VK_FILTER_CUBIC_IMG || create_info.minFilter == VK_FILTER_CUBIC_IMG) {
            if (!IsExtEnabled(extensions.vk_ext_filter_cubic)) {
                skip |= LogError("VUID-VkSamplerCreateInfo-magFilter-07911", device,
                                 create_info_loc.pNext(Struct::VkSamplerReductionModeCreateInfo, Field::reductionMode),
                                 "is %s, magFilter is %s and minFilter is %s, but "
                                 "extension %s is not enabled.",
                                 string_VkSamplerReductionMode(sampler_reduction->reductionMode),
                                 string_VkFilter(create_info.magFilter), string_VkFilter(create_info.minFilter),
                                 VK_EXT_FILTER_CUBIC_EXTENSION_NAME);
            }
        }
    }

    return skip;
}

// VK_EXT_custom_border_color
bool Device::ValidateSamplerCustomBorderColor(const VkSamplerCreateInfo& create_info, const Location& create_info_loc) const {
    bool skip = false;

    if (create_info.borderColor == VK_BORDER_COLOR_INT_CUSTOM_EXT || create_info.borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT) {
        if (!enabled_features.customBorderColors) {
            skip |=
                LogError("VUID-VkSamplerCreateInfo-customBorderColors-04085", device, create_info_loc.dot(Field::borderColor),
                         "is %s but customBorderColors feature was not enabled.", string_VkBorderColor(create_info.borderColor));
        }

        auto custom_create_info = vku::FindStructInPNextChain<VkSamplerCustomBorderColorCreateInfoEXT>(create_info.pNext);
        if (!custom_create_info) {
            skip |= LogError("VUID-VkSamplerCreateInfo-borderColor-04011", device, create_info_loc.dot(Field::borderColor),
                             "is %s but there is no VkSamplerCustomBorderColorCreateInfoEXT "
                             "struct in pNext chain.\n%s",
                             string_VkBorderColor(create_info.borderColor),
                             PrintPNextChain(Struct::VkSamplerCreateInfo, create_info.pNext).c_str());
        } else {
            if ((custom_create_info->format != VK_FORMAT_UNDEFINED) && !vkuFormatIsDepthAndStencil(custom_create_info->format) &&
                ((create_info.borderColor == VK_BORDER_COLOR_INT_CUSTOM_EXT &&
                  !vkuFormatIsSampledInt(custom_create_info->format)) ||
                 (create_info.borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT &&
                  !vkuFormatIsSampledFloat(custom_create_info->format)))) {
                skip |= LogError("VUID-VkSamplerCustomBorderColorCreateInfoEXT-format-07605", device,
                                 create_info_loc.pNext(Struct::VkSamplerCustomBorderColorCreateInfoEXT, Field::format),
                                 "%s does not match borderColor (%s).", string_VkFormat(custom_create_info->format),
                                 string_VkBorderColor(create_info.borderColor));
            }

            if (custom_create_info->format == VK_FORMAT_UNDEFINED && !enabled_features.customBorderColorWithoutFormat) {
                skip |= LogError("VUID-VkSamplerCustomBorderColorCreateInfoEXT-format-04014", device,
                                 create_info_loc.pNext(Struct::VkSamplerCustomBorderColorCreateInfoEXT, Field::format),
                                 "is VK_FORMAT_UNDEFINED but the "
                                 "customBorderColorWithoutFormat feature was not enabled.");
            }
        }
    }

    if (auto custom_index_create_info =
            vku::FindStructInPNextChain<VkSamplerCustomBorderColorIndexCreateInfoEXT>(create_info.pNext)) {
        if (custom_index_create_info->index >= phys_dev_ext_props.custom_border_color_props.maxCustomBorderColorSamplers) {
            skip |= LogError("VUID-VkSamplerCustomBorderColorIndexCreateInfoEXT-index-11289", device,
                             create_info_loc.pNext(Struct::VkSamplerCustomBorderColorIndexCreateInfoEXT, Field::index),
                             "is %" PRIu32 " but maxCustomBorderColorSamplers is %" PRIu32, custom_index_create_info->index,
                             phys_dev_ext_props.custom_border_color_props.maxCustomBorderColorSamplers);
        }
    }

    return skip;
}

// VK_EXT_fragment_density_map
bool Device::ValidateSamplerSubsampled(const VkSamplerCreateInfo& create_info, const Location& create_info_loc) const {
    bool skip = false;
    if ((create_info.flags & VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT) == 0) return skip;

    if (create_info.minFilter != create_info.magFilter) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-02574", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT, but "
                         "minFilter (%s) and magFilter (%s) are not equal.",
                         string_VkFilter(create_info.minFilter), string_VkFilter(create_info.magFilter));
    }
    if (create_info.mipmapMode != VK_SAMPLER_MIPMAP_MODE_NEAREST) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-02575", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT, but "
                         "mipmapMode (%s) is not VK_SAMPLER_MIPMAP_MODE_NEAREST.",
                         string_VkSamplerMipmapMode(create_info.mipmapMode));
    }
    if (create_info.minLod != 0.0 || create_info.maxLod != 0.0) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-02576", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT, but "
                         "minLod (%f) and maxLod (%f) are not zero.",
                         create_info.minLod, create_info.maxLod);
    }
    if (((create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) &&
         (create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER)) ||
        ((create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) &&
         (create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER))) {
        skip |=
            LogError("VUID-VkSamplerCreateInfo-flags-02577", device, create_info_loc.dot(Field::flags),
                     "includes VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT, so "
                     "addressModeU (%s) and addressModeV (%s) must be "
                     "CLAMP_TO_EDGE or CLAMP_TO_BORDER",
                     string_VkSamplerAddressMode(create_info.addressModeU), string_VkSamplerAddressMode(create_info.addressModeV));
    }
    if (create_info.anisotropyEnable) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-02578", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT, "
                         "but anisotropyEnable is VK_TRUE.");
    }
    if (create_info.compareEnable) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-02579", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT, "
                         "but compareEnable is VK_TRUE.");
    }
    if (create_info.unnormalizedCoordinates) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-02580", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT, "
                         "but unnormalizedCoordinates is VK_TRUE.");
    }

    return skip;
}

// VK_QCOM_image_processing
bool Device::ValidateSamplerImageProcessingQCOM(const VkSamplerCreateInfo& create_info, const Location& create_info_loc) const {
    bool skip = false;
    if ((create_info.flags & VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM) == 0) return skip;

    if ((create_info.minFilter != VK_FILTER_NEAREST) || (create_info.magFilter != VK_FILTER_NEAREST)) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-06964", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM, so "
                         "minFilter (%s) must be VK_FILTER_NEAREST and "
                         "magFilter (%s) must be VK_FILTER_NEAREST.",
                         string_VkFilter(create_info.minFilter), string_VkFilter(create_info.magFilter));
    }
    if (create_info.mipmapMode != VK_SAMPLER_MIPMAP_MODE_NEAREST) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-06965", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM, so "
                         "mipmapMode (%s) must be VK_SAMPLER_MIPMAP_MODE_NEAREST.",
                         string_VkSamplerMipmapMode(create_info.mipmapMode));
    }
    if ((create_info.minLod != 0) || (create_info.maxLod != 0)) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-06966", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM, so "
                         "minLod (%f) and maxLod (%f) must be 0.",
                         create_info.minLod, create_info.maxLod);
    }
    if (((create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) &&
         (create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER)) ||
        ((create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) &&
         (create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER))) {
        skip |=
            LogError("VUID-VkSamplerCreateInfo-flags-06967", device, create_info_loc.dot(Field::flags),
                     "includes VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM, so "
                     "addressModeU (%s) and addressModeV (%s) must be either "
                     "VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE or VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER.",
                     string_VkSamplerAddressMode(create_info.addressModeU), string_VkSamplerAddressMode(create_info.addressModeV));
    }
    if (((create_info.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER) ||
         (create_info.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER)) &&
        (create_info.borderColor != VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK)) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-06968", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM, "
                         "so if either addressModeU (%s) or addressModeV (%s) is "
                         "VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, then "
                         "borderColor (%s) must be VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK.",
                         string_VkSamplerAddressMode(create_info.addressModeU),
                         string_VkSamplerAddressMode(create_info.addressModeV), string_VkBorderColor(create_info.borderColor));
    }
    if (create_info.anisotropyEnable) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-06969", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM, "
                         "but anisotropyEnable is VK_TRUE.");
    }
    if (create_info.compareEnable) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-06970", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_IMAGE_PROCESSING_BIT_QCOM, "
                         "but compareEnable is VK_TRUE.");
    }

    if (enabled_features.textureBlockMatch2) {
        if (const auto* block_match_window_ci = vku::FindStructInPNextChain<VkSamplerBlockMatchWindowCreateInfoQCOM>(create_info.pNext)) {
            if (block_match_window_ci->windowExtent.width > phys_dev_ext_props.image_processing2_props.maxBlockMatchWindow.width ||
                block_match_window_ci->windowExtent.height > phys_dev_ext_props.image_processing2_props.maxBlockMatchWindow.height) {
                skip |= LogError("VUID-VkSamplerBlockMatchWindowCreateInfoQCOM-WindowExtent-09210",
                                 device,
                                 create_info_loc.pNext(Struct::VkSamplerBlockMatchWindowCreateInfoQCOM, Field::windowExtent),
                                 "(%s) is larger than VkPhysicalDeviceImageProcessing2PropertiesQCOM::maxBlockMatchWindow (%s).",
                                 string_VkExtent2D(block_match_window_ci->windowExtent).c_str(),
                                 string_VkExtent2D(phys_dev_ext_props.image_processing2_props.maxBlockMatchWindow).c_str());
            }
        }
    }

    return skip;
}

bool Device::ValidateSamplerCreateInfo(const VkSamplerCreateInfo& create_info, const Location& create_info_loc,
                                       const Context& context) const {
    bool skip = false;

    if (create_info.anisotropyEnable == VK_TRUE) {
        if (!IsBetweenInclusive(create_info.maxAnisotropy, 1.0F, phys_dev_props.limits.maxSamplerAnisotropy)) {
            skip |= LogError("VUID-VkSamplerCreateInfo-anisotropyEnable-01071", device, create_info_loc.dot(Field::maxAnisotropy),
                             "is %f but must be in the range of [1.0, %f] (maxSamplerAnistropy).", create_info.maxAnisotropy,
                             phys_dev_props.limits.maxSamplerAnisotropy);
        }

        // Anistropy cannot be enabled in sampler unless enabled as a feature
        if (enabled_features.samplerAnisotropy == VK_FALSE) {
            skip |=
                LogError("VUID-VkSamplerCreateInfo-anisotropyEnable-01070", device, create_info_loc.dot(Field::anisotropyEnable),
                         "is VK_TRUE but the samplerAnisotropy feature was not enabled.");
        }
    }

    if (create_info.unnormalizedCoordinates == VK_TRUE) {
        if (create_info.minFilter != create_info.magFilter) {
            skip |= LogError("VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01072", device,
                             create_info_loc.dot(Field::unnormalizedCoordinates),
                             "is VK_TRUE, but minFilter (%s) is different then magFilter (%s).",
                             string_VkFilter(create_info.minFilter), string_VkFilter(create_info.magFilter));
        }
        if (create_info.mipmapMode != VK_SAMPLER_MIPMAP_MODE_NEAREST) {
            skip |= LogError("VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01073", device,
                             create_info_loc.dot(Field::unnormalizedCoordinates),
                             "is VK_TRUE, but mipmapMode (%s) must be VK_SAMPLER_MIPMAP_MODE_NEAREST.",
                             string_VkSamplerMipmapMode(create_info.mipmapMode));
        }
        if (create_info.minLod != 0.0f || create_info.maxLod != 0.0f) {
            skip |=
                LogError("VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01074", device,
                         create_info_loc.dot(Field::unnormalizedCoordinates),
                         "is VK_TRUE, but minLod (%f) and maxLod (%f) must both be zero.", create_info.minLod, create_info.maxLod);
        }
        if ((create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE &&
             create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER) ||
            (create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE &&
             create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER)) {
            skip |= LogError("VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01075", device,
                             create_info_loc.dot(Field::unnormalizedCoordinates),
                             "is VK_TRUE, but addressModeU (%s) and addressModeV (%s) must both be "
                             "CLAMP_TO_EDGE or CLAMP_TO_BORDER.",
                             string_VkSamplerAddressMode(create_info.addressModeU),
                             string_VkSamplerAddressMode(create_info.addressModeV));
        }
        if (create_info.anisotropyEnable == VK_TRUE) {
            skip |= LogError("VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01076", device, create_info_loc,
                             "anisotropyEnable and unnormalizedCoordinates are both VK_TRUE.");
        }
        if (create_info.compareEnable == VK_TRUE) {
            skip |= LogError("VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01077", device, create_info_loc,
                             "compareEnable and unnormalizedCoordinates are both VK_TRUE.");
        }
    }

    if (create_info.compareEnable == VK_TRUE) {
        skip |= context.ValidateRangedEnum(create_info_loc.dot(Field::compareOp), vvl::Enum::VkCompareOp, create_info.compareOp,
                                           "VUID-VkSamplerCreateInfo-compareEnable-01080");
    }

    if ((create_info.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER) ||
        (create_info.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER) ||
        (create_info.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER)) {
        skip |= context.ValidateRangedEnum(create_info_loc.dot(Field::borderColor), vvl::Enum::VkBorderColor,
                                           create_info.borderColor, "VUID-VkSamplerCreateInfo-addressModeU-01078");
    }

    if (enabled_features.samplerMirrorClampToEdge == VK_FALSE) {
        // Only display at most one of address mode error otherwise it turns into spam
        if (create_info.addressModeU == VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE) {
            skip |= LogError("VUID-VkSamplerCreateInfo-addressModeU-01079", device, create_info_loc.dot(Field::addressModeU),
                             "is VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE but the "
                             "VK_KHR_sampler_mirror_clamp_to_edge extension or samplerMirrorClampToEdge feature was not enabled.");
        } else if (create_info.addressModeV == VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE) {
            skip |= LogError("VUID-VkSamplerCreateInfo-addressModeU-01079", device, create_info_loc.dot(Field::addressModeV),
                             "is VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE but the "
                             "VK_KHR_sampler_mirror_clamp_to_edge extension or samplerMirrorClampToEdge feature was not enabled.");
        } else if (create_info.addressModeW == VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE) {
            skip |= LogError("VUID-VkSamplerCreateInfo-addressModeU-01079", device, create_info_loc.dot(Field::addressModeW),
                             "is VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE but the "
                             "VK_KHR_sampler_mirror_clamp_to_edge extension or samplerMirrorClampToEdge feature was not enabled.");
        }
    }

    // Checks for the IMG cubic filtering extension
    if (IsExtEnabled(extensions.vk_img_filter_cubic)) {
        if ((create_info.anisotropyEnable == VK_TRUE) &&
            ((create_info.minFilter == VK_FILTER_CUBIC_IMG) || (create_info.magFilter == VK_FILTER_CUBIC_IMG))) {
            skip |= LogError("VUID-VkSamplerCreateInfo-magFilter-01081", device, create_info_loc.dot(Field::anisotropyEnable),
                             "is VK_TRUE, but minFilter (%s) and magFilter (%s) can't be cubic.",
                             string_VkFilter(create_info.minFilter), string_VkFilter(create_info.magFilter));
        }
    }

    // Check for valid Lod range
    if (create_info.minLod > create_info.maxLod) {
        skip |= LogError("VUID-VkSamplerCreateInfo-maxLod-01973", device, create_info_loc.dot(Field::minLod),
                         "(%f) is greater than maxLod (%f)", create_info.minLod, create_info.maxLod);
    }

    // Check mipLodBias to device limit
    if (create_info.mipLodBias > phys_dev_props.limits.maxSamplerLodBias) {
        skip |= LogError("VUID-VkSamplerCreateInfo-mipLodBias-01069", device, create_info_loc.dot(Field::mipLodBias),
                         "(%f) is greater than maxSamplerLodBias (%f)", create_info.mipLodBias,
                         phys_dev_props.limits.maxSamplerLodBias);
    }

    if (vku::FindStructInPNextChain<VkSamplerYcbcrConversionInfo>(create_info.pNext)) {
        if ((create_info.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) ||
            (create_info.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) ||
            (create_info.addressModeW != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)) {
            skip |= LogError("VUID-VkSamplerCreateInfo-addressModeU-01646", device, create_info_loc.dot(Field::pNext),
                             "contains a VkSamplerYcbcrConversionInfo struct, but all address modes must be "
                             "VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE:\n"
                             "addressModeU (%s)\naddressModeV (%s)\naddressModeW (%s)\n",
                             string_VkSamplerAddressMode(create_info.addressModeU),
                             string_VkSamplerAddressMode(create_info.addressModeV),
                             string_VkSamplerAddressMode(create_info.addressModeW));
        } else if (create_info.anisotropyEnable) {
            skip |= LogError("VUID-VkSamplerCreateInfo-addressModeU-01646", device, create_info_loc.dot(Field::pNext),
                             "contains a VkSamplerYcbcrConversionInfo struct, but anisotropyEnable must be VK_FALSE.");
        } else if (create_info.unnormalizedCoordinates) {
            skip |= LogError("VUID-VkSamplerCreateInfo-addressModeU-01646", device, create_info_loc.dot(Field::pNext),
                             "contains a VkSamplerYcbcrConversionInfo struct, but unnormalizedCoordinates must be VK_FALSE.");
        }
    }

    if (vku::FindStructInPNextChain<VkSamplerBorderColorComponentMappingCreateInfoEXT>(create_info.pNext)) {
        if (!enabled_features.borderColorSwizzle) {
            skip |=
                LogError("VUID-VkSamplerBorderColorComponentMappingCreateInfoEXT-borderColorSwizzle-06437", device, create_info_loc,
                         "The borderColorSwizzle feature must be enabled to use "
                         "VkPhysicalDeviceBorderColorSwizzleFeaturesEXT");
        }
    }

    if ((create_info.flags & VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT) && (!enabled_features.nonSeamlessCubeMap)) {
        skip |= LogError("VUID-VkSamplerCreateInfo-nonSeamlessCubeMap-06788", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT but the "
                         "nonSeamlessCubeMap feature was not enabled.");
    }

    if ((create_info.flags & VK_SAMPLER_CREATE_DESCRIPTOR_BUFFER_CAPTURE_REPLAY_BIT_EXT) &&
        !enabled_features.descriptorBufferCaptureReplay) {
        skip |= LogError("VUID-VkSamplerCreateInfo-flags-08110", device, create_info_loc.dot(Field::flags),
                         "includes VK_SAMPLER_CREATE_DESCRIPTOR_BUFFER_CAPTURE_REPLAY_BIT_EXT but descriptorBufferCaptureReplay "
                         "feature was not enabled.");
    }

    if (vku::FindStructInPNextChain<VkOpaqueCaptureDescriptorDataCreateInfoEXT>(create_info.pNext)) {
        if (!(create_info.flags & VK_SAMPLER_CREATE_DESCRIPTOR_BUFFER_CAPTURE_REPLAY_BIT_EXT)) {
            skip |= LogError("VUID-VkSamplerCreateInfo-pNext-08111", device, create_info_loc.dot(Field::flags),
                             "is %s but VkOpaqueCaptureDescriptorDataCreateInfoEXT is in pNext chain.\n%s",
                             string_VkSamplerCreateFlags(create_info.flags).c_str(),
                             PrintPNextChain(Struct::VkSamplerCreateInfo, create_info.pNext).c_str());
        }
    }

    skip |= ValidateSamplerFilterMinMax(create_info, create_info_loc);
    skip |= ValidateSamplerCustomBorderColor(create_info, create_info_loc);
    skip |= ValidateSamplerSubsampled(create_info, create_info_loc);
    skip |= ValidateSamplerImageProcessingQCOM(create_info, create_info_loc);

    return skip;
}

bool Device::manual_PreCallValidateCreateSampler(VkDevice device, const VkSamplerCreateInfo* pCreateInfo,
                                                 const VkAllocationCallbacks* pAllocator, VkSampler* pSampler,
                                                 const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;
    const Location create_info_loc = error_obj.location.dot(Field::pCreateInfo);
    skip |= ValidateSamplerCreateInfo(*pCreateInfo, create_info_loc, context);
    return skip;
}

bool Device::ValidateMutableDescriptorTypeCreateInfo(const VkDescriptorSetLayoutCreateInfo& create_info,
                                                     const VkMutableDescriptorTypeCreateInfoEXT& mutable_create_info,
                                                     const Location& create_info_loc) const {
    bool skip = false;

    for (uint32_t i = 0; i < create_info.bindingCount; ++i) {
        const Location binding_loc = create_info_loc.dot(Field::pBindings, i);
        uint32_t mutable_type_count = 0;
        if (mutable_create_info.mutableDescriptorTypeListCount > i) {
            mutable_type_count = mutable_create_info.pMutableDescriptorTypeLists[i].descriptorTypeCount;
        }
        if (create_info.pBindings[i].descriptorType == VK_DESCRIPTOR_TYPE_MUTABLE_EXT) {
            if (mutable_type_count == 0) {
                skip |= LogError(
                    "VUID-VkMutableDescriptorTypeListEXT-descriptorTypeCount-04597", device, binding_loc.dot(Field::descriptorType),
                    "is VK_DESCRIPTOR_TYPE_MUTABLE_EXT, but "
                    "VkMutableDescriptorTypeCreateInfoEXT::pMutableDescriptorTypeLists[%" PRIu32 "].descriptorTypeCount is 0.",
                    i);
            }
        } else {
            if (mutable_type_count > 0) {
                skip |= LogError(
                    "VUID-VkMutableDescriptorTypeListEXT-descriptorTypeCount-04599", device, binding_loc.dot(Field::descriptorType),
                    "is %s, but "
                    "VkMutableDescriptorTypeCreateInfoEXT::pMutableDescriptorTypeLists[%" PRIu32 "].descriptorTypeCount is not 0.",
                    string_VkDescriptorType(create_info.pBindings[i].descriptorType), i);
            }
        }
    }

    for (uint32_t j = 0; j < mutable_create_info.mutableDescriptorTypeListCount; ++j) {
        const Location mutable_loc =
            create_info_loc.pNext(Struct::VkMutableDescriptorTypeCreateInfoEXT, Field::pMutableDescriptorTypeLists, j);
        for (uint32_t k = 0; k < mutable_create_info.pMutableDescriptorTypeLists[j].descriptorTypeCount; ++k) {
            const Location type_loc = mutable_loc.dot(Field::pDescriptorTypes, k);
            switch (mutable_create_info.pMutableDescriptorTypeLists[j].pDescriptorTypes[k]) {
                case VK_DESCRIPTOR_TYPE_MUTABLE_EXT:
                    skip |= LogError("VUID-VkMutableDescriptorTypeListEXT-pDescriptorTypes-04600", device, type_loc,
                                     "is VK_DESCRIPTOR_TYPE_MUTABLE_EXT.");
                    break;
                case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
                    skip |= LogError("VUID-VkMutableDescriptorTypeListEXT-pDescriptorTypes-04601", device, type_loc,
                                     "is VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC.");
                    break;
                case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
                    skip |= LogError("VUID-VkMutableDescriptorTypeListEXT-pDescriptorTypes-04602", device, type_loc,
                                     "is VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC.");
                    break;
                case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK:
                    skip |= LogError("VUID-VkMutableDescriptorTypeListEXT-pDescriptorTypes-04603", device, type_loc,
                                     "is VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK.");
                    break;
                case VK_DESCRIPTOR_TYPE_TENSOR_ARM:
                    skip |= LogError("VUID-VkMutableDescriptorTypeListEXT-pDescriptorTypes-09696", device, type_loc,
                                     "is VK_DESCRIPTOR_TYPE_TENSOR_ARM.");
                    break;
                default:
                    break;
            }
            for (uint32_t l = k + 1; l < mutable_create_info.pMutableDescriptorTypeLists[j].descriptorTypeCount; ++l) {
                if (mutable_create_info.pMutableDescriptorTypeLists[j].pDescriptorTypes[k] ==
                    mutable_create_info.pMutableDescriptorTypeLists[j].pDescriptorTypes[l]) {
                    skip |=
                        LogError("VUID-VkMutableDescriptorTypeListEXT-pDescriptorTypes-04598", device, type_loc,
                                 "and pDescriptorTypes[%" PRIu32 "] are both %s.", l,
                                 string_VkDescriptorType(mutable_create_info.pMutableDescriptorTypeLists[j].pDescriptorTypes[k]));
                }
            }
        }
    }

    return skip;
}

bool Device::ValidateDescriptorSetLayoutCreateInfo(const VkDescriptorSetLayoutCreateInfo& create_info,
                                                   const Location& create_info_loc) const {
    bool skip = false;

    const bool has_descriptor_buffer_flag = (create_info.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT) != 0;
    const bool has_push_descriptor_flag = (create_info.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT) != 0;
    // Validation for parameters excluded from the generated validation code due to a 'noautovalidity' tag in vk.xml
    if (create_info.pBindings != nullptr) {
        const auto* mutable_descriptor_type = vku::FindStructInPNextChain<VkMutableDescriptorTypeCreateInfoEXT>(create_info.pNext);
        for (const auto [i, binding] : vvl::enumerate(create_info.pBindings, create_info.bindingCount)) {
            if (binding.descriptorCount == 0) {
                continue;
            }

            const Location binding_loc = create_info_loc.dot(Field::pBindings, i);
            VkShaderStageFlags stage_flags = binding.stageFlags;
            if (stage_flags != 0) {
                if (stage_flags != VK_SHADER_STAGE_ALL && ((stage_flags & (~AllVkShaderStageFlagBitsExcludingStageAll)) != 0)) {
                    skip |= LogError(
                        "VUID-VkDescriptorSetLayoutBinding-descriptorCount-09465", device, binding_loc.dot(Field::descriptorCount),
                        "is %" PRIu32 " but stageFlags is invalid (0x%" PRIx32 ").", binding.descriptorCount, stage_flags);
                }

                if ((binding.descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT) &&
                    (stage_flags != VK_SHADER_STAGE_FRAGMENT_BIT)) {
                    skip |= LogError("VUID-VkDescriptorSetLayoutBinding-descriptorType-01510", device,
                                     binding_loc.dot(Field::stageFlags),
                                     "is %s but descriptorType is VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT.",
                                     string_VkShaderStageFlags(stage_flags).c_str());
                }
            }

            if (binding.descriptorType == VK_DESCRIPTOR_TYPE_MUTABLE_EXT) {
                if (mutable_descriptor_type) {
                    if (i >= mutable_descriptor_type->mutableDescriptorTypeListCount) {
                        skip |= LogError(
                            "VUID-VkDescriptorSetLayoutCreateInfo-pBindings-07303", device,
                            binding_loc.pNext(Struct::VkMutableDescriptorTypeCreateInfoEXT, Field::mutableDescriptorTypeListCount),
                            "(%" PRIu32 ") is less than or equal to %" PRIu32 ", but pBindings[%" PRIu32
                            " ].descriptorType is VK_DESCRIPTOR_TYPE_MUTABLE_EXT",
                            mutable_descriptor_type->mutableDescriptorTypeListCount, i, i);
                    }
                } else {
                    skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-pBindings-07303", device,
                                     binding_loc.dot(Field::descriptorType),
                                     "is VK_DESCRIPTOR_TYPE_MUTABLE_EXT but VkMutableDescriptorTypeCreateInfoEXT is not "
                                     "included in the pNext chain.\n%s",
                                     PrintPNextChain(Struct::VkDescriptorSetLayoutCreateInfo, create_info.pNext).c_str());
                }
                if (binding.pImmutableSamplers) {
                    skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-descriptorType-04594", device,
                                     binding_loc.dot(Field::descriptorType),
                                     "is VK_DESCRIPTOR_TYPE_MUTABLE_EXT but pImmutableSamplers is not NULL.");
                }
                if (!enabled_features.mutableDescriptorType) {
                    skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-mutableDescriptorType-04595", device,
                                     binding_loc.dot(Field::descriptorType),
                                     "is VK_DESCRIPTOR_TYPE_MUTABLE_EXT but "
                                     "mutableDescriptorType feature was not enabled.");
                }
            }

            if (has_push_descriptor_flag && binding.descriptorType == VK_DESCRIPTOR_TYPE_MUTABLE_EXT) {
                skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-flags-04591", device, binding_loc.dot(Field::descriptorType),
                                 "is VK_DESCRIPTOR_TYPE_MUTABLE_EXT, but flags includes "
                                 "VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT.");
            }

            if (has_descriptor_buffer_flag && ((binding.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) ||
                                               (binding.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC))) {
                skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-flags-08000", device, binding_loc.dot(Field::descriptorType),
                                 "is %s, but flags includes VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT.",
                                 string_VkDescriptorType(binding.descriptorType));
            }
        }

        if (mutable_descriptor_type) {
            skip |= ValidateMutableDescriptorTypeCreateInfo(create_info, *mutable_descriptor_type, create_info_loc);
        }
    }

    const bool has_host_only_pool_flag = (create_info.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_HOST_ONLY_POOL_BIT_EXT) != 0;
    const bool has_update_after_bind_flag = (create_info.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT) != 0;
    const bool has_embedded_immutable_sampler_flag =
        (create_info.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_EMBEDDED_IMMUTABLE_SAMPLERS_BIT_EXT) != 0;

    if (has_push_descriptor_flag && has_host_only_pool_flag) {
        skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-flags-04590", device, create_info_loc.dot(Field::flags), "is %s.",
                         string_VkDescriptorSetLayoutCreateFlags(create_info.flags).c_str());
    }
    if (has_update_after_bind_flag && has_host_only_pool_flag) {
        skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-flags-04592", device, create_info_loc.dot(Field::flags), "is %s.",
                         string_VkDescriptorSetLayoutCreateFlags(create_info.flags).c_str());
    }
    if (has_host_only_pool_flag && !enabled_features.mutableDescriptorType) {
        skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-flags-04596", device, create_info_loc.dot(Field::flags),
                         "is %s, but mutableDescriptorType feature was not enabled.",
                         string_VkDescriptorSetLayoutCreateFlags(create_info.flags).c_str());
    }

    if (has_embedded_immutable_sampler_flag && !has_descriptor_buffer_flag) {
        skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-flags-08001", device, create_info_loc.dot(Field::flags), "is %s.",
                         string_VkDescriptorSetLayoutCreateFlags(create_info.flags).c_str());
    }

    if (has_descriptor_buffer_flag && has_update_after_bind_flag) {
        skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-flags-08002", device, create_info_loc.dot(Field::flags), "is %s.",
                         string_VkDescriptorSetLayoutCreateFlags(create_info.flags).c_str());
    }

    if (has_descriptor_buffer_flag && has_host_only_pool_flag) {
        skip |= LogError("VUID-VkDescriptorSetLayoutCreateInfo-flags-08003", device, create_info_loc.dot(Field::flags), "is %s.",
                         string_VkDescriptorSetLayoutCreateFlags(create_info.flags).c_str());
    }

    return skip;
}

bool Device::manual_PreCallValidateRegisterCustomBorderColorEXT(VkDevice device,
                                                                const VkSamplerCustomBorderColorCreateInfoEXT* pBorderColor,
                                                                VkBool32 requestIndex, uint32_t* pIndex,
                                                                const Context& context) const {
    bool skip = false;

    if (requestIndex == VK_TRUE && pIndex && *pIndex >= phys_dev_ext_props.custom_border_color_props.maxCustomBorderColorSamplers) {
        skip |= LogError("VUID-vkRegisterCustomBorderColorEXT-requestIndex-11287", device,
                         context.error_obj.location.dot(Field::pIndex),
                         "(%" PRIu32 ") is greater than or equal to maxCustomBorderColorSamplers (%" PRIu32 ").", *pIndex,
                         phys_dev_ext_props.custom_border_color_props.maxCustomBorderColorSamplers);
    }

    return skip;
}

bool Device::manual_PreCallValidateUnregisterCustomBorderColorEXT(VkDevice device, uint32_t index, const Context& context) const {
    bool skip = false;

    if (index >= phys_dev_ext_props.custom_border_color_props.maxCustomBorderColorSamplers) {
        skip |= LogError("VUID-vkUnregisterCustomBorderColorEXT-index-11288", device, context.error_obj.location.dot(Field::index),
                         "(%" PRIu32 ") is greater than or equal to maxCustomBorderColorSamplers (%" PRIu32 ").", index,
                         phys_dev_ext_props.custom_border_color_props.maxCustomBorderColorSamplers);
    }

    return skip;
}

bool Instance::manual_PreCallValidateGetPhysicalDeviceDescriptorSizeEXT(VkPhysicalDevice physicalDevice,
                                                                        VkDescriptorType descriptorType,
                                                                        const Context& context) const {
    bool skip = false;

    if (!IsValueIn(descriptorType,
                   {VK_DESCRIPTOR_TYPE_SAMPLER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
                    VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
                    VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM,
                    VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
                    VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV, VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV,
                    VK_DESCRIPTOR_TYPE_TENSOR_ARM, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT})) {
        skip |= LogError("VUID-vkGetPhysicalDeviceDescriptorSizeEXT-type-11362", physicalDevice,
                         context.error_obj.location.dot(Field::descriptorType), "(%s) is not in the allowed list.",
                         string_VkDescriptorType(descriptorType));
    }

    return skip;
}

bool Device::manual_PreCallValidateCreateDescriptorSetLayout(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
                                                             const VkAllocationCallbacks* pAllocator,
                                                             VkDescriptorSetLayout* pSetLayout, const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;
    skip |= ValidateDescriptorSetLayoutCreateInfo(*pCreateInfo, error_obj.location.dot(Field::pCreateInfo));
    return skip;
}

bool Device::manual_PreCallValidateGetDescriptorSetLayoutSupport(VkDevice device,
                                                                 const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
                                                                 VkDescriptorSetLayoutSupport* pSupport,
                                                                 const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;
    skip |= ValidateDescriptorSetLayoutCreateInfo(*pCreateInfo, error_obj.location.dot(Field::pCreateInfo));
    return skip;
}

bool Device::manual_PreCallValidateFreeDescriptorSets(VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount,
                                                      const VkDescriptorSet* pDescriptorSets, const Context& context) const {
    const auto& error_obj = context.error_obj;
    // Validation for parameters excluded from the generated validation code due to a 'noautovalidity' tag in vk.xml
    // This is an array of handles, where the elements are allowed to be VK_NULL_HANDLE, and does not require any validation beyond
    // ValidateArray()
    return context.ValidateArray(error_obj.location.dot(Field::descriptorSetCount), error_obj.location.dot(Field::pDescriptorSets),
                                 descriptorSetCount, &pDescriptorSets, true, true, kVUIDUndefined,
                                 "VUID-vkFreeDescriptorSets-pDescriptorSets-00310");
}

bool Device::ValidateWriteDescriptorSet(const Context& context, const Location& loc, const uint32_t descriptorWriteCount,
                                        const VkWriteDescriptorSet* pDescriptorWrites) const {
    bool skip = false;
    if (!pDescriptorWrites) {
        return skip;
    }
    const bool is_push_descriptor = IsValueIn(loc.function, {Func::vkCmdPushDescriptorSet, Func::vkCmdPushDescriptorSet2,
                                                             Func::vkCmdPushDescriptorSetKHR, Func::vkCmdPushDescriptorSet2KHR});

    for (uint32_t i = 0; i < descriptorWriteCount; ++i) {
        const Location writes_loc = loc.dot(Field::pDescriptorWrites, i);
        const auto& descriptor_writes = pDescriptorWrites[i];

        // If called from vkCmdPushDescriptorSetKHR, the dstSet member is ignored.
        if (!is_push_descriptor) {
            // dstSet must be a valid VkDescriptorSet handle
            skip |= context.ValidateRequiredHandle(writes_loc.dot(Field::dstSet), descriptor_writes.dstSet);
        }

        const VkDescriptorType descriptor_type = descriptor_writes.descriptorType;
        if (IsValueIn(descriptor_type,
                      {VK_DESCRIPTOR_TYPE_SAMPLER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                       VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT})) {
            if (descriptor_writes.pImageInfo != nullptr && descriptor_type != VK_DESCRIPTOR_TYPE_SAMPLER) {
                for (uint32_t descriptor_index = 0; descriptor_index < descriptor_writes.descriptorCount; ++descriptor_index) {
                    skip |= context.ValidateRangedEnum(writes_loc.dot(Field::pImageInfo, descriptor_index).dot(Field::imageLayout),
                                                       vvl::Enum::VkImageLayout,
                                                       descriptor_writes.pImageInfo[descriptor_index].imageLayout, kVUIDUndefined);
                }
            }
        }
        const auto* tensor_struct = vku::FindStructInPNextChain<VkWriteDescriptorSetTensorARM>(descriptor_writes.pNext);
        if (tensor_struct) {
            for (uint32_t j = 0; j < tensor_struct->tensorViewCount; ++j) {
                if (!enabled_features.nullDescriptor && tensor_struct->pTensorViews[j] == VK_NULL_HANDLE) {
                    skip |= LogError("VUID-VkWriteDescriptorSetTensorARM-nullDescriptor-09898", device,
                                     writes_loc.pNext(Struct::VkWriteDescriptorSetTensorARM, Field::pTensorViews, j),
                                     "cannot be VK_NULL_HANDLE.");
                }
            }
        }
    }
    return skip;
}

bool Device::manual_PreCallValidateUpdateDescriptorSets(VkDevice device, uint32_t descriptorWriteCount,
                                                        const VkWriteDescriptorSet* pDescriptorWrites, uint32_t descriptorCopyCount,
                                                        const VkCopyDescriptorSet* pDescriptorCopies,
                                                        const Context& context) const {
    const auto& error_obj = context.error_obj;
    return ValidateWriteDescriptorSet(context, error_obj.location, descriptorWriteCount, pDescriptorWrites);
}

static bool MutableDescriptorTypePartialOverlap(const VkDescriptorPoolCreateInfo* pCreateInfo, uint32_t i, uint32_t j) {
    bool partial_overlap = false;

    constexpr std::array all_descriptor_types = {
        VK_DESCRIPTOR_TYPE_SAMPLER,
        VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
        VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
        VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
        VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
        VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
        VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
        VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC,
        VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
        VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK,
        VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
        VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV,
    };

    const auto* mutable_descriptor_type = vku::FindStructInPNextChain<VkMutableDescriptorTypeCreateInfoEXT>(pCreateInfo->pNext);
    if (mutable_descriptor_type) {
        vvl::span<const VkDescriptorType> first_types, second_types;

        if (mutable_descriptor_type->mutableDescriptorTypeListCount > i) {
            const uint32_t descriptor_type_count = mutable_descriptor_type->pMutableDescriptorTypeLists[i].descriptorTypeCount;
            auto* descriptor_types = mutable_descriptor_type->pMutableDescriptorTypeLists[i].pDescriptorTypes;
            first_types = vvl::make_span(descriptor_types, descriptor_type_count);
        } else {
            first_types = vvl::make_span(all_descriptor_types.data(), all_descriptor_types.size());
        }

        if (mutable_descriptor_type->mutableDescriptorTypeListCount > j) {
            const uint32_t descriptor_type_count = mutable_descriptor_type->pMutableDescriptorTypeLists[j].descriptorTypeCount;
            auto* descriptor_types = mutable_descriptor_type->pMutableDescriptorTypeLists[j].pDescriptorTypes;
            second_types = vvl::make_span(descriptor_types, descriptor_type_count);
        } else {
            second_types = vvl::make_span(all_descriptor_types.data(), all_descriptor_types.size());
        }

        bool complete_overlap = first_types.size() == second_types.size();
        bool disjoint = true;
        for (const auto first_type : first_types) {
            bool found = false;
            for (const auto second_type : second_types) {
                if (first_type == second_type) {
                    found = true;
                    break;
                }
            }
            if (found) {
                disjoint = false;
            } else {
                complete_overlap = false;
            }
            if (!disjoint && !complete_overlap) {
                partial_overlap = true;
                break;
            }
        }
    }

    return partial_overlap;
}

bool Device::manual_PreCallValidateCreateDescriptorPool(VkDevice device, const VkDescriptorPoolCreateInfo* pCreateInfo,
                                                        const VkAllocationCallbacks* pAllocator, VkDescriptorPool* pDescriptorPool,
                                                        const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    const Location create_info_loc = error_obj.location.dot(Field::pCreateInfo);
    if (pCreateInfo->maxSets == 0 && ((pCreateInfo->flags & VK_DESCRIPTOR_POOL_CREATE_ALLOW_OVERALLOCATION_SETS_BIT_NV) == 0)) {
        skip |= LogError("VUID-VkDescriptorPoolCreateInfo-descriptorPoolOverallocation-09227", device,
                         create_info_loc.dot(Field::maxSets), "is zero.");
    }

    const auto* inline_uniform_info = vku::FindStructInPNextChain<VkDescriptorPoolInlineUniformBlockCreateInfo>(pCreateInfo->pNext);
    const bool non_zero_inline_uniform_count = inline_uniform_info && inline_uniform_info->maxInlineUniformBlockBindings != 0;

    if (pCreateInfo->pPoolSizes) {
        for (uint32_t i = 0; i < pCreateInfo->poolSizeCount; ++i) {
            const Location pool_loc = create_info_loc.dot(Field::pPoolSizes, i);
            if (pCreateInfo->pPoolSizes[i].descriptorCount <= 0) {
                skip |= LogError("VUID-VkDescriptorPoolSize-descriptorCount-00302", device, pool_loc.dot(Field::descriptorCount),
                                 "is zero.");
            }
            if (pCreateInfo->pPoolSizes[i].type == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK) {
                if (!IsIntegerMultipleOf(pCreateInfo->pPoolSizes[i].descriptorCount, 4)) {
                    skip |= LogError("VUID-VkDescriptorPoolSize-type-02218", device, pool_loc.dot(Field::descriptorCount),
                                     "is %" PRIu32 " (not a multiple of 4), but type is VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK.",
                                     pCreateInfo->pPoolSizes[i].descriptorCount);
                }
                if (!non_zero_inline_uniform_count) {
                    skip |=
                        LogError("VUID-VkDescriptorPoolCreateInfo-pPoolSizes-09424", device, pool_loc.dot(Field::type),
                                 "is VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK but no maxInlineUniformBlockBindings was provided.");
                }
            }
            if (pCreateInfo->pPoolSizes[i].type == VK_DESCRIPTOR_TYPE_MUTABLE_EXT && !enabled_features.mutableDescriptorType) {
                skip |= LogError("VUID-VkDescriptorPoolCreateInfo-mutableDescriptorType-04608", device, pool_loc.dot(Field::type),
                                 "is VK_DESCRIPTOR_TYPE_MUTABLE_EXT "
                                 ", but mutableDescriptorType feature was not enabled.");
            }
            if (pCreateInfo->pPoolSizes[i].type == VK_DESCRIPTOR_TYPE_MUTABLE_EXT) {
                for (uint32_t j = i + 1; j < pCreateInfo->poolSizeCount; ++j) {
                    if (pCreateInfo->pPoolSizes[j].type == VK_DESCRIPTOR_TYPE_MUTABLE_EXT) {
                        if (MutableDescriptorTypePartialOverlap(pCreateInfo, i, j)) {
                            skip |= LogError("VUID-VkDescriptorPoolCreateInfo-pPoolSizes-04787", device, pool_loc.dot(Field::type),
                                             "and pPoolSizes[%" PRIu32
                                             "].type are both VK_DESCRIPTOR_TYPE_MUTABLE_EXT "
                                             " and have sets which partially overlap.",
                                             j);
                        }
                    }
                }
            }
        }
    }

    if (pCreateInfo->flags & VK_DESCRIPTOR_POOL_CREATE_HOST_ONLY_BIT_EXT && !enabled_features.mutableDescriptorType) {
        skip |= LogError("VUID-VkDescriptorPoolCreateInfo-flags-04609", device, create_info_loc.dot(Field::flags),
                         "includes VK_DESCRIPTOR_POOL_CREATE_HOST_ONLY_BIT_EXT, "
                         "but mutableDescriptorType feature was not enabled.");
    }
    if ((pCreateInfo->flags & VK_DESCRIPTOR_POOL_CREATE_HOST_ONLY_BIT_EXT) &&
        (pCreateInfo->flags & VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT)) {
        skip |= LogError("VUID-VkDescriptorPoolCreateInfo-flags-04607", device, create_info_loc.dot(Field::flags),
                         "includes both "
                         "VK_DESCRIPTOR_POOL_CREATE_HOST_ONLY_BIT_EXT and VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT");
    }

    return skip;
}

bool Device::manual_PreCallValidateCreateQueryPool(VkDevice device, const VkQueryPoolCreateInfo* pCreateInfo,
                                                   const VkAllocationCallbacks* pAllocator, VkQueryPool* pQueryPool,
                                                   const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;
    const Location create_info_loc = error_obj.location.dot(Field::pCreateInfo);

    switch (pCreateInfo->queryType) {
        case VK_QUERY_TYPE_PIPELINE_STATISTICS: {
            if (!enabled_features.pipelineStatisticsQuery) {
                skip |= LogError("VUID-VkQueryPoolCreateInfo-queryType-00791", device, create_info_loc.dot(Field::queryType),
                                 "is VK_QUERY_TYPE_PIPELINE_STATISTICS but pipelineStatisticsQuery feature was not enabled.");
            } else if ((pCreateInfo->pipelineStatistics & (VK_QUERY_PIPELINE_STATISTIC_TASK_SHADER_INVOCATIONS_BIT_EXT |
                                                           VK_QUERY_PIPELINE_STATISTIC_MESH_SHADER_INVOCATIONS_BIT_EXT)) &&
                       !enabled_features.meshShaderQueries) {
                skip |= LogError("VUID-VkQueryPoolCreateInfo-meshShaderQueries-07069", device,
                                 create_info_loc.dot(Field::pipelineStatistics),
                                 "(%s) contains mesh/task shader bit, but "
                                 "meshShaderQueries feature was not enabled.",
                                 string_VkQueryPipelineStatisticFlags(pCreateInfo->pipelineStatistics).c_str());
            }

            if (pCreateInfo->pipelineStatistics == 0) {
                skip |= LogError("VUID-VkQueryPoolCreateInfo-queryType-09534", device, create_info_loc.dot(Field::queryType),
                                 "is VK_QUERY_TYPE_PIPELINE_STATISTICS, but pipelineStatistics is zero");
            } else if ((pCreateInfo->pipelineStatistics & (~AllVkQueryPipelineStatisticFlagBits)) != 0) {
                skip |= LogError("VUID-VkQueryPoolCreateInfo-queryType-00792", device, create_info_loc.dot(Field::queryType),
                                 "is VK_QUERY_TYPE_PIPELINE_STATISTICS, but "
                                 "pipelineStatistics must be a valid combination of VkQueryPipelineStatisticFlagBits "
                                 "values.");
            }
            break;
        }
        case VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR: {
            if (!enabled_features.performanceCounterQueryPools) {
                skip |=
                    LogError("VUID-VkQueryPoolPerformanceCreateInfoKHR-performanceCounterQueryPools-03237", device,
                             create_info_loc.dot(Field::queryType),
                             "is VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR but performanceCounterQueryPools feature was not enabled.");
            }
            if (!vku::FindStructInPNextChain<VkQueryPoolPerformanceCreateInfoKHR>(pCreateInfo->pNext)) {
                skip |= LogError("VUID-VkQueryPoolCreateInfo-queryType-03222", device, create_info_loc.dot(Field::queryType),
                                 "is VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR, but the pNext chain does not contain an instance of "
                                 "VkQueryPoolPerformanceCreateInfoKHR.");
            }
            break;
        }
        case VK_QUERY_TYPE_MESH_PRIMITIVES_GENERATED_EXT: {
            if (!enabled_features.meshShaderQueries) {
                skip |=
                    LogError("VUID-VkQueryPoolCreateInfo-meshShaderQueries-07068", device, create_info_loc.dot(Field::queryType),
                             "is VK_QUERY_TYPE_MESH_PRIMITIVES_GENERATED_EXT but meshShaderQueries feature was not enabled.");
            }
            break;
        }
        default:
            break;
    }

    if (pCreateInfo->queryCount == 0) {
        skip |= LogError("VUID-VkQueryPoolCreateInfo-queryCount-02763", device, create_info_loc.dot(Field::queryCount), "is zero.");
    }
    return skip;
}

bool Device::manual_PreCallValidateCreateSamplerYcbcrConversion(VkDevice device,
                                                                const VkSamplerYcbcrConversionCreateInfo* pCreateInfo,
                                                                const VkAllocationCallbacks* pAllocator,
                                                                VkSamplerYcbcrConversion* pYcbcrConversion,
                                                                const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    // Check samplerYcbcrConversion feature is set
    if (!enabled_features.samplerYcbcrConversion) {
        skip |= LogError("VUID-vkCreateSamplerYcbcrConversion-None-01648", device, error_obj.location,
                         "samplerYcbcrConversion feature must be enabled.");
    }

    const VkFormat format = pCreateInfo->format;
    const Location create_info_loc = error_obj.location.dot(Field::pCreateInfo);

    // If there is a VkExternalFormatANDROID with externalFormat != 0, the value of components is ignored.
    if (GetExternalFormat(pCreateInfo->pNext) != 0) {
        return skip;
    }
    const VkComponentMapping components = pCreateInfo->components;
    // XChroma Subsampled is same as "the format has a _422 or _420 suffix" from spec
    if (vkuFormatIsXChromaSubsampled(format) == true) {
        if ((components.g != VK_COMPONENT_SWIZZLE_G) && (components.g != VK_COMPONENT_SWIZZLE_IDENTITY)) {
            skip |= LogError("VUID-VkSamplerYcbcrConversionCreateInfo-components-02581", device, create_info_loc,
                             "When using a XChroma subsampled format (%s) the components.g (%s) needs to be VK_COMPONENT_SWIZZLE_G "
                             "or VK_COMPONENT_SWIZZLE_IDENTITY.",
                             string_VkFormat(format), string_VkComponentSwizzle(components.g));
        }

        if ((components.a != VK_COMPONENT_SWIZZLE_A) && (components.a != VK_COMPONENT_SWIZZLE_IDENTITY) &&
            (components.a != VK_COMPONENT_SWIZZLE_ONE) && (components.a != VK_COMPONENT_SWIZZLE_ZERO)) {
            skip |=
                LogError("VUID-VkSamplerYcbcrConversionCreateInfo-components-02582", device, create_info_loc,
                         " When using a XChroma subsampled format (%s) the components.a (%s) needs to be VK_COMPONENT_SWIZZLE_A or "
                         "VK_COMPONENT_SWIZZLE_IDENTITY or VK_COMPONENT_SWIZZLE_ONE or VK_COMPONENT_SWIZZLE_ZERO.",
                         string_VkFormat(format), string_VkComponentSwizzle(components.a));
        }

        if ((components.r != VK_COMPONENT_SWIZZLE_R) && (components.r != VK_COMPONENT_SWIZZLE_IDENTITY) &&
            (components.r != VK_COMPONENT_SWIZZLE_B)) {
            skip |= LogError("VUID-VkSamplerYcbcrConversionCreateInfo-components-02583", device, create_info_loc,
                             "When using a XChroma subsampled format (%s) the components.r (%s) needs to be VK_COMPONENT_SWIZZLE_R "
                             "or VK_COMPONENT_SWIZZLE_IDENTITY or VK_COMPONENT_SWIZZLE_B.",
                             string_VkFormat(format), string_VkComponentSwizzle(components.r));
        }

        if ((components.b != VK_COMPONENT_SWIZZLE_B) && (components.b != VK_COMPONENT_SWIZZLE_IDENTITY) &&
            (components.b != VK_COMPONENT_SWIZZLE_R)) {
            skip |= LogError("VUID-VkSamplerYcbcrConversionCreateInfo-components-02584", device, create_info_loc,
                             "When using a XChroma subsampled format (%s) the components.b (%s) needs to be VK_COMPONENT_SWIZZLE_B "
                             "or VK_COMPONENT_SWIZZLE_IDENTITY or VK_COMPONENT_SWIZZLE_R.",
                             string_VkFormat(format), string_VkComponentSwizzle(components.b));
        }

        // If one is identity, both need to be
        const bool r_identity = ((components.r == VK_COMPONENT_SWIZZLE_R) || (components.r == VK_COMPONENT_SWIZZLE_IDENTITY));
        const bool b_identity = ((components.b == VK_COMPONENT_SWIZZLE_B) || (components.b == VK_COMPONENT_SWIZZLE_IDENTITY));
        if ((r_identity != b_identity) && ((r_identity == true) || (b_identity == true))) {
            skip |=
                LogError("VUID-VkSamplerYcbcrConversionCreateInfo-components-02585", device, create_info_loc,
                         "When using a XChroma subsampled format (%s) if either the components.r (%s) or components.b (%s) "
                         "are an identity swizzle, then both need to be an identity swizzle.",
                         string_VkFormat(format), string_VkComponentSwizzle(components.r), string_VkComponentSwizzle(components.b));
        }
    }

    if (pCreateInfo->ycbcrModel != VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY) {
        // This VU covers a lot, could have been multiple VUs, so provide a good error message for all cases.
        const char* vuid = "VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrModel-01655";
        if (components.r == VK_COMPONENT_SWIZZLE_ZERO || components.g == VK_COMPONENT_SWIZZLE_ZERO ||
            components.b == VK_COMPONENT_SWIZZLE_ZERO) {
            skip |= LogError(vuid, device, create_info_loc,
                             "The ycbcrModel (%s) is not VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY so the r, g, and b in "
                             "pCreateInfo->components can't all be VK_COMPONENT_SWIZZLE_ZERO\n%s",
                             string_VkSamplerYcbcrModelConversion(pCreateInfo->ycbcrModel),
                             string_VkComponentMapping(components).c_str());
        } else if (components.r == VK_COMPONENT_SWIZZLE_ONE || components.g == VK_COMPONENT_SWIZZLE_ONE ||
                   components.b == VK_COMPONENT_SWIZZLE_ONE) {
            skip |= LogError(vuid, device, create_info_loc,
                             "The ycbcrModel (%s) is not VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY so the r, g, and b in "
                             "pCreateInfo->components can't all be VK_COMPONENT_SWIZZLE_ONE\n%s",
                             string_VkSamplerYcbcrModelConversion(pCreateInfo->ycbcrModel),
                             string_VkComponentMapping(components).c_str());
        }

        // "must not correspond to a component which contains zero or one as a consequence of conversion to RGBA"
        // 4 component format = no issue
        // 3 = no [a]
        // 2 = no [b,a]
        // 1 = no [g,b,a]
        // depth/stencil = no [g,b,a] (shouldn't ever occur, but no VU preventing it)
        const uint32_t component_count = (vkuFormatIsDepthOrStencil(format) == true) ? 1 : vkuFormatComponentCount(format);

        if (format == VK_FORMAT_UNDEFINED) {
            // If using external format they should have been caught in GetExternalFormat above. This means the user forgot to pass
            // in the VkExternalFormatANDROID and will get an error below which will not be obvious what is going on.
            skip |= LogError(vuid, device, create_info_loc,
                             "The ycbcrModel (%s) is not VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY and pCreateInfo->format is "
                             "VK_FORMAT_UNDEFINED. Either you forgot to set the format here or if you are trying to use an "
                             "external format, and forgot to pass in VkExternalFormatANDROID (or equivalent) into the "
                             "pCreateInfo->pNext chain as this check doesn't apply for if externalFormat is non-zero.",
                             string_VkSamplerYcbcrModelConversion(pCreateInfo->ycbcrModel));

        } else if ((component_count < 4) && ((components.r == VK_COMPONENT_SWIZZLE_A) || (components.g == VK_COMPONENT_SWIZZLE_A) ||
                                             (components.b == VK_COMPONENT_SWIZZLE_A))) {
            skip |= LogError(vuid, device, create_info_loc,
                             "The ycbcrModel (%s) is not VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY so the r, g, and b in "
                             "pCreateInfo->components can't all be VK_COMPONENT_SWIZZLE_A because %s only has %" PRIu32
                             " components (not 4) so the 'a' component is invalid\n%s",
                             string_VkSamplerYcbcrModelConversion(pCreateInfo->ycbcrModel), string_VkFormat(format),
                             component_count, string_VkComponentMapping(components).c_str());
        } else if ((component_count < 3) &&
                   ((components.r == VK_COMPONENT_SWIZZLE_B) || (components.g == VK_COMPONENT_SWIZZLE_B) ||
                    (components.b == VK_COMPONENT_SWIZZLE_B) || (components.b == VK_COMPONENT_SWIZZLE_IDENTITY))) {
            skip |= LogError(
                vuid, device, create_info_loc,
                "The ycbcrModel (%s) is not VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY so the r, g, and b in "
                "pCreateInfo->components can't all be VK_COMPONENT_SWIZZLE_B because %s only has %" PRIu32
                " components (not 3 or 4) so the 'b' component is invalid\n%s%s",
                string_VkSamplerYcbcrModelConversion(pCreateInfo->ycbcrModel), string_VkFormat(format), component_count,
                string_VkComponentMapping(components).c_str(),
                (components.b == VK_COMPONENT_SWIZZLE_IDENTITY)
                    ? "\n(components.b also can't be VK_COMPONENT_SWIZZLE_IDENTITY as the is equivalent to VK_COMPONENT_SWIZZLE_B)"
                    : "");
        } else if ((component_count < 2) &&
                   ((components.r == VK_COMPONENT_SWIZZLE_G) || (components.g == VK_COMPONENT_SWIZZLE_G) ||
                    (components.g == VK_COMPONENT_SWIZZLE_IDENTITY) || (components.b == VK_COMPONENT_SWIZZLE_G))) {
            skip |= LogError(
                vuid, device, create_info_loc,
                "The ycbcrModel (%s) is not VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY so the r, g, and b in "
                "pCreateInfo->components can't all be VK_COMPONENT_SWIZZLE_G because %s only has %" PRIu32
                " components (not 2, 3 or 4) so the 'g' component is invalid\n%s%s",
                string_VkSamplerYcbcrModelConversion(pCreateInfo->ycbcrModel), string_VkFormat(format), component_count,
                string_VkComponentMapping(components).c_str(),
                (components.g == VK_COMPONENT_SWIZZLE_IDENTITY)
                    ? "\n(components.g also can't be VK_COMPONENT_SWIZZLE_IDENTITY as the is equivalent to VK_COMPONENT_SWIZZLE_G)"
                    : "");
        }
    }

    return skip;
}

bool Device::manual_PreCallValidateGetDescriptorEXT(VkDevice device, const VkDescriptorGetInfoEXT* pDescriptorInfo, size_t dataSize,
                                                    void* pDescriptor, const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;
    if (!enabled_features.descriptorBuffer) {
        skip |=
            LogError("VUID-vkGetDescriptorEXT-None-08015", device, error_obj.location, "descriptorBuffer feature was not enabled.");
    }

    const Location descriptor_info_loc = error_obj.location.dot(Field::pDescriptorInfo);
    const Location data_loc = descriptor_info_loc.dot(Field::data);
    const VkDescriptorAddressInfoEXT* address_info = nullptr;
    Field data_field = Field::Empty;
    switch (pDescriptorInfo->type) {
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
        case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK:
            skip |= LogError("VUID-VkDescriptorGetInfoEXT-type-08018", device, descriptor_info_loc.dot(Field::type), "is %s.",
                             string_VkDescriptorType(pDescriptorInfo->type));
            break;
        case VK_DESCRIPTOR_TYPE_SAMPLER:
            if (!pDescriptorInfo->data.pSampler) {
                skip |= LogError("VUID-VkDescriptorGetInfoEXT-pSampler-parameter", device, descriptor_info_loc.dot(Field::type),
                                 "is VK_DESCRIPTOR_TYPE_SAMPLER, but pSampler is null.");
            }
            break;
        case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
            if (!pDescriptorInfo->data.pCombinedImageSampler) {
                skip |= LogError("VUID-VkDescriptorGetInfoEXT-pCombinedImageSampler-parameter", device,
                                 descriptor_info_loc.dot(Field::type),
                                 "is VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, but pCombinedImageSampler is null.");
            }
            break;
        case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
            if (!pDescriptorInfo->data.pInputAttachmentImage) {
                skip |= LogError("VUID-VkDescriptorGetInfoEXT-pInputAttachmentImage-parameter", device,
                                 descriptor_info_loc.dot(Field::type),
                                 "is VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, but pInputAttachmentImage is null.");
            }
            break;
        case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
            if (pDescriptorInfo->data.pUniformTexelBuffer) {
                address_info = pDescriptorInfo->data.pUniformTexelBuffer;
                data_field = Field::pUniformTexelBuffer;
            }
            break;
        case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
            if (pDescriptorInfo->data.pStorageTexelBuffer) {
                address_info = pDescriptorInfo->data.pStorageTexelBuffer;
                data_field = Field::pStorageTexelBuffer;
            }
            break;
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
            if (pDescriptorInfo->data.pUniformBuffer) {
                address_info = pDescriptorInfo->data.pUniformBuffer;
                data_field = Field::pUniformBuffer;
            }
            break;
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
            if (pDescriptorInfo->data.pStorageBuffer) {
                address_info = pDescriptorInfo->data.pStorageBuffer;
                data_field = Field::pStorageBuffer;
            }
            break;
        default:
            break;
    }

    if (address_info) {
        const Location address_loc = data_loc.dot(data_field);
        skip |= ValidateDescriptorAddressInfoEXT(context, *address_info, address_loc);

        if (address_info->address != 0) {
            if ((pDescriptorInfo->type == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER ||
                 pDescriptorInfo->type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) &&
                address_info->format == VK_FORMAT_UNDEFINED) {
                skip |= LogError("VUID-VkDescriptorAddressInfoEXT-None-09508", device, address_loc.dot(Field::format),
                                 "is VK_FORMAT_UNDEFINED.");
            }
        }

        if (api_version < VK_API_VERSION_1_3 && !enabled_features.ycbcr2plane444Formats) {
            if (IsValueIn(address_info->format,
                          {VK_FORMAT_G8_B8R8_2PLANE_444_UNORM, VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16,
                           VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16, VK_FORMAT_G16_B16R16_2PLANE_444_UNORM})) {
                skip |= LogError("VUID-VkDescriptorAddressInfoEXT-None-12271", device, address_loc.dot(Field::format), "is %s.",
                                 string_VkFormat(address_info->format));
            }
        }
    }

    const auto* tensor_struct = vku::FindStructInPNextChain<VkDescriptorGetTensorInfoARM>(pDescriptorInfo->pNext);
    if (tensor_struct) {
        if (!enabled_features.nullDescriptor && tensor_struct->tensorView == VK_NULL_HANDLE) {
            skip |= LogError("VUID-VkDescriptorGetTensorInfoARM-nullDescriptor-09899", device,
                             error_obj.location.dot(Field::pNext).dot(Field::tensorView),
                             "is VK_NULL_HANDLE and the nullDescriptor feature is not enabled.");
        }
    }
    return skip;
}

bool Device::ValidateCmdSetDescriptorBufferOffsets(VkCommandBuffer commandBuffer, VkPipelineLayout layout, uint32_t setCount,
                                                   const uint32_t* pBufferIndices, const VkDeviceSize* pOffsets,
                                                   const Location& loc) const {
    bool skip = false;
    const bool is_2 = loc.function != Func::vkCmdSetDescriptorBufferOffsetsEXT;

    if (!enabled_features.descriptorBuffer) {
        const char* vuid = is_2 ? "VUID-vkCmdSetDescriptorBufferOffsets2EXT-descriptorBuffer-09470"
                                : "VUID-vkCmdSetDescriptorBufferOffsetsEXT-None-08060";
        skip |= LogError(vuid, commandBuffer, loc, "descriptorBuffer feature was not enabled.");
    }

    for (uint32_t i = 0; i < setCount; i++) {
        const uint32_t buffer_index = pBufferIndices[i];
        if (buffer_index >= phys_dev_ext_props.descriptor_buffer_props.maxDescriptorBufferBindings) {
            const char* vuid = is_2 ? "VUID-VkSetDescriptorBufferOffsetsInfoEXT-pBufferIndices-08064"
                                    : "VUID-vkCmdSetDescriptorBufferOffsetsEXT-pBufferIndices-08064";
            const LogObjectList objlist(commandBuffer, layout);
            skip |= LogError(vuid, objlist, loc.dot(Field::pBufferIndices, i),
                             "(%" PRIu32 ") is greater than maxDescriptorBufferBindings (%" PRIu32 ") ", buffer_index,
                             phys_dev_ext_props.descriptor_buffer_props.maxDescriptorBufferBindings);
        }

        const VkDeviceAddress offset = pOffsets[i];
        if (!IsIntegerMultipleOf(offset, phys_dev_ext_props.descriptor_buffer_props.descriptorBufferOffsetAlignment)) {
            const char* vuid = is_2 ? "VUID-VkSetDescriptorBufferOffsetsInfoEXT-pOffsets-08061"
                                    : "VUID-vkCmdSetDescriptorBufferOffsetsEXT-pOffsets-08061";
            const LogObjectList objlist(commandBuffer, layout);
            skip |= LogError(vuid, objlist, loc.dot(Field::pOffsets, i),
                             "(%" PRIuLEAST64
                             ") is not aligned to descriptorBufferOffsetAlignment"
                             " (%" PRIuLEAST64 ")",
                             offset, phys_dev_ext_props.descriptor_buffer_props.descriptorBufferOffsetAlignment);
        }
    }

    return skip;
}

bool Device::manual_PreCallValidateCmdSetDescriptorBufferOffsetsEXT(VkCommandBuffer commandBuffer,
                                                                    VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout,
                                                                    uint32_t firstSet, uint32_t setCount,
                                                                    const uint32_t* pBufferIndices, const VkDeviceSize* pOffsets,
                                                                    const Context& context) const {
    return ValidateCmdSetDescriptorBufferOffsets(commandBuffer, layout, setCount, pBufferIndices, pOffsets,
                                                 context.error_obj.location);
}

bool Device::manual_PreCallValidateCmdSetDescriptorBufferOffsets2EXT(
    VkCommandBuffer commandBuffer, const VkSetDescriptorBufferOffsetsInfoEXT* pSetDescriptorBufferOffsetsInfo,
    const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;
    skip |= ValidateCmdSetDescriptorBufferOffsets(
        commandBuffer, pSetDescriptorBufferOffsetsInfo->layout, pSetDescriptorBufferOffsetsInfo->setCount,
        pSetDescriptorBufferOffsetsInfo->pBufferIndices, pSetDescriptorBufferOffsetsInfo->pOffsets, error_obj.location);

    if (pSetDescriptorBufferOffsetsInfo->layout == VK_NULL_HANDLE) {
        if (!enabled_features.dynamicPipelineLayout) {
            skip |=
                LogError("VUID-VkSetDescriptorBufferOffsetsInfoEXT-None-09495", commandBuffer,
                         error_obj.location.dot(Field::pSetDescriptorBufferOffsetsInfo).dot(Field::layout), "is VK_NULL_HANDLE.");
        } else if (!vku::FindStructInPNextChain<VkPipelineLayoutCreateInfo>(pSetDescriptorBufferOffsetsInfo->pNext)) {
            skip |= LogError("VUID-VkSetDescriptorBufferOffsetsInfoEXT-layout-09496", commandBuffer,
                             error_obj.location.dot(Field::pSetDescriptorBufferOffsetsInfo).dot(Field::layout),
                             "is VK_NULL_HANDLE and pNext is missing VkPipelineLayoutCreateInfo.");
        }
    }

    return skip;
}

bool Device::manual_PreCallValidateCmdBindDescriptorBufferEmbeddedSamplers2EXT(
    VkCommandBuffer commandBuffer, const VkBindDescriptorBufferEmbeddedSamplersInfoEXT* pBindDescriptorBufferEmbeddedSamplersInfo,
    const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    if (pBindDescriptorBufferEmbeddedSamplersInfo->layout == VK_NULL_HANDLE) {
        if (!enabled_features.dynamicPipelineLayout) {
            skip |= LogError("VUID-VkBindDescriptorBufferEmbeddedSamplersInfoEXT-None-09495", commandBuffer,
                             error_obj.location.dot(Field::pBindDescriptorBufferEmbeddedSamplersInfo).dot(Field::layout),
                             "is VK_NULL_HANDLE.");
        } else if (!vku::FindStructInPNextChain<VkPipelineLayoutCreateInfo>(pBindDescriptorBufferEmbeddedSamplersInfo->pNext)) {
            skip |= LogError("VUID-VkBindDescriptorBufferEmbeddedSamplersInfoEXT-layout-09496", commandBuffer,
                             error_obj.location.dot(Field::pBindDescriptorBufferEmbeddedSamplersInfo).dot(Field::layout),
                             "is VK_NULL_HANDLE and pNext is missing VkPipelineLayoutCreateInfo.");
        }
    }

    return skip;
}

bool Device::manual_PreCallValidateCmdPushDescriptorSetWithTemplate2(
    VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfo* pPushDescriptorSetWithTemplateInfo,
    const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    if (pPushDescriptorSetWithTemplateInfo->layout == VK_NULL_HANDLE) {
        if (!enabled_features.dynamicPipelineLayout) {
            skip |= LogError("VUID-VkPushDescriptorSetWithTemplateInfo-None-09495", commandBuffer,
                             error_obj.location.dot(Field::pPushDescriptorSetWithTemplateInfo).dot(Field::layout),
                             "is VK_NULL_HANDLE.");
        } else if (!vku::FindStructInPNextChain<VkPipelineLayoutCreateInfo>(pPushDescriptorSetWithTemplateInfo->pNext)) {
            skip |= LogError("VUID-VkPushDescriptorSetWithTemplateInfo-layout-09496", commandBuffer,
                             error_obj.location.dot(Field::pPushDescriptorSetWithTemplateInfo).dot(Field::layout),
                             "is VK_NULL_HANDLE and pNext is missing VkPipelineLayoutCreateInfo.");
        }
    }
    return skip;
}

bool Device::manual_PreCallValidateCmdBindDescriptorSets2(VkCommandBuffer commandBuffer,
                                                          const VkBindDescriptorSetsInfoKHR* pBindDescriptorSetsInfo,
                                                          const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;
    if (pBindDescriptorSetsInfo->layout == VK_NULL_HANDLE) {
        if (!enabled_features.dynamicPipelineLayout) {
            skip |= LogError("VUID-VkBindDescriptorSetsInfo-None-09495", commandBuffer,
                             error_obj.location.dot(Field::pBindDescriptorSetsInfo).dot(Field::layout), "is VK_NULL_HANDLE.");
        } else if (!vku::FindStructInPNextChain<VkPipelineLayoutCreateInfo>(pBindDescriptorSetsInfo->pNext)) {
            skip |= LogError("VUID-VkBindDescriptorSetsInfo-layout-09496", commandBuffer,
                             error_obj.location.dot(Field::pBindDescriptorSetsInfo).dot(Field::layout),
                             "is VK_NULL_HANDLE and pNext is missing VkPipelineLayoutCreateInfo.");
        }
    }

    return skip;
}

bool Device::manual_PreCallValidateCmdBindSamplerHeapEXT(VkCommandBuffer commandBuffer, const VkBindHeapInfoEXT* pBindInfo,
                                                         const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    if (pBindInfo->reservedRangeOffset + pBindInfo->reservedRangeSize > pBindInfo->heapRange.size) {
        skip |= LogError("VUID-vkCmdBindSamplerHeapEXT-pBindInfo-11223", commandBuffer,
                         error_obj.location.dot(Field::pBindInfo).dot(Field::reservedRangeOffset),
                         "(%" PRIu64 ") + reservedRangeSize (%" PRIu64 ") is greater than pBindInfo->heapRange.size (%" PRIu64 ").",
                         pBindInfo->reservedRangeOffset, pBindInfo->reservedRangeSize, pBindInfo->heapRange.size);
    }

    if (pBindInfo->reservedRangeSize < phys_dev_ext_props.descriptor_heap_props.minSamplerHeapReservedRange) {
        skip |= LogError("VUID-vkCmdBindSamplerHeapEXT-pBindInfo-11224", commandBuffer,
                         error_obj.location.dot(Field::pBindInfo).dot(Field::reservedRangeSize),
                         "(%" PRIu64 ") is less than minSamplerHeapReservedRange (%" PRIu64 ").", pBindInfo->reservedRangeSize,
                         phys_dev_ext_props.descriptor_heap_props.minSamplerHeapReservedRange);
    }

    if (pBindInfo->heapRange.size > phys_dev_ext_props.descriptor_heap_props.maxSamplerHeapSize) {
        skip |= LogError("VUID-vkCmdBindSamplerHeapEXT-pBindInfo-11225", commandBuffer,
                         error_obj.location.dot(Field::pBindInfo).dot(Field::heapRange).dot(Field::size),
                         "(%" PRIu64 ") is greater than maxSamplerHeapSize (%" PRIu64 ").", pBindInfo->heapRange.size,
                         phys_dev_ext_props.descriptor_heap_props.maxSamplerHeapSize);
    }

    if (!IsPointerAligned(pBindInfo->heapRange.address, phys_dev_ext_props.descriptor_heap_props.samplerHeapAlignment)) {
        skip |= LogError("VUID-vkCmdBindSamplerHeapEXT-pBindInfo-11226", commandBuffer,
                         error_obj.location.dot(Field::pBindInfo).dot(Field::heapRange).dot(Field::address),
                         "(0x%" PRIx64 ") must be aligned with samplerHeapAlignment (%" PRIu64 ").", pBindInfo->heapRange.address,
                         phys_dev_ext_props.descriptor_heap_props.samplerHeapAlignment);
    }

    if (!IsPointerAligned(pBindInfo->reservedRangeOffset, phys_dev_ext_props.descriptor_heap_props.samplerDescriptorAlignment)) {
        skip |= LogError("VUID-vkCmdBindSamplerHeapEXT-pBindInfo-11434", commandBuffer,
                         error_obj.location.dot(Field::pBindInfo).dot(Field::reservedRangeOffset),
                         "(0x%" PRIx64 ") must be aligned with samplerDescriptorAlignment (%" PRIu64 ").",
                         pBindInfo->reservedRangeOffset, phys_dev_ext_props.descriptor_heap_props.samplerDescriptorAlignment);
    }

    return skip;
}

bool Device::manual_PreCallValidateCmdBindResourceHeapEXT(VkCommandBuffer commandBuffer, const VkBindHeapInfoEXT* pBindInfo,
                                                          const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    if (pBindInfo->reservedRangeOffset + pBindInfo->reservedRangeSize > pBindInfo->heapRange.size) {
        skip |= LogError("VUID-vkCmdBindResourceHeapEXT-pBindInfo-11232", commandBuffer,
                         error_obj.location.dot(Field::pBindInfo).dot(Field::reservedRangeOffset),
                         "(%" PRIu64 ") + reservedRangeSize (%" PRIu64 ") is greater than pBindInfo->heapRange.size (%" PRIu64 ").",
                         pBindInfo->reservedRangeOffset, pBindInfo->reservedRangeSize, pBindInfo->heapRange.size);
    }

    if (pBindInfo->reservedRangeSize < phys_dev_ext_props.descriptor_heap_props.minResourceHeapReservedRange) {
        skip |= LogError("VUID-vkCmdBindResourceHeapEXT-pBindInfo-11233", commandBuffer,
                         error_obj.location.dot(Field::pBindInfo).dot(Field::reservedRangeSize),
                         "(%" PRIu64 ") is less than minResourceHeapReservedRange (%" PRIu64 ").", pBindInfo->reservedRangeSize,
                         phys_dev_ext_props.descriptor_heap_props.minResourceHeapReservedRange);
    }

    if (pBindInfo->heapRange.size > phys_dev_ext_props.descriptor_heap_props.maxResourceHeapSize) {
        skip |= LogError("VUID-vkCmdBindResourceHeapEXT-pBindInfo-11234", commandBuffer,
                         error_obj.location.dot(Field::pBindInfo).dot(Field::heapRange).dot(Field::size),
                         "(%" PRIu64 ") is greater than maxResourceHeapSize (%" PRIu64 ").", pBindInfo->heapRange.size,
                         phys_dev_ext_props.descriptor_heap_props.maxResourceHeapSize);
    }

    if (!IsPointerAligned(pBindInfo->heapRange.address, phys_dev_ext_props.descriptor_heap_props.resourceHeapAlignment)) {
        skip |= LogError("VUID-vkCmdBindResourceHeapEXT-pBindInfo-11235", commandBuffer,
                         error_obj.location.dot(Field::pBindInfo).dot(Field::heapRange).dot(Field::address),
                         "(0x%" PRIx64 ") must be aligned with resourceHeapAlignment (%" PRIu64 ").", pBindInfo->heapRange.address,
                         phys_dev_ext_props.descriptor_heap_props.resourceHeapAlignment);
    }

    // While these are 2 VUs, they really should be one as you want to know both together
    const bool buffer_misaligned =
        !IsPointerAligned(pBindInfo->reservedRangeOffset, phys_dev_ext_props.descriptor_heap_props.bufferDescriptorAlignment);
    const bool image_misaligned =
        !IsPointerAligned(pBindInfo->reservedRangeOffset, phys_dev_ext_props.descriptor_heap_props.imageDescriptorAlignment);
    if (buffer_misaligned || image_misaligned) {
        const char* vuid =
            buffer_misaligned ? "VUID-vkCmdBindResourceHeapEXT-pBindInfo-11435" : "VUID-vkCmdBindResourceHeapEXT-pBindInfo-11436";
        skip |= LogError(vuid, commandBuffer, error_obj.location.dot(Field::pBindInfo).dot(Field::reservedRangeOffset),
                         "(0x%" PRIx64 ") must be aligned with both bufferDescriptorAlignment (0x%" PRIx64
                         ") and imageDescriptorAlignment (0x%" PRIx64 ").",
                         pBindInfo->reservedRangeOffset, phys_dev_ext_props.descriptor_heap_props.bufferDescriptorAlignment,
                         phys_dev_ext_props.descriptor_heap_props.imageDescriptorAlignment);
    }
    return skip;
}

bool Device::ValidateHeapTexelBufferAlignment(const VkTexelBufferDescriptorInfoEXT& info, const VkDescriptorType type,
                                              const Location& loc) const {
    bool skip = false;
    if (enabled_features.texelBufferAlignment) {
        VkDeviceSize texel_block_size = GetTexelBufferFormatSize(info.format);
        bool texel_size_of_three = false;
        if ((texel_block_size % 3) == 0) {
            texel_size_of_three = true;
            texel_block_size /= 3;
        }

        if (type == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER) {
            VkDeviceSize alignment_requirement = phys_dev_props_core13.uniformTexelBufferOffsetAlignmentBytes;
            if (phys_dev_props_core13.uniformTexelBufferOffsetSingleTexelAlignment) {
                alignment_requirement = std::min(alignment_requirement, texel_block_size);
            }
            if (!IsPointerAligned(info.addressRange.address, alignment_requirement)) {
                std::ostringstream ss;
                ss << "(0x" << std::hex << info.addressRange.address << std::dec << ") must be a aligned to "
                   << alignment_requirement << "\n";
                if (phys_dev_props_core13.uniformTexelBufferOffsetSingleTexelAlignment) {
                    ss << "uniformTexelBufferOffsetSingleTexelAlignment is VK_TRUE, so we take "
                          "min(uniformTexelBufferOffsetAlignmentBytes, texelBlockSize("
                       << string_VkFormat(info.format) << ")) which is min("
                       << phys_dev_props_core13.uniformTexelBufferOffsetAlignmentBytes << ", " << texel_block_size << ")";
                    if (texel_size_of_three) {
                        ss << "\nThe size of a texel " << (texel_block_size * 3)
                           << " was a multiple of three bytes, so the size of a single component of "
                           << string_VkFormat(info.format) << " was used instead";
                    }
                } else {
                    ss << "uniformTexelBufferOffsetSingleTexelAlignment is VK_FALSE and uniformTexelBufferOffsetAlignmentBytes is "
                       << phys_dev_props_core13.uniformTexelBufferOffsetAlignmentBytes;
                }

                skip |= LogError("VUID-VkResourceDescriptorInfoEXT-type-12349", device,
                                 loc.dot(Field::addressRange).dot(Field::address).dot(Field::address), "%s", ss.str().c_str());
            }
        } else if (type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) {
            VkDeviceSize alignment_requirement = phys_dev_props_core13.storageTexelBufferOffsetAlignmentBytes;
            if (phys_dev_props_core13.storageTexelBufferOffsetSingleTexelAlignment) {
                alignment_requirement = std::min(alignment_requirement, texel_block_size);
            }
            if (!IsPointerAligned(info.addressRange.address, alignment_requirement)) {
                std::ostringstream ss;
                ss << "(0x" << std::hex << info.addressRange.address << std::dec << ") must be aligned to " << alignment_requirement
                   << "\n";
                if (phys_dev_props_core13.storageTexelBufferOffsetSingleTexelAlignment) {
                    ss << "storageTexelBufferOffsetSingleTexelAlignment is VK_TRUE, so we take "
                          "min(storageTexelBufferOffsetAlignmentBytes, texelBlockSize("
                       << string_VkFormat(info.format) << ")) which is min("
                       << phys_dev_props_core13.storageTexelBufferOffsetAlignmentBytes << ", " << texel_block_size << ")";
                    if (texel_size_of_three) {
                        ss << "\nThe size of a texel " << (texel_block_size * 3)
                           << " was a multiple of three bytes, so the size of a single component of "
                           << string_VkFormat(info.format) << " was used instead";
                    }
                } else {
                    ss << "storageTexelBufferOffsetSingleTexelAlignment is VK_FALSE and storageTexelBufferOffsetAlignmentBytes is "
                       << phys_dev_props_core13.storageTexelBufferOffsetAlignmentBytes;
                }

                skip |= LogError("VUID-VkResourceDescriptorInfoEXT-type-12349", device,
                                 loc.dot(Field::addressRange).dot(Field::address).dot(Field::address), "%s", ss.str().c_str());
            }
        }
    } else if (!IsPointerAligned(info.addressRange.address, phys_dev_props.limits.minTexelBufferOffsetAlignment)) {
        skip |= LogError("VUID-VkResourceDescriptorInfoEXT-type-12349", device, loc.dot(Field::addressRange).dot(Field::address),
                         "(0x%" PRIx64 ") is not aligned to minTexelBufferOffsetAlignment (%" PRIu64 ")", info.addressRange.address,
                         phys_dev_props.limits.minTexelBufferOffsetAlignment);
    }
    return skip;
}

bool Device::manual_PreCallValidateWriteResourceDescriptorsEXT(VkDevice device, uint32_t resourceCount,
                                                               const VkResourceDescriptorInfoEXT* pResources,
                                                               const VkHostAddressRangeEXT* pDescriptors,
                                                               const Context& context) const {
    bool skip = false;
    if (!pResources) {
        return skip;
    }

    if (!enabled_features.descriptorHeap) {
        skip |= LogError("VUID-vkWriteResourceDescriptorsEXT-descriptorHeap-11206", device, context.error_obj.location,
                         "descriptorHeap feature was not enabled.");
    }

    for (uint32_t i = 0; i < resourceCount; ++i) {
        const VkResourceDescriptorInfoEXT& resource = pResources[i];
        const Location resource_loc = context.error_obj.location.dot(Field::pResources, i);
        const Location data_loc = resource_loc.dot(Field::data);

        if (IsDescriptorHeapImage(resource.type)) {
            if (!resource.data.pImage) {
                if (!enabled_features.nullDescriptor &&
                    (resource.type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || resource.type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)) {
                    skip |= LogError("VUID-VkResourceDescriptorInfoEXT-None-11211", device, data_loc.dot(Field::pImage),
                                     "is NULL, but nullDescriptor feature is not enabled. (type is %s)",
                                     string_VkDescriptorType(resource.type));
                }
                if (IsValueIn(resource.type, {VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM,
                                              VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT})) {
                    skip |= LogError("VUID-VkResourceDescriptorInfoEXT-type-11469", device, data_loc.dot(Field::pImage),
                                     "must not be NULL for descriptor type %s.", string_VkDescriptorType(resource.type));
                }
            } else {
                if (!IsValueIn(resource.data.pImage->layout,
                               {VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
                                VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL,
                                VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL,
                                VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL,
                                VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR,
                                VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT, VK_IMAGE_LAYOUT_GENERAL})) {
                    skip |= LogError("VUID-VkImageDescriptorInfoEXT-layout-11219", device,
                                     data_loc.dot(Field::pImage).dot(Field::layout), "(%s) is not valid.",
                                     string_VkImageLayout(resource.data.pImage->layout));
                }

                if (resource.data.pImage->pView) {
                    skip |=
                        ValidateImageViewCreateInfo(*resource.data.pImage->pView, data_loc.dot(Field::pImage).dot(Field::pView));

                    if ((resource.data.pImage->pView->subresourceRange.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) &&
                        IsValueIn(
                            resource.data.pImage->layout,
                            {VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL,
                             VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL,
                             VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL})) {
                        skip |= LogError(
                            "VUID-VkImageDescriptorInfoEXT-layout-11221", device, data_loc.dot(Field::pImage).dot(Field::layout),
                            "is %s, but aspectMask (%s) includes VK_IMAGE_ASPECT_COLOR_BIT.",
                            string_VkImageLayout(resource.data.pImage->layout),
                            string_VkImageAspectFlags(resource.data.pImage->pView->subresourceRange.aspectMask).c_str());
                    }
                }
            }
        } else if (IsDescriptorHeapTexelBuffer(resource.type)) {
            if (resource.data.pTexelBuffer) {
                const VkTexelBufferDescriptorInfoEXT& texel_buffer = *resource.data.pTexelBuffer;
                skip |= ValidateHeapTexelBufferAlignment(texel_buffer, resource.type, data_loc.dot(Field::pTexelBuffer));
            } else if (!enabled_features.nullDescriptor) {
                skip |= LogError("VUID-VkResourceDescriptorInfoEXT-None-11212", device, data_loc.dot(Field::pTexelBuffer),
                                 "is NULL, but nullDescriptor feature is not enabled. (type is %s)",
                                 string_VkDescriptorType(resource.type));
            }
        } else if (IsDescriptorHeapAddr(resource.type)) {
            if (!resource.data.pAddressRange && !enabled_features.nullDescriptor) {
                skip |= LogError("VUID-VkResourceDescriptorInfoEXT-None-11213", device, data_loc.dot(Field::pAddressRange),
                                 "is NULL, but nullDescriptor feature is not enabled. (type is %s)",
                                 string_VkDescriptorType(resource.type));
            }
            if (resource.data.pAddressRange) {
                if (resource.type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) {
                    if (!IsPointerAligned(resource.data.pAddressRange->address,
                                          phys_dev_props.limits.minUniformBufferOffsetAlignment)) {
                        skip |=
                            LogError("VUID-VkResourceDescriptorInfoEXT-type-12350", device,
                                     data_loc.dot(Field::pAddressRange).dot(Field::address),
                                     "(0x%" PRIx64 ") is not aligned to minUniformBufferOffsetAlignment (%" PRIu64 ")",
                                     resource.data.pAddressRange->address, phys_dev_props.limits.minUniformBufferOffsetAlignment);
                    }
                    if (resource.data.pAddressRange->size > phys_dev_props.limits.maxUniformBufferRange) {
                        // VUID being added in https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/8269/diffs
                        skip |=
                            LogError("UNASSIGNED-VkResourceDescriptorInfoEXT-size-UBO", device,
                                     data_loc.dot(Field::pAddressRange).dot(Field::size),
                                     "(%" PRIu64 ") is greater than maxUniformBufferRange (%" PRIu32
                                     ")\nHint: You can have multiple descriptors point to different parts of the Uniform Buffer.",
                                     resource.data.pAddressRange->size, phys_dev_props.limits.maxUniformBufferRange);
                    }
                } else if (resource.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) {
                    if (!IsPointerAligned(resource.data.pAddressRange->address,
                                          phys_dev_props.limits.minStorageBufferOffsetAlignment)) {
                        skip |=
                            LogError("VUID-VkResourceDescriptorInfoEXT-type-12351", device,
                                     data_loc.dot(Field::pAddressRange).dot(Field::address),
                                     "(0x%" PRIx64 ") is not aligned to minStorageBufferOffsetAlignment (%" PRIu64 ")",
                                     resource.data.pAddressRange->address, phys_dev_props.limits.minStorageBufferOffsetAlignment);
                    }

                    if (!enabled_features.shader64BitIndexing &&
                        resource.data.pAddressRange->size > phys_dev_props.limits.maxStorageBufferRange) {
                        // VUID being added in https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/8269/diffs
                        skip |= LogError("UNASSIGNED-VkResourceDescriptorInfoEXT-size-SSBO", device,
                                         data_loc.dot(Field::pAddressRange).dot(Field::size),
                                         "(%" PRIu64 ") is greater than maxStorageBufferRange (%" PRIu32
                                         ")\nHint: You can have multiple descriptors point to different parts of the Storage "
                                         "Buffer\nHint: This can be relaxed if shader64BitIndexing is enabled",
                                         resource.data.pAddressRange->size, phys_dev_props.limits.maxStorageBufferRange);
                    }

                } else if (IsValueIn(resource.type,
                                     {VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV,
                                      VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV})) {
                    if (!IsPointerAligned(resource.data.pAddressRange->address, 256)) {
                        skip |= LogError("VUID-VkResourceDescriptorInfoEXT-type-11454", device,
                                         data_loc.dot(Field::pAddressRange).dot(Field::address),
                                         "(0x%" PRIx64 ") is not aligned to 256", resource.data.pAddressRange->address);
                    }
                }
            }
        } else if (IsDescriptorHeapTensor(resource.type)) {
            if (!resource.data.pTensorARM && !enabled_features.nullDescriptor) {
                skip |= LogError("VUID-VkResourceDescriptorInfoEXT-None-11457", device, data_loc.dot(Field::pTensorARM),
                                 "is NULL, but nullDescriptor feature is not enabled. (type is %s)",
                                 string_VkDescriptorType(resource.type));
            }
        } else {
            skip |= LogError("VUID-VkResourceDescriptorInfoEXT-type-11210", device, resource_loc.dot(Field::type),
                             "(%s) is not a supported type when using descriptor heaps.", string_VkDescriptorType(resource.type));
        }

        const auto* object_name = vku::FindStructInPNextChain<VkDebugUtilsObjectNameInfoEXT>(resource.pNext);
        if (object_name && object_name->objectType != VK_OBJECT_TYPE_UNKNOWN) {
            skip |=
                LogError("VUID-VkResourceDescriptorInfoEXT-pNext-11401", device, resource_loc.dot(Field::pNext),
                         "contains VkDebugUtilsObjectNameInfoEXT structure with objectType %s (must be VK_OBJECT_TYPE_UNKNOWN).",
                         string_VkObjectType(object_name->objectType));
        }
    }

    return skip;
}

bool Device::manual_PreCallValidateWriteSamplerDescriptorsEXT(VkDevice device, uint32_t samplerCount,
                                                              const VkSamplerCreateInfo* pSamplers,
                                                              const VkHostAddressRangeEXT* pDescriptors,
                                                              const Context& context) const {
    bool skip = false;

    if (!enabled_features.descriptorHeap) {
        skip |= LogError("VUID-vkWriteSamplerDescriptorsEXT-descriptorHeap-11202", device, context.error_obj.location,
                         "descriptorHeap feature was not enabled.");
    }

    for (uint32_t i = 0; i < samplerCount; ++i) {
        const VkSamplerCreateInfo& sampler_ci = pSamplers[i];
        const Location sampler_loc = context.error_obj.location.dot(Field::pSamplers, i);

        const VkHostAddressRangeEXT& descriptor_range = pDescriptors[i];
        if (static_cast<VkDeviceSize>(descriptor_range.size) < phys_dev_ext_props.descriptor_heap_props.samplerDescriptorSize) {
            skip |= LogError("VUID-vkWriteSamplerDescriptorsEXT-size-11203", device,
                             context.error_obj.location.dot(Field::pDescriptors, i).dot(Field::size),
                             "(%" PRIu64 ") is less than the size of a samplerDescriptorSize (%" PRIu64 ").",
                             static_cast<VkDeviceSize>(descriptor_range.size),
                             phys_dev_ext_props.descriptor_heap_props.samplerDescriptorSize);
        }
        if (vku::FindStructInPNextChain<VkSamplerYcbcrConversionInfo>(sampler_ci.pNext)) {
            skip |= LogError("VUID-vkWriteSamplerDescriptorsEXT-pSamplers-11204", device, sampler_loc.dot(Field::pNext),
                             "cannot contain a VkSamplerYcbcrConversionInfo structure.\n%s",
                             PrintPNextChain(Struct::VkSamplerCreateInfo, sampler_ci.pNext).c_str());
        }

        if (sampler_ci.borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT ||
            sampler_ci.borderColor == VK_BORDER_COLOR_INT_CUSTOM_EXT) {
            const auto bc = vku::FindStructInPNextChain<VkSamplerCustomBorderColorIndexCreateInfoEXT>(sampler_ci.pNext);
            if (!bc) {
                skip |= LogError("VUID-vkWriteSamplerDescriptorsEXT-borderColor-11444", device, sampler_loc.dot(Field::borderColor),
                                 "is %s, but VkSamplerCustomBorderColorIndexCreateInfoEXT is missing.\n%s",
                                 string_VkBorderColor(sampler_ci.borderColor),
                                 PrintPNextChain(Struct::VkSamplerCreateInfo, sampler_ci.pNext).c_str());
            } else if (bc->index >= phys_dev_ext_props.custom_border_color_props.maxCustomBorderColorSamplers) {
                skip |= LogError("VUID-vkWriteSamplerDescriptorsEXT-borderColor-11205", device,
                                 sampler_loc.pNext(Struct::VkSamplerCustomBorderColorIndexCreateInfoEXT, Field::index),
                                 "(%" PRIu32 ") is greater than or equal to maxCustomBorderColorSamplers (%" PRIu32 ").", bc->index,
                                 phys_dev_ext_props.custom_border_color_props.maxCustomBorderColorSamplers);
            }
        }

        const auto* object_name = vku::FindStructInPNextChain<VkDebugUtilsObjectNameInfoEXT>(sampler_ci.pNext);
        if (object_name && object_name->objectType != VK_OBJECT_TYPE_UNKNOWN) {
            skip |=
                LogError("VUID-vkWriteSamplerDescriptorsEXT-pNext-11400", device, sampler_loc.dot(Field::pNext),
                         "contains VkDebugUtilsObjectNameInfoEXT structure with objectType %s. (must be VK_OBJECT_TYPE_UNKNOWN)",
                         string_VkObjectType(object_name->objectType));
        }

        skip |= ValidateSamplerCreateInfo(sampler_ci, sampler_loc, context);
    }

    return skip;
}

}  // namespace stateless
