/* Copyright (c) 2015-2023 The Khronos Group Inc.
 * Copyright (c) 2015-2023 Valve Corporation
 * Copyright (c) 2015-2023 LunarG, Inc.
 * Copyright (C) 2015-2023 Google Inc.
 * Modifications Copyright (C) 2020-2022 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 <string>
#include <sstream>
#include <vector>

#include "containers/range_vector.h"
#include "core_validation.h"
#include <vulkan/vk_enum_string_helper.h>
#include "generated/chassis.h"

// Returns the intersection of the ranges [x, x + x_size) and [y, y + y_size)
static sparse_container::range<int64_t> GetRangeIntersection(int64_t x, uint64_t x_size, int64_t y, uint64_t y_size) {
    int64_t intersection_min = std::max(x, y);
    int64_t intersection_max = std::min(x + static_cast<int64_t>(x_size), y + static_cast<int64_t>(y_size));

    return {intersection_min, intersection_max};
}

// Returns true if [x, x + x_size) and [y, y + y_size) overlap
static bool RangesIntersect(int64_t x, uint64_t x_size, int64_t y, uint64_t y_size) {
    auto intersection = GetRangeIntersection(x, x_size, y, y_size);
    return intersection.non_empty();
}

struct ImageRegionIntersection {
    VkImageSubresourceLayers subresource = {};
    VkOffset3D offset = {0, 0, 0};
    VkExtent3D extent = {1, 1, 1};
    bool has_instersection = false;
    std::string String() const noexcept {
        std::stringstream ss;
        ss << "{ subresource { aspectMask: " << string_VkImageAspectFlags(subresource.aspectMask)
           << ", mipLevel: " << subresource.mipLevel << ", baseArrayLayer: " << subresource.baseArrayLayer
           << ", layerCount: " << subresource.layerCount << " }, offset {" << offset.x << ", " << offset.y << ", " << offset.z
           << "}, extent {" << extent.width << ", " << extent.height << ", " << extent.depth << "} }";
        return ss.str();
    }
};

// Returns true if source area of first vkImageCopy/vkImageCopy2KHR region intersects dest area of second region
// It is assumed that these are copy regions within a single image (otherwise no possibility of collision)
template <typename RegionType>
static ImageRegionIntersection GetRegionIntersection(const RegionType &region0, const RegionType &region1, VkImageType type,
                                                     bool is_multiplane) {
    ImageRegionIntersection result = {};

    // Separate planes within a multiplane image cannot intersect
    if (is_multiplane && (region0.srcSubresource.aspectMask != region1.dstSubresource.aspectMask)) {
        return result;
    }
    auto intersection = GetRangeIntersection(region0.srcSubresource.baseArrayLayer, region0.srcSubresource.layerCount,
                                             region1.dstSubresource.baseArrayLayer, region1.dstSubresource.layerCount);
    if ((region0.srcSubresource.mipLevel == region1.dstSubresource.mipLevel) && intersection.non_empty()) {
        result.subresource.aspectMask = region0.srcSubresource.aspectMask;
        result.subresource.baseArrayLayer = static_cast<uint32_t>(intersection.begin);
        result.subresource.layerCount = static_cast<uint32_t>(intersection.distance());
        result.subresource.mipLevel = region0.srcSubresource.mipLevel;
        result.has_instersection = true;
        switch (type) {
            case VK_IMAGE_TYPE_3D:
                intersection =
                    GetRangeIntersection(region0.srcOffset.z, region0.extent.depth, region1.dstOffset.z, region1.extent.depth);
                if (intersection.non_empty()) {
                    result.offset.z = static_cast<int32_t>(intersection.begin);
                    result.extent.depth = static_cast<uint32_t>(intersection.distance());
                } else {
                    result.has_instersection = false;
                    return result;
                }
                [[fallthrough]];
            case VK_IMAGE_TYPE_2D:
                intersection =
                    GetRangeIntersection(region0.srcOffset.y, region0.extent.height, region1.dstOffset.y, region1.extent.height);
                if (intersection.non_empty()) {
                    result.offset.y = static_cast<int32_t>(intersection.begin);
                    result.extent.height = static_cast<uint32_t>(intersection.distance());
                } else {
                    result.has_instersection = false;
                    return result;
                }
                [[fallthrough]];
            case VK_IMAGE_TYPE_1D:
                intersection =
                    GetRangeIntersection(region0.srcOffset.x, region0.extent.width, region1.dstOffset.x, region1.extent.width);
                if (intersection.non_empty()) {
                    result.offset.x = static_cast<int32_t>(intersection.begin);
                    result.extent.width = static_cast<uint32_t>(intersection.distance());
                } else {
                    result.has_instersection = false;
                    return result;
                }
                break;
            default:
                // Unrecognized or new IMAGE_TYPE enums will be caught in parameter_validation
                assert(false);
        }
    }
    return result;
}

// Returns true if source area of first vkImageCopy/vkImageCopy2KHR region intersects dest area of second region
// It is assumed that these are copy regions within a single image (otherwise no possibility of collision)
template <typename RegionType>
static bool RegionIntersects(const RegionType *region0, const RegionType *region1, VkImageType type, bool is_multiplane) {
    return GetRegionIntersection(region0, region1, type, is_multiplane).has_instersection;
}

template <typename RegionType>
static bool RegionIntersectsBlit(const RegionType *region0, const RegionType *region1, VkImageType type, bool is_multiplane) {
    bool result = false;

    // Separate planes within a multiplane image cannot intersect
    if (is_multiplane && (region0->srcSubresource.aspectMask != region1->dstSubresource.aspectMask)) {
        return result;
    }

    if ((region0->srcSubresource.mipLevel == region1->dstSubresource.mipLevel) &&
        (RangesIntersect(region0->srcSubresource.baseArrayLayer, region0->srcSubresource.layerCount,
                         region1->dstSubresource.baseArrayLayer, region1->dstSubresource.layerCount))) {
        result = true;
        switch (type) {
            case VK_IMAGE_TYPE_3D:
                result &= RangesIntersect(region0->srcOffsets[0].z, region0->srcOffsets[1].z - region0->srcOffsets[0].z,
                                          region1->dstOffsets[0].z, region1->dstOffsets[1].z - region1->dstOffsets[0].z);
                [[fallthrough]];
            case VK_IMAGE_TYPE_2D:
                result &= RangesIntersect(region0->srcOffsets[0].y, region0->srcOffsets[1].y - region0->srcOffsets[0].y,
                                          region1->dstOffsets[0].y, region1->dstOffsets[1].y - region1->dstOffsets[0].y);
                [[fallthrough]];
            case VK_IMAGE_TYPE_1D:
                result &= RangesIntersect(region0->srcOffsets[0].x, region0->srcOffsets[1].x - region0->srcOffsets[0].x,
                                          region1->dstOffsets[0].x, region1->dstOffsets[1].x - region1->dstOffsets[0].x);
                break;
            default:
                // Unrecognized or new IMAGE_TYPE enums will be caught in parameter_validation
                assert(false);
        }
    }
    return result;
}

// Test if the extent argument has all dimensions set to 0.
static inline bool IsExtentAllZeroes(const VkExtent3D &extent) {
    return ((extent.width == 0) && (extent.height == 0) && (extent.depth == 0));
}

// Returns the image transfer granularity for a specific image scaled by compressed block size if necessary.
VkExtent3D CoreChecks::GetScaledItg(const vvl::CommandBuffer &cb_state, const vvl::Image &image_state) const {
    // Default to (0, 0, 0) granularity in case we can't find the real granularity for the physical device.
    VkExtent3D granularity = {0, 0, 0};
    const VkFormat image_format = image_state.createInfo.format;
    const auto pool = cb_state.command_pool;
    if (pool) {
        granularity = physical_device_state->queue_family_properties[pool->queueFamilyIndex].minImageTransferGranularity;
        if (vkuFormatIsBlockedImage(image_format)) {
            auto block_size = vkuFormatTexelBlockExtent(image_format);
            granularity.width *= block_size.width;
            granularity.height *= block_size.height;
        }
    }
    return granularity;
}

// Test elements of a VkExtent3D structure against alignment constraints contained in another VkExtent3D structure
static inline bool IsExtentAligned(const VkExtent3D &extent, const VkExtent3D &granularity) {
    bool valid = true;
    if ((SafeModulo(extent.depth, granularity.depth) != 0) || (SafeModulo(extent.width, granularity.width) != 0) ||
        (SafeModulo(extent.height, granularity.height) != 0)) {
        valid = false;
    }
    return valid;
}

// Check elements of a VkOffset3D structure against a queue family's Image Transfer Granularity values
bool CoreChecks::CheckItgOffset(const LogObjectList &objlist, const VkOffset3D &offset, const VkExtent3D &granularity,
                                const Location &offset_loc, const char *vuid) const {
    bool skip = false;
    VkExtent3D offset_extent = {};
    offset_extent.width = static_cast<uint32_t>(abs(offset.x));
    offset_extent.height = static_cast<uint32_t>(abs(offset.y));
    offset_extent.depth = static_cast<uint32_t>(abs(offset.z));
    if (IsExtentAllZeroes(granularity)) {
        // If the queue family image transfer granularity is (0, 0, 0), then the offset must always be (0, 0, 0)
        if (IsExtentAllZeroes(offset_extent) == false) {
            skip |= LogError(vuid, objlist, offset_loc,
                             "(x=%" PRId32 ", y=%" PRId32 ", z=%" PRId32
                             ") must be (x=0, y=0, z=0) when the command buffer's queue family "
                             "image transfer granularity is (w=0, h=0, d=0).",
                             offset.x, offset.y, offset.z);
        }
    } else {
        // If the queue family image transfer granularity is not (0, 0, 0), then the offset dimensions must always be even
        // integer multiples of the image transfer granularity.
        if (IsExtentAligned(offset_extent, granularity) == false) {
            skip |= LogError(vuid, objlist, offset_loc,
                             "(x=%" PRId32 ", y=%" PRId32 ", z=%" PRId32
                             ") dimensions must be even integer multiples of this command "
                             "buffer's queue family image transfer granularity (w=%" PRIu32 ", h=%" PRIu32 ", d=%" PRIu32 ").",
                             offset.x, offset.y, offset.z, granularity.width, granularity.height, granularity.depth);
        }
    }
    return skip;
}

// Test if two VkExtent3D structs are equivalent
static inline bool IsExtentEqual(const VkExtent3D &extent, const VkExtent3D &other_extent) {
    bool result = true;
    if ((extent.width != other_extent.width) || (extent.height != other_extent.height) || (extent.depth != other_extent.depth)) {
        result = false;
    }
    return result;
}

// Check elements of a VkExtent3D structure against a queue family's Image Transfer Granularity values
bool CoreChecks::CheckItgExtent(const LogObjectList &objlist, const VkExtent3D &extent, const VkOffset3D &offset,
                                const VkExtent3D &granularity, const VkExtent3D &subresource_extent, const VkImageType image_type,
                                const Location &extent_loc, const char *vuid) const {
    bool skip = false;
    if (IsExtentAllZeroes(granularity)) {
        // If the queue family image transfer granularity is (0, 0, 0), then the extent must always match the image
        // subresource extent.
        if (IsExtentEqual(extent, subresource_extent) == false) {
            skip |= LogError(vuid, objlist, extent_loc,
                             "(w=%" PRIu32 ", h=%" PRIu32 ", d=%" PRIu32 ") must match the image subresource extents (w=%" PRIu32
                             ", h=%" PRIu32 ", d=%" PRIu32
                             ") "
                             "when the command buffer's queue family image transfer granularity is (w=0, h=0, d=0).",
                             extent.width, extent.height, extent.depth, subresource_extent.width, subresource_extent.height,
                             subresource_extent.depth);
        }
    } else {
        // If the queue family image transfer granularity is not (0, 0, 0), then the extent dimensions must always be even
        // integer multiples of the image transfer granularity or the offset + extent dimensions must always match the image
        // subresource extent dimensions.
        VkExtent3D offset_extent_sum = {};
        offset_extent_sum.width = static_cast<uint32_t>(abs(offset.x)) + extent.width;
        offset_extent_sum.height = static_cast<uint32_t>(abs(offset.y)) + extent.height;
        offset_extent_sum.depth = static_cast<uint32_t>(abs(offset.z)) + extent.depth;
        bool x_ok = true;
        bool y_ok = true;
        bool z_ok = true;
        switch (image_type) {
            case VK_IMAGE_TYPE_3D:
                z_ok =
                    ((0 == SafeModulo(extent.depth, granularity.depth)) || (subresource_extent.depth == offset_extent_sum.depth));
                [[fallthrough]];
            case VK_IMAGE_TYPE_2D:
                y_ok = ((0 == SafeModulo(extent.height, granularity.height)) ||
                        (subresource_extent.height == offset_extent_sum.height));
                [[fallthrough]];
            case VK_IMAGE_TYPE_1D:
                x_ok =
                    ((0 == SafeModulo(extent.width, granularity.width)) || (subresource_extent.width == offset_extent_sum.width));
                break;
            default:
                // Unrecognized or new IMAGE_TYPE enums will be caught in parameter_validation
                assert(false);
        }
        if (!(x_ok && y_ok && z_ok)) {
            skip |= LogError(vuid, objlist, extent_loc,
                             "(w=%" PRIu32 ", h=%" PRIu32 ", d=%" PRIu32
                             ") dimensions must be even integer multiples of this command "
                             "buffer's queue family image transfer granularity (w=%" PRIu32 ", h=%" PRIu32 ", d=%" PRIu32
                             ") or offset (x=%" PRId32 ", y=%" PRId32 ", z=%" PRId32
                             ") + "
                             "extent (w=%" PRIu32 ", h=%" PRIu32 ", d=%" PRIu32
                             ") must match the image subresource extents (w=%" PRIu32 ", h=%" PRIu32 ", d=%" PRIu32 ").",
                             extent.width, extent.height, extent.depth, granularity.width, granularity.height, granularity.depth,
                             offset.x, offset.y, offset.z, extent.width, extent.height, extent.depth, subresource_extent.width,
                             subresource_extent.height, subresource_extent.depth);
        }
    }
    return skip;
}
template <typename HandleT>
bool CoreChecks::ValidateImageMipLevel(const HandleT handle, const vvl::Image &image_state, uint32_t mip_level,
                                       const Location &mip_loc, const char *vuid) const {
    bool skip = false;
    if (mip_level >= image_state.createInfo.mipLevels) {
        const LogObjectList objlist(handle, image_state.Handle());
        skip |= LogError(vuid, objlist, mip_loc, "is %" PRIu32 ", but provided %s has %" PRIu32 " mip levels.", mip_level,
                         FormatHandle(image_state).c_str(), image_state.createInfo.mipLevels);
    }
    return skip;
}
template <typename HandleT>
bool CoreChecks::ValidateImageArrayLayerRange(const HandleT handle, const vvl::Image &img, const uint32_t base_layer,
                                              const uint32_t layer_count, const Location &subresource_loc, const char *vuid) const {
    bool skip = false;
    if (base_layer >= img.createInfo.arrayLayers || layer_count > img.createInfo.arrayLayers ||
        (base_layer + layer_count) > img.createInfo.arrayLayers) {
        if (layer_count != VK_REMAINING_ARRAY_LAYERS) {
            const LogObjectList objlist(handle, img.Handle());
            skip |= LogError(vuid, objlist, subresource_loc.dot(Field::baseArrayLayer),
                             "is %" PRIu32 " and layerCount is %" PRIu32 ", but provided %s has %" PRIu32 " array layers.",
                             base_layer, layer_count, FormatHandle(img).c_str(), img.createInfo.arrayLayers);
        }
    }
    return skip;
}

// All VUID from copy_bufferimage_to_imagebuffer_common.txt with more as a result of host_image_copy
static const char *GetBufferMemoryImageCopyCommandVUID(const std::string &id, bool from_image, bool copy2, bool is_memory = false) {
    // clang-format off
    static const std::map<std::string, std::array<const char *, 6>> copy_imagebuffermemory_vuid = {
        {"06659", {
            "VUID-VkBufferImageCopy-imageExtent-06659",      // !copy2  & !from_image
            "VUID-VkBufferImageCopy-imageExtent-06659",      // !copy2  &  from_image
            "VUID-VkBufferImageCopy2-imageExtent-06659",     //  copy2  & !from_image
            "VUID-VkBufferImageCopy2-imageExtent-06659",     //  copy2  &  from_image
            "VUID-VkMemoryToImageCopyEXT-imageExtent-06659", //  memory & !from_image
            "VUID-VkImageToMemoryCopyEXT-imageExtent-06659", //  memory &  from_image
        }},
        {"06660", {
            "VUID-VkBufferImageCopy-imageExtent-06660",
            "VUID-VkBufferImageCopy-imageExtent-06660",
            "VUID-VkBufferImageCopy2-imageExtent-06660",
            "VUID-VkBufferImageCopy2-imageExtent-06660",
            "VUID-VkMemoryToImageCopyEXT-imageExtent-06660",
            "VUID-VkImageToMemoryCopyEXT-imageExtent-06660",
        }},
        {"06661", {
            "VUID-VkBufferImageCopy-imageExtent-06661",
            "VUID-VkBufferImageCopy-imageExtent-06661",
            "VUID-VkBufferImageCopy2-imageExtent-06661",
            "VUID-VkBufferImageCopy2-imageExtent-06661",
            "VUID-VkMemoryToImageCopyEXT-imageExtent-06661",
            "VUID-VkImageToMemoryCopyEXT-imageExtent-06661",
        }},
        {"09101", {
            "VUID-VkBufferImageCopy-bufferRowLength-09101",
            "VUID-VkBufferImageCopy-bufferRowLength-09101",
            "VUID-VkBufferImageCopy2-bufferRowLength-09101",
            "VUID-VkBufferImageCopy2-bufferRowLength-09101",
            "VUID-VkMemoryToImageCopyEXT-memoryRowLength-09101",
            "VUID-VkImageToMemoryCopyEXT-memoryRowLength-09101",
        }},
        {"00196", {
            "VUID-VkBufferImageCopy-bufferImageHeight-09102",
            "VUID-VkBufferImageCopy-bufferImageHeight-09102",
            "VUID-VkBufferImageCopy2-bufferImageHeight-09102",
            "VUID-VkBufferImageCopy2-bufferImageHeight-09102",
            "VUID-VkMemoryToImageCopyEXT-memoryImageHeight-09102",
            "VUID-VkImageToMemoryCopyEXT-memoryImageHeight-09102",
        }},
        {"09103", {
            "VUID-VkBufferImageCopy-aspectMask-09103",
            "VUID-VkBufferImageCopy-aspectMask-09103",
            "VUID-VkBufferImageCopy2-aspectMask-09103",
            "VUID-VkBufferImageCopy2-aspectMask-09103",
            "VUID-VkMemoryToImageCopyEXT-aspectMask-09103",
            "VUID-VkImageToMemoryCopyEXT-aspectMask-09103",
        }},
        {"07975", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07975",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07975",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07975",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07975",
            kVUIDUndefined,
            kVUIDUndefined,
        }},
        {"07976", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07976",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07976",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07976",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07976",
            kVUIDUndefined,
            kVUIDUndefined,
        }},
        {"00197", {
            "VUID-vkCmdCopyBufferToImage-imageSubresource-07971",
            "VUID-vkCmdCopyImageToBuffer-imageSubresource-07971",
            "VUID-VkCopyBufferToImageInfo2-pRegions-06223",
            "VUID-VkCopyImageToBufferInfo2-imageOffset-00197",
            "VUID-VkCopyMemoryToImageInfoEXT-imageSubresource-07971",
            "VUID-VkCopyImageToMemoryInfoEXT-imageSubresource-07971",
        }},
        {"00198", {
            "VUID-vkCmdCopyBufferToImage-imageSubresource-07972",
            "VUID-vkCmdCopyImageToBuffer-imageSubresource-07972",
            "VUID-VkCopyBufferToImageInfo2-pRegions-06224",
            "VUID-VkCopyImageToBufferInfo2-imageOffset-00198",
            "VUID-VkCopyMemoryToImageInfoEXT-imageSubresource-07972",
            "VUID-VkCopyImageToMemoryInfoEXT-imageSubresource-07972",
        }},
        {"07979", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07979",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07979",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07979",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07979",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07979",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07979",
        }},
        {"09104", {
            "VUID-vkCmdCopyBufferToImage-imageOffset-09104",
            "VUID-vkCmdCopyImageToBuffer-imageOffset-09104",
            "VUID-VkCopyBufferToImageInfo2-imageOffset-09104",
            "VUID-VkCopyImageToBufferInfo2-imageOffset-09104",
            "VUID-VkCopyMemoryToImageInfoEXT-imageOffset-09104",
            "VUID-VkCopyImageToMemoryInfoEXT-imageOffset-09104",
        }},
        {"07980", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07980",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07980",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07980",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07980",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07980",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07980",
        }},
        {"09106", {
            "VUID-vkCmdCopyBufferToImage-bufferRowLength-09106",
            "VUID-vkCmdCopyImageToBuffer-bufferRowLength-09106",
            "VUID-VkCopyBufferToImageInfo2-bufferRowLength-09106",
            "VUID-VkCopyImageToBufferInfo2-bufferRowLength-09106",
            "VUID-VkCopyMemoryToImageInfoEXT-memoryRowLength-09106",
            "VUID-VkCopyImageToMemoryInfoEXT-memoryRowLength-09106",
        }},
        {"09107", {
            "VUID-vkCmdCopyBufferToImage-bufferImageHeight-09107",
            "VUID-vkCmdCopyImageToBuffer-bufferImageHeight-09107",
            "VUID-VkCopyBufferToImageInfo2-bufferImageHeight-09107",
            "VUID-VkCopyImageToBufferInfo2-bufferImageHeight-09107",
            "VUID-VkCopyMemoryToImageInfoEXT-memoryImageHeight-09107",
            "VUID-VkCopyImageToMemoryInfoEXT-memoryImageHeight-09107",
        }},
        {"07274", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07274",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07274",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07274",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07274",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07274",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07274",
        }},
        {"07275", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07275",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07275",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07275",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07275",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07275",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07275",
        }},
        {"07276", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07276",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07276",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07276",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07276",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07276",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07276",
        }},
        {"09108", {
            "VUID-vkCmdCopyBufferToImage-bufferRowLength-09108",
            "VUID-vkCmdCopyImageToBuffer-bufferRowLength-09108",
            "VUID-VkCopyBufferToImageInfo2-bufferRowLength-09108",
            "VUID-VkCopyImageToBufferInfo2-bufferRowLength-09108",
            "VUID-VkCopyMemoryToImageInfoEXT-memoryRowLength-09108",
            "VUID-VkCopyImageToMemoryInfoEXT-memoryRowLength-09108",
        }},
        {"00207", {
            "VUID-vkCmdCopyBufferToImage-dstImage-00207",
            "VUID-vkCmdCopyImageToBuffer-srcImage-00207",
            "VUID-VkCopyBufferToImageInfo2-dstImage-00207",
            "VUID-VkCopyImageToBufferInfo2-srcImage-00207",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-00207",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-00207",
        }},
        {"00208", {
            "VUID-vkCmdCopyBufferToImage-dstImage-00208",
            "VUID-vkCmdCopyImageToBuffer-srcImage-00208",
            "VUID-VkCopyBufferToImageInfo2-dstImage-00208",
            "VUID-VkCopyImageToBufferInfo2-srcImage-00208",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-00208",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-00208",
        }},
        {"00209", {
            "VUID-vkCmdCopyBufferToImage-dstImage-00209",
            "VUID-vkCmdCopyImageToBuffer-srcImage-00209",
            "VUID-VkCopyBufferToImageInfo2-dstImage-00209",
            "VUID-VkCopyImageToBufferInfo2-srcImage-00209",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-00209",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-00209",
        }},
        {"09105", {
            "VUID-vkCmdCopyBufferToImage-imageSubresource-09105",
            "VUID-vkCmdCopyImageToBuffer-imageSubresource-09105",
            "VUID-VkCopyBufferToImageInfo2-imageSubresource-09105",
            "VUID-VkCopyImageToBufferInfo2-imageSubresource-09105",
            "VUID-VkCopyMemoryToImageInfoEXT-imageSubresource-09105",
            "VUID-VkCopyImageToMemoryInfoEXT-imageSubresource-09105",
        }},
        {"07981", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07981",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07981",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07981",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07981",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07981",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07981",
        }},
        {"07983", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07983",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07983",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07983",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07983",
            "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07983",
            "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07983",
        }},
        {"04052", {
            // was split up in 1.3.236 spec (internal MR 5371)
            "VUID-vkCmdCopyBufferToImage-commandBuffer-07737",
            "VUID-vkCmdCopyImageToBuffer-commandBuffer-07746",
            "VUID-vkCmdCopyBufferToImage2-commandBuffer-07737",
            "VUID-vkCmdCopyImageToBuffer2-commandBuffer-07746",
            kVUIDUndefined,
            kVUIDUndefined,
        }},
        {"07978", {
            "VUID-vkCmdCopyBufferToImage-dstImage-07978",
            "VUID-vkCmdCopyImageToBuffer-srcImage-07978",
            "VUID-VkCopyBufferToImageInfo2-dstImage-07978",
            "VUID-VkCopyImageToBufferInfo2-srcImage-07978",
            kVUIDUndefined,
            kVUIDUndefined,
        }},
        {"00173", {
            "VUID-vkCmdCopyBufferToImage-pRegions-00173",
            "VUID-vkCmdCopyImageToBuffer-pRegions-00184",
            "VUID-VkCopyBufferToImageInfo2-pRegions-00173",
            "VUID-VkCopyImageToBufferInfo2-pRegions-00184",
            kVUIDUndefined,
            kVUIDUndefined,
        }}
    };
    // clang-format on

    uint8_t index = 0;
    if (is_memory) {
        index = 4 + (from_image ? 1 : 0);
    } else {
        index |= uint8_t((from_image) ? 0x1 : 0);
        index |= uint8_t((copy2) ? 0x2 : 0);
    }
    return copy_imagebuffermemory_vuid.at(id).at(index);
}

bool VerifyAspectsPresent(VkImageAspectFlags aspect_mask, VkFormat format) {
    if ((aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0) {
        if (!(vkuFormatIsColor(format) || vkuFormatIsMultiplane(format))) return false;
    }
    if ((aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) {
        if (!vkuFormatHasDepth(format)) return false;
    }
    if ((aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) {
        if (!vkuFormatHasStencil(format)) return false;
    }
    if (0 != (aspect_mask & (VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT | VK_IMAGE_ASPECT_PLANE_2_BIT))) {
        if (vkuFormatPlaneCount(format) == 1) return false;
    }
    return true;
}

template <typename T>
uint32_t GetRowLength(T data) {
    return data.bufferRowLength;
}
template <>
uint32_t GetRowLength<VkMemoryToImageCopyEXT>(VkMemoryToImageCopyEXT data) {
    return data.memoryRowLength;
}
template <>
uint32_t GetRowLength<VkImageToMemoryCopyEXT>(VkImageToMemoryCopyEXT data) {
    return data.memoryRowLength;
}
template <typename T>
uint32_t GetImageHeight(T data) {
    return data.bufferImageHeight;
}
template <>
uint32_t GetImageHeight<VkMemoryToImageCopyEXT>(VkMemoryToImageCopyEXT data) {
    return data.memoryImageHeight;
}
template <>
uint32_t GetImageHeight<VkImageToMemoryCopyEXT>(VkImageToMemoryCopyEXT data) {
    return data.memoryImageHeight;
}
template <typename HandleT, typename RegionType>
bool CoreChecks::ValidateHeterogeneousCopyData(const HandleT handle, uint32_t regionCount, const RegionType *pRegions,
                                               const vvl::Image &image_state, const Location &loc) const {
    bool skip = false;
    const bool is_2 = IsValueIn(loc.function, {Func::vkCmdCopyBufferToImage2, Func::vkCmdCopyBufferToImage2KHR});
    const bool from_image = IsValueIn(loc.function, {Func::vkCmdCopyImageToBuffer, Func::vkCmdCopyImageToBuffer2,
                                                     Func::vkCmdCopyImageToBuffer2KHR, Func::vkCopyImageToMemoryEXT});
    const bool is_memory = IsValueIn(loc.function, {Func::vkCopyMemoryToImageEXT, Func::vkCopyImageToMemoryEXT});

    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const Location subresource_loc = region_loc.dot(Field::imageSubresource);
        const RegionType region = pRegions[i];
        const VkImageAspectFlags region_aspect_mask = region.imageSubresource.aspectMask;
        if (image_state.createInfo.imageType == VK_IMAGE_TYPE_1D) {
            if ((region.imageOffset.y != 0) || (region.imageExtent.height != 1)) {
                const LogObjectList objlist(handle, image_state.image());
                skip |= LogError(GetBufferMemoryImageCopyCommandVUID("07979", from_image, is_2, is_memory), objlist, region_loc,
                                 "imageOffset.y is %" PRId32 " and imageExtent.height is %" PRIu32
                                 ". For 1D images these must be 0 "
                                 "and 1, respectively.",
                                 region.imageOffset.y, region.imageExtent.height);
            }
        }

        if ((image_state.createInfo.imageType == VK_IMAGE_TYPE_1D) || (image_state.createInfo.imageType == VK_IMAGE_TYPE_2D)) {
            if ((region.imageOffset.z != 0) || (region.imageExtent.depth != 1)) {
                const LogObjectList objlist(handle, image_state.image());
                skip |= LogError(GetBufferMemoryImageCopyCommandVUID("07980", from_image, is_2, is_memory), objlist, region_loc,
                                 "imageOffset.z is %" PRId32 " and imageExtent.depth is %" PRIu32
                                 ". For 1D and 2D images these "
                                 "must be 0 and 1, respectively.",
                                 region.imageOffset.z, region.imageExtent.depth);
            }
        }

        if (image_state.createInfo.imageType == VK_IMAGE_TYPE_3D) {
            if ((0 != region.imageSubresource.baseArrayLayer) || (1 != region.imageSubresource.layerCount)) {
                const LogObjectList objlist(handle, image_state.image());
                skip |=
                    LogError(GetBufferMemoryImageCopyCommandVUID("07983", from_image, is_2, is_memory), objlist, subresource_loc,
                             "baseArrayLayer is %" PRIu32 " and layerCount is %" PRIu32
                             ". "
                             "For 3D images these must be 0 and 1, respectively.",
                             region.imageSubresource.baseArrayLayer, region.imageSubresource.layerCount);
            }
        }

        // Make sure not a empty region
        if (region.imageExtent.width == 0) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("06659", from_image, is_2, is_memory), objlist,
                             region_loc.dot(Field::imageExtent).dot(Field::width), "is zero (empty copies are not allowed).");
        }
        if (region.imageExtent.height == 0) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("06660", from_image, is_2, is_memory), objlist,
                             region_loc.dot(Field::imageExtent).dot(Field::height), "is zero (empty copies are not allowed).");
        }
        if (region.imageExtent.depth == 0) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("06661", from_image, is_2, is_memory), objlist,
                             region_loc.dot(Field::imageExtent).dot(Field::depth), "is zero (empty copies are not allowed).");
        }

        //  BufferRowLength must be 0, or greater than or equal to the width member of imageExtent
        uint32_t row_length = GetRowLength(region);
        if ((row_length != 0) && (row_length < region.imageExtent.width)) {
            const LogObjectList objlist(handle, image_state.image());
            Field field = is_memory ? Field::memoryRowLength : Field::bufferRowLength;
            skip |=
                LogError(GetBufferMemoryImageCopyCommandVUID("09101", from_image, is_2, is_memory), objlist, region_loc.dot(field),
                         "(%" PRIu32 ") must be zero or greater-than-or-equal-to imageExtent.width (%" PRIu32 ").", row_length,
                         region.imageExtent.width);
        }

        //  BufferImageHeight must be 0, or greater than or equal to the height member of imageExtent
        uint32_t image_height = GetImageHeight(region);
        if ((image_height != 0) && (image_height < region.imageExtent.height)) {
            const LogObjectList objlist(handle, image_state.image());
            Field field = is_memory ? Field::memoryImageHeight : Field::bufferImageHeight;
            skip |=
                LogError(GetBufferMemoryImageCopyCommandVUID("00196", from_image, is_2, is_memory), objlist, region_loc.dot(field),
                         "(%" PRIu32 ") must be zero or greater-than-or-equal-to imageExtent.height (%" PRIu32 ").", image_height,
                         region.imageExtent.height);
        }

        // subresource aspectMask must have exactly 1 bit set
        if (GetBitSetCount(region_aspect_mask) != 1) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("09103", from_image, is_2, is_memory), objlist,
                             subresource_loc.dot(Field::aspectMask), "is %s (only one bit allowed).",
                             string_VkImageAspectFlags(region_aspect_mask).c_str());
        }

        // Calculate adjusted image extent, accounting for multiplane image factors
        VkExtent3D adjusted_image_extent = image_state.GetEffectiveSubresourceExtent(region.imageSubresource);
        // imageOffset.x and (imageExtent.width + imageOffset.x) must both be >= 0 and <= image subresource width
        if ((region.imageOffset.x < 0) || (region.imageOffset.x > static_cast<int32_t>(adjusted_image_extent.width)) ||
            ((region.imageOffset.x + static_cast<int32_t>(region.imageExtent.width)) >
             static_cast<int32_t>(adjusted_image_extent.width))) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("00197", from_image, is_2, is_memory), objlist, region_loc,
                             "imageOffset.x (%" PRId32 ") and (imageExtent.width + imageOffset.x) (%" PRIu32
                             ") must be >= "
                             "zero or <= image subresource width (%" PRIu32 ").",
                             region.imageOffset.x, (region.imageOffset.x + region.imageExtent.width), adjusted_image_extent.width);
        }

        // imageOffset.y and (imageExtent.height + imageOffset.y) must both be >= 0 and <= image subresource height
        if ((region.imageOffset.y < 0) || (region.imageOffset.y > static_cast<int32_t>(adjusted_image_extent.height)) ||
            ((region.imageOffset.y + static_cast<int32_t>(region.imageExtent.height)) >
             static_cast<int32_t>(adjusted_image_extent.height))) {
            const LogObjectList objlist(handle, image_state.image());
            skip |=
                LogError(GetBufferMemoryImageCopyCommandVUID("00198", from_image, is_2, is_memory), objlist, region_loc,
                         "imageOffset.y (%" PRId32 ") and (imageExtent.height + imageOffset.y) (%" PRIu32
                         ") must be >= "
                         "zero or <= image subresource height (%" PRIu32 ").",
                         region.imageOffset.y, (region.imageOffset.y + region.imageExtent.height), adjusted_image_extent.height);
        }

        // imageOffset.z and (imageExtent.depth + imageOffset.z) must both be >= 0 and <= image subresource depth
        if ((region.imageOffset.z < 0) || (region.imageOffset.z > static_cast<int32_t>(adjusted_image_extent.depth)) ||
            ((region.imageOffset.z + static_cast<int32_t>(region.imageExtent.depth)) >
             static_cast<int32_t>(adjusted_image_extent.depth))) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("09104", from_image, is_2, is_memory), objlist, region_loc,
                             "imageOffset.z (%" PRId32 ") and (imageExtent.depth + imageOffset.z) (%" PRIu32
                             ") must be >= "
                             "zero or <= image subresource depth (%" PRIu32 ").",
                             region.imageOffset.z, (region.imageOffset.z + region.imageExtent.depth), adjusted_image_extent.depth);
        }

        const VkFormat image_format = image_state.createInfo.format;
        // image subresource aspect bit must match format
        if (!VerifyAspectsPresent(region_aspect_mask, image_format)) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("09105", from_image, is_2, is_memory), objlist,
                             subresource_loc.dot(Field::aspectMask), "%s invalid for image format %s.",
                             string_VkImageAspectFlags(region_aspect_mask).c_str(), string_VkFormat(image_format));
        }

        auto block_size = vkuFormatTexelBlockExtent(image_format);
        //  BufferRowLength must be a multiple of block width
        if (SafeModulo(row_length, block_size.width) != 0) {
            const LogObjectList objlist(handle, image_state.image());
            Field field = is_memory ? Field::memoryRowLength : Field::bufferRowLength;
            skip |= LogError(
                GetBufferMemoryImageCopyCommandVUID("09106", from_image, is_2, is_memory), objlist, region_loc.dot(field),
                "(%" PRIu32 ") must be a multiple of the blocked image's texel width (%" PRIu32 ").", row_length, block_size.width);
        }

        //  BufferRowHeight must be a multiple of block height
        if (SafeModulo(image_height, block_size.height) != 0) {
            const LogObjectList objlist(handle, image_state.image());
            Field field = is_memory ? Field::memoryImageHeight : Field::bufferImageHeight;
            skip |=
                LogError(GetBufferMemoryImageCopyCommandVUID("09107", from_image, is_2, is_memory), objlist, region_loc.dot(field),
                         "(%" PRIu32 ") must be a multiple of the blocked image's texel height (%" PRIu32 ").", image_height,
                         block_size.height);
        }

        //  image offsets x must be multiple of block width
        if (SafeModulo(region.imageOffset.x, block_size.width) != 0) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("07274", from_image, is_2, is_memory), objlist,
                             region_loc.dot(Field::imageOffset).dot(Field::x),
                             "(%" PRId32
                             ") must be a multiple of the blocked image's texel "
                             "width (%" PRIu32 ").",
                             region.imageOffset.x, block_size.width);
        }

        //  image offsets y must be multiple of block height
        if (SafeModulo(region.imageOffset.y, block_size.height) != 0) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("07275", from_image, is_2, is_memory), objlist,
                             region_loc.dot(Field::imageOffset).dot(Field::y),
                             "(%" PRId32
                             ") must be a multiple of the blocked image's texel "
                             "height (%" PRIu32 ").",
                             region.imageOffset.y, block_size.height);
        }

        //  image offsets z must be multiple of block depth
        if (SafeModulo(region.imageOffset.z, block_size.depth) != 0) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("07276", from_image, is_2, is_memory), objlist,
                             region_loc.dot(Field::imageOffset).dot(Field::z),
                             "(%" PRId32
                             ") must be a multiple of the blocked image's texel "
                             "depth (%" PRIu32 ").",
                             region.imageOffset.z, block_size.depth);
        }

        // imageExtent width must be a multiple of block width, or extent+offset width must equal subresource width
        VkExtent3D mip_extent = image_state.GetEffectiveSubresourceExtent(region.imageSubresource);
        if ((SafeModulo(region.imageExtent.width, block_size.width) != 0) &&
            (region.imageExtent.width + region.imageOffset.x != mip_extent.width)) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("00207", from_image, is_2, is_memory), objlist,
                             region_loc.dot(Field::imageExtent).dot(Field::width),
                             "(%" PRIu32
                             ") must be a multiple of the blocked texture block width "
                             "(%" PRIu32 "), or when added to imageOffset.x (%" PRId32
                             ") must equal the image subresource width (%" PRIu32 ").",
                             region.imageExtent.width, block_size.width, region.imageOffset.x, mip_extent.width);
        }

        // imageExtent height must be a multiple of block height, or extent+offset height must equal subresource height
        if ((SafeModulo(region.imageExtent.height, block_size.height) != 0) &&
            (region.imageExtent.height + region.imageOffset.y != mip_extent.height)) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("00208", from_image, is_2, is_memory), objlist,
                             region_loc.dot(Field::imageExtent).dot(Field::height),
                             "(%" PRIu32
                             ") must be a multiple of the blocked texture block height "
                             "(%" PRIu32 "), or when added to imageOffset.y (%" PRId32
                             ") must equal the image subresource height (%" PRIu32 ").",
                             region.imageExtent.height, block_size.height, region.imageOffset.y, mip_extent.height);
        }

        // imageExtent depth must be a multiple of block depth, or extent+offset depth must equal subresource depth
        if ((SafeModulo(region.imageExtent.depth, block_size.depth) != 0) &&
            (region.imageExtent.depth + region.imageOffset.z != mip_extent.depth)) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("00209", from_image, is_2, is_memory), objlist,
                             region_loc.dot(Field::imageExtent).dot(Field::depth),
                             "(%" PRIu32
                             ") must be a multiple of the blocked texture block depth "
                             "(%" PRIu32 "), or when added to imageOffset.z (%" PRId32
                             ") must equal the image subresource depth (%" PRIu32 ").",
                             region.imageExtent.depth, block_size.depth, region.imageOffset.z, mip_extent.depth);
        }

        // *RowLength divided by the texel block extent width and then multiplied by the texel block size of the image must be
        // less than or equal to 2^31-1
        const uint32_t element_size =
            vkuFormatIsDepthOrStencil(image_format)
                ? 0
                : vkuFormatElementSizeWithAspect(image_format, static_cast<VkImageAspectFlagBits>(region_aspect_mask));
        double test_value = row_length / block_size.width;
        test_value = test_value * element_size;
        const auto two_to_31_minus_1 = static_cast<double>((1u << 31) - 1);
        if (test_value > two_to_31_minus_1) {
            const LogObjectList objlist(handle, image_state.image());
            Field field = is_memory ? Field::memoryRowLength : Field::bufferRowLength;
            skip |=
                LogError(GetBufferMemoryImageCopyCommandVUID("09108", from_image, is_2, is_memory), objlist, region_loc.dot(field),
                         "(%" PRIu32 ") divided by the texel block extent width (%" PRIu32
                         ") then multiplied by the "
                         "texel block size of image (%" PRIu32 ") is (%" PRIu64 ") which is greater than 2^31 - 1",
                         row_length, block_size.width, element_size, static_cast<uint64_t>(test_value));
        }

        // Checks that apply only to multi-planar format images
        if (vkuFormatIsMultiplane(image_format) && !IsOnlyOneValidPlaneAspect(image_format, region_aspect_mask)) {
            const LogObjectList objlist(handle, image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("07981", from_image, is_2, is_memory), objlist,
                             subresource_loc.dot(Field::aspectMask), "(%s) is invalid for multi-planar format %s.",
                             string_VkImageAspectFlags(region_aspect_mask).c_str(), string_VkFormat(image_format));
        }
    }
    return skip;
}

template <typename RegionType>
bool CoreChecks::ValidateBufferImageCopyData(const vvl::CommandBuffer &cb_state, uint32_t regionCount, const RegionType *pRegions,
                                             const vvl::Image &image_state, const Location &loc) const {
    bool skip = false;

    const bool is_2 = IsValueIn(loc.function, {Func::vkCmdCopyBufferToImage2, Func::vkCmdCopyBufferToImage2KHR});
    const bool image_to_buffer =
        IsValueIn(loc.function, {Func::vkCmdCopyImageToBuffer, Func::vkCmdCopyImageToBuffer2, Func::vkCmdCopyImageToBuffer2KHR});

    const VkFormat image_format = image_state.createInfo.format;

    skip |= ValidateHeterogeneousCopyData(cb_state.commandBuffer(), regionCount, pRegions, image_state, loc);

    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const RegionType region = pRegions[i];
        const VkImageAspectFlags region_aspect_mask = region.imageSubresource.aspectMask;

        // If the the calling command's VkImage parameter's format is not a depth/stencil format,
        // then bufferOffset must be a multiple of the calling command's VkImage parameter's element size
        const uint32_t element_size =
            vkuFormatIsDepthOrStencil(image_format)
                ? 0
                : vkuFormatElementSizeWithAspect(image_format, static_cast<VkImageAspectFlagBits>(region_aspect_mask));
        const VkDeviceSize bufferOffset = region.bufferOffset;

        if (vkuFormatIsDepthOrStencil(image_format)) {
            if (SafeModulo(bufferOffset, 4) != 0) {
                const LogObjectList objlist(cb_state.commandBuffer(), image_state.image());
                skip |= LogError(GetBufferMemoryImageCopyCommandVUID("07978", image_to_buffer, is_2), objlist,
                                 region_loc.dot(Field::bufferOffset),
                                 "(%" PRIu64 ") must be a multiple 4 if using a depth/stencil format (%s).", bufferOffset,
                                 string_VkFormat(image_format));
            }
        } else {
            // If not depth/stencil and not multi-plane
            if (!vkuFormatIsMultiplane(image_format) && (SafeModulo(bufferOffset, element_size) != 0)) {
                const LogObjectList objlist(cb_state.commandBuffer(), image_state.image());
                skip |= LogError(GetBufferMemoryImageCopyCommandVUID("07975", image_to_buffer, is_2), objlist,
                                 region_loc.dot(Field::bufferOffset),
                                 "(%" PRIu64 ") must be a multiple of %s texel size (%" PRIu32 ").", bufferOffset,
                                 string_VkFormat(image_format), element_size);
            }
        }

        // Checks that apply only to multi-planar format images
        if (vkuFormatIsMultiplane(image_format)) {
            // image subresource aspectMask must be VK_IMAGE_ASPECT_PLANE_*_BIT
            if (0 !=
                (region_aspect_mask & (VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT | VK_IMAGE_ASPECT_PLANE_2_BIT))) {
                // Know aspect mask is valid
                const VkFormat compatible_format =
                    vkuFindMultiplaneCompatibleFormat(image_format, static_cast<VkImageAspectFlagBits>(region_aspect_mask));
                const uint32_t compatible_size = vkuFormatElementSize(compatible_format);
                if (SafeModulo(bufferOffset, compatible_size) != 0) {
                    const LogObjectList objlist(cb_state.commandBuffer(), image_state.image());
                    skip |= LogError(GetBufferMemoryImageCopyCommandVUID("07976", image_to_buffer, is_2), objlist,
                                     region_loc.dot(Field::bufferOffset),
                                     "(%" PRIu64 ") is not a multiple of %s texel size (%" PRIu32 ") for plane %" PRIu32 " (%s).",
                                     bufferOffset, string_VkFormat(image_format), element_size,
                                     vkuGetPlaneIndex(static_cast<VkImageAspectFlagBits>(region_aspect_mask)),
                                     string_VkFormat(compatible_format));
                }
            }
        }

        if (SafeModulo(bufferOffset, 4) != 0) {
            std::stringstream ss;
            ss << "(" << bufferOffset << ") is not a multiple of 4, but is ";
            const char *vuid = GetBufferMemoryImageCopyCommandVUID("04052", image_to_buffer, is_2);
            skip |= ValidateCmdQueueFlags(cb_state, region_loc.dot(Field::bufferOffset),
                                          VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, vuid, ss.str().c_str());
        }
    }

    return skip;
}

template <typename RegionType>
bool CoreChecks::ValidateCmdCopyBufferBounds(VkCommandBuffer cb, const vvl::Buffer &src_buffer_state,
                                             const vvl::Buffer &dst_buffer_state, uint32_t regionCount, const RegionType *pRegions,
                                             const Location &loc) const {
    bool skip = false;
    const bool is_2 = loc.function == Func::vkCmdCopyBuffer2 || loc.function == Func::vkCmdCopyBuffer2KHR;
    const char *vuid;

    VkDeviceSize src_buffer_size = src_buffer_state.createInfo.size;
    VkDeviceSize dst_buffer_size = dst_buffer_state.createInfo.size;
    const bool are_buffers_sparse = src_buffer_state.sparse || dst_buffer_state.sparse;

    const LogObjectList src_objlist(cb, dst_buffer_state.Handle());
    const LogObjectList dst_objlist(cb, dst_buffer_state.Handle());
    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const RegionType region = pRegions[i];

        // The srcOffset member of each element of pRegions must be less than the size of srcBuffer
        if (region.srcOffset >= src_buffer_size) {
            vuid = is_2 ? "VUID-VkCopyBufferInfo2-srcOffset-00113" : "VUID-vkCmdCopyBuffer-srcOffset-00113";
            skip |= LogError(vuid, src_objlist, region_loc.dot(Field::srcOffset),
                             "(%" PRIuLEAST64 ") is greater than size of srcBuffer (%" PRIuLEAST64 ").", region.srcOffset,
                             src_buffer_size);
        }

        // The dstOffset member of each element of pRegions must be less than the size of dstBuffer
        if (region.dstOffset >= dst_buffer_size) {
            vuid = is_2 ? "VUID-VkCopyBufferInfo2-dstOffset-00114" : "VUID-vkCmdCopyBuffer-dstOffset-00114";
            skip |= LogError(vuid, dst_objlist, region_loc.dot(Field::dstOffset),
                             "(%" PRIuLEAST64 ") is greater than size of dstBuffer (%" PRIuLEAST64 ").", region.dstOffset,
                             dst_buffer_size);
        }

        // The size member of each element of pRegions must be less than or equal to the size of srcBuffer minus srcOffset
        if (region.size > (src_buffer_size - region.srcOffset)) {
            vuid = is_2 ? "VUID-VkCopyBufferInfo2-size-00115" : "VUID-vkCmdCopyBuffer-size-00115";
            skip |= LogError(vuid, src_objlist, region_loc.dot(Field::size),
                             "(%" PRIuLEAST64 ") is greater than the source buffer size (%" PRIuLEAST64
                             ") minus srcOffset (%" PRIuLEAST64 ").",
                             region.size, src_buffer_size, region.srcOffset);
        }

        // The size member of each element of pRegions must be less than or equal to the size of dstBuffer minus dstOffset
        if (region.size > (dst_buffer_size - region.dstOffset)) {
            vuid = is_2 ? "VUID-VkCopyBufferInfo2-size-00116" : "VUID-vkCmdCopyBuffer-size-00116";
            skip |= LogError(vuid, dst_objlist, region_loc.dot(Field::size),
                             "(%" PRIuLEAST64 ") is greater than the destination buffer size (%" PRIuLEAST64
                             ") minus dstOffset (%" PRIuLEAST64 ").",
                             region.size, dst_buffer_size, region.dstOffset);
        }

        // The union of the source regions, and the union of the destination regions, must not overlap in memory
        if (!skip && !are_buffers_sparse) {
            auto src_region = sparse_container::range<VkDeviceSize>{region.srcOffset, region.srcOffset + region.size};
            for (uint32_t j = 0; j < regionCount; j++) {
                auto dst_region =
                    sparse_container::range<VkDeviceSize>{pRegions[j].dstOffset, pRegions[j].dstOffset + pRegions[j].size};
                if (src_buffer_state.DoesResourceMemoryOverlap(src_region, &dst_buffer_state, dst_region)) {
                    const LogObjectList objlist(cb, src_buffer_state.Handle(), dst_buffer_state.Handle());
                    vuid = is_2 ? "VUID-VkCopyBufferInfo2-pRegions-00117" : "VUID-vkCmdCopyBuffer-pRegions-00117";
                    skip |= LogError(vuid, objlist, region_loc, "Detected overlap between source and dest regions in memory.");
                }
            }
        }
    }

    return skip;
}
template <typename RegionType>
bool CoreChecks::ValidateCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount,
                                       const RegionType *pRegions, const Location &loc) const {
    bool skip = false;
    auto cb_state_ptr = GetRead<vvl::CommandBuffer>(commandBuffer);
    auto src_buffer_state = Get<vvl::Buffer>(srcBuffer);
    auto dst_buffer_state = Get<vvl::Buffer>(dstBuffer);
    if (!cb_state_ptr || !src_buffer_state || !dst_buffer_state) {
        return skip;
    }
    const vvl::CommandBuffer &cb_state = *cb_state_ptr;

    const bool is_2 = loc.function == Func::vkCmdCopyBuffer2 || loc.function == Func::vkCmdCopyBuffer2KHR;
    const char *vuid;
    const Location src_buffer_loc = loc.dot(Field::srcBuffer);
    const Location dst_buffer_loc = loc.dot(Field::dstBuffer);

    vuid = is_2 ? "VUID-VkCopyBufferInfo2-srcBuffer-00119" : "VUID-vkCmdCopyBuffer-srcBuffer-00119";
    skip |= ValidateMemoryIsBoundToBuffer(commandBuffer, *src_buffer_state, src_buffer_loc, vuid);
    vuid = is_2 ? "VUID-VkCopyBufferInfo2-dstBuffer-00121" : "VUID-vkCmdCopyBuffer-dstBuffer-00121";
    skip |= ValidateMemoryIsBoundToBuffer(commandBuffer, *dst_buffer_state, dst_buffer_loc, vuid);

    // Validate that SRC & DST buffers have correct usage flags set
    vuid = is_2 ? "VUID-VkCopyBufferInfo2-srcBuffer-00118" : "VUID-vkCmdCopyBuffer-srcBuffer-00118";
    skip |= ValidateBufferUsageFlags(LogObjectList(commandBuffer, srcBuffer), *src_buffer_state, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
                                     true, vuid, src_buffer_loc);
    vuid = is_2 ? "VUID-VkCopyBufferInfo2-dstBuffer-00120" : "VUID-vkCmdCopyBuffer-dstBuffer-00120";
    skip |= ValidateBufferUsageFlags(LogObjectList(commandBuffer, dstBuffer), *dst_buffer_state, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
                                     true, vuid, dst_buffer_loc);

    skip |= ValidateCmd(cb_state, loc);
    skip |= ValidateCmdCopyBufferBounds(commandBuffer, *src_buffer_state, *dst_buffer_state, regionCount, pRegions, loc);

    vuid = is_2 ? "VUID-vkCmdCopyBuffer2-commandBuffer-01822" : "VUID-vkCmdCopyBuffer-commandBuffer-01822";
    skip |= ValidateProtectedBuffer(cb_state, *src_buffer_state, src_buffer_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdCopyBuffer2-commandBuffer-01823" : "VUID-vkCmdCopyBuffer-commandBuffer-01823";
    skip |= ValidateProtectedBuffer(cb_state, *dst_buffer_state, dst_buffer_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdCopyBuffer2-commandBuffer-01824" : "VUID-vkCmdCopyBuffer-commandBuffer-01824";
    skip |= ValidateUnprotectedBuffer(cb_state, *dst_buffer_state, dst_buffer_loc, vuid);

    return skip;
}

bool CoreChecks::PreCallValidateCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer,
                                              uint32_t regionCount, const VkBufferCopy *pRegions,
                                              const ErrorObject &error_obj) const {
    return ValidateCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, regionCount, pRegions, error_obj.location);
}

bool CoreChecks::PreCallValidateCmdCopyBuffer2KHR(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2KHR *pCopyBufferInfo,
                                                  const ErrorObject &error_obj) const {
    return PreCallValidateCmdCopyBuffer2(commandBuffer, pCopyBufferInfo, error_obj);
}

bool CoreChecks::PreCallValidateCmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2 *pCopyBufferInfo,
                                               const ErrorObject &error_obj) const {
    return ValidateCmdCopyBuffer(commandBuffer, pCopyBufferInfo->srcBuffer, pCopyBufferInfo->dstBuffer,
                                 pCopyBufferInfo->regionCount, pCopyBufferInfo->pRegions,
                                 error_obj.location.dot(Field::pCopyBufferInfo));
}

// Check valid usage Image Transfer Granularity requirements for elements of a VkBufferImageCopy/VkBufferImageCopy2 structure
template <typename RegionType>
bool CoreChecks::ValidateCopyBufferImageTransferGranularityRequirements(const vvl::CommandBuffer &cb_state,
                                                                        const vvl::Image &image_state, const RegionType *region,
                                                                        const Location &region_loc, const char *vuid) const {
    bool skip = false;
    const LogObjectList objlist(cb_state.Handle(), image_state.Handle());
    VkExtent3D granularity = GetScaledItg(cb_state, image_state);
    skip |= CheckItgOffset(objlist, region->imageOffset, granularity, region_loc.dot(Field::imageOffset), vuid);
    VkExtent3D subresource_extent = image_state.GetEffectiveSubresourceExtent(region->imageSubresource);
    skip |= CheckItgExtent(objlist, region->imageExtent, region->imageOffset, granularity, subresource_extent,
                           image_state.createInfo.imageType, region_loc.dot(Field::imageExtent), vuid);
    return skip;
}

template <typename HandleT>
bool CoreChecks::ValidateImageSubresourceLayers(HandleT handle, const VkImageSubresourceLayers *subresource_layers,
                                                const Location &subresource_loc) const {
    bool skip = false;
    const VkImageAspectFlags aspect_mask = subresource_layers->aspectMask;
    if (subresource_layers->layerCount == VK_REMAINING_ARRAY_LAYERS) {
        if (!enabled_features.maintenance5) {
            skip |= LogError("VUID-VkImageSubresourceLayers-layerCount-09243", handle, subresource_loc.dot(Field::layerCount),
                             "is VK_REMAINING_ARRAY_LAYERS.");
        }
    } else if (subresource_layers->layerCount == 0) {
        skip |=
            LogError("VUID-VkImageSubresourceLayers-layerCount-01700", handle, subresource_loc.dot(Field::layerCount), "is zero.");
    }
    // aspectMask must not contain VK_IMAGE_ASPECT_METADATA_BIT
    if (aspect_mask & VK_IMAGE_ASPECT_METADATA_BIT) {
        skip |= LogError("VUID-VkImageSubresourceLayers-aspectMask-00168", handle, subresource_loc.dot(Field::aspectMask), "is %s.",
                         string_VkImageAspectFlags(aspect_mask).c_str());
    }
    // if aspectMask contains COLOR, it must not contain either DEPTH or STENCIL
    if ((aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) && (aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT))) {
        skip |= LogError("VUID-VkImageSubresourceLayers-aspectMask-00167", handle, subresource_loc.dot(Field::aspectMask), "is %s.",
                         string_VkImageAspectFlags(aspect_mask).c_str());
    }
    // aspectMask must not contain VK_IMAGE_ASPECT_MEMORY_PLANE_i_BIT_EXT
    if (aspect_mask & (VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT | VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT |
                       VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT | VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT)) {
        skip |= LogError("VUID-VkImageSubresourceLayers-aspectMask-02247", handle, subresource_loc.dot(Field::aspectMask), "is %s.",
                         string_VkImageAspectFlags(aspect_mask).c_str());
    }
    return skip;
}

// Check valid usage Image Transfer Granularity requirements for elements of a VkImageCopy/VkImageCopy2KHR structure
template <typename RegionType>
bool CoreChecks::ValidateCopyImageTransferGranularityRequirements(const vvl::CommandBuffer &cb_state,
                                                                  const vvl::Image &src_image_state,
                                                                  const vvl::Image &dst_image_state, const RegionType *region,
                                                                  const Location &region_loc) const {
    bool skip = false;
    const bool is_2 = region_loc.function == Func::vkCmdCopyImage2 || region_loc.function == Func::vkCmdCopyImage2KHR;
    const char *vuid;

    const VkExtent3D extent = region->extent;
    {
        // Source image checks
        const LogObjectList objlist(cb_state.Handle(), src_image_state.Handle());
        const VkExtent3D granularity = GetScaledItg(cb_state, src_image_state);
        vuid = is_2 ? "VUID-VkCopyImageInfo2-srcOffset-01783" : "VUID-vkCmdCopyImage-srcOffset-01783";
        skip |= CheckItgOffset(objlist, region->srcOffset, granularity, region_loc.dot(Field::srcOffset), vuid);
        const VkExtent3D subresource_extent = src_image_state.GetEffectiveSubresourceExtent(region->srcSubresource);
        vuid = is_2 ? "VUID-VkCopyImageInfo2-srcOffset-01783" : "VUID-vkCmdCopyImage-srcOffset-01783";
        skip |= CheckItgExtent(objlist, extent, region->srcOffset, granularity, subresource_extent,
                               src_image_state.createInfo.imageType, region_loc.dot(Field::extent), vuid);
    }

    {
        // Destination image checks
        const LogObjectList objlist(cb_state.Handle(), dst_image_state.Handle());
        const VkExtent3D granularity = GetScaledItg(cb_state, dst_image_state);
        vuid = is_2 ? "VUID-VkCopyImageInfo2-dstOffset-01784" : "VUID-vkCmdCopyImage-dstOffset-01784";
        skip |= CheckItgOffset(objlist, region->dstOffset, granularity, region_loc.dot(Field::dstOffset), vuid);
        // Adjust dest extent, if necessary
        const VkExtent3D dest_effective_extent =
            GetAdjustedDestImageExtent(src_image_state.createInfo.format, dst_image_state.createInfo.format, extent);
        const VkExtent3D subresource_extent = dst_image_state.GetEffectiveSubresourceExtent(region->dstSubresource);
        vuid = is_2 ? "VUID-VkCopyImageInfo2-dstOffset-01784" : "VUID-vkCmdCopyImage-dstOffset-01784";
        skip |= CheckItgExtent(objlist, dest_effective_extent, region->dstOffset, granularity, subresource_extent,
                               dst_image_state.createInfo.imageType, region_loc.dot(Field::extent), vuid);
    }
    return skip;
}

static const char *GetImageCopyVUID(const std::string &id, bool copy2, bool host_version) {
    // clang-format off
    static const std::map<std::string, std::array<const char *, 3>> copy_image_vuid = {
        {"00146", {
            "VUID-vkCmdCopyImage-srcImage-00146",            // !copy2 & !host_version
            "VUID-VkCopyImageInfo2-srcImage-00146",          // copy2 && !host_version
            "VUID-VkCopyImageToImageInfoEXT-srcImage-07979", // host_version
        }},
        {"01785", {
            "VUID-vkCmdCopyImage-srcImage-01785",
            "VUID-VkCopyImageInfo2-srcImage-01785",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-07980",
        }},
        {"07278", {
            "VUID-vkCmdCopyImage-pRegions-07278",
            "VUID-VkCopyImageInfo2-pRegions-07278",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-07274",
        }},
        {"07279", {
            "VUID-vkCmdCopyImage-pRegions-07279",
            "VUID-VkCopyImageInfo2-pRegions-07279",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-07275",
        }},
        {"07280", {
            "VUID-vkCmdCopyImage-pRegions-07280",
            "VUID-VkCopyImageInfo2-pRegions-07280",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-07276",
        }},
        {"01728", {
            "VUID-vkCmdCopyImage-srcImage-01728",
            "VUID-VkCopyImageInfo2-srcImage-01728",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-00207",
        }},
        {"01729", {
            "VUID-vkCmdCopyImage-srcImage-01729",
            "VUID-VkCopyImageInfo2-srcImage-01729",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-00208",
        }},
        {"01730", {
            "VUID-vkCmdCopyImage-srcImage-01730",
            "VUID-VkCopyImageInfo2-srcImage-01730",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-00209",
        }},
        {"00152", {
            "VUID-vkCmdCopyImage-dstImage-00152",
            "VUID-VkCopyImageInfo2-dstImage-00152",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-07979",
        }},
        {"01786", {
            "VUID-vkCmdCopyImage-dstImage-01786",
            "VUID-VkCopyImageInfo2-dstImage-01786",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-07980",
        }},
        {"04443", {
            "VUID-vkCmdCopyImage-srcImage-04443",
            "VUID-VkCopyImageInfo2-srcImage-04443",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-07983",
       }},
       {"04444", {
            "VUID-vkCmdCopyImage-dstImage-04444",
            "VUID-VkCopyImageInfo2-dstImage-04444",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-07983",
       }},
       {"07281", {
            "VUID-vkCmdCopyImage-pRegions-07281",
            "VUID-VkCopyImageInfo2-pRegions-07281",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-07274",
       }},
       {"07282", {
            "VUID-vkCmdCopyImage-pRegions-07282",
            "VUID-VkCopyImageInfo2-pRegions-07282",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-07275",
       }},
       {"07283", {
            "VUID-vkCmdCopyImage-pRegions-07283",
            "VUID-VkCopyImageInfo2-pRegions-07283",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-07276",
       }},
       {"01732", {
            "VUID-vkCmdCopyImage-dstImage-01732",
            "VUID-VkCopyImageInfo2-dstImage-01732",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-00207",
       }},
       {"01733", {
            "VUID-vkCmdCopyImage-dstImage-01733",
            "VUID-VkCopyImageInfo2-dstImage-01733",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-00208",
       }},
       {"01734", {
            "VUID-vkCmdCopyImage-dstImage-01734",
            "VUID-VkCopyImageInfo2-dstImage-01734",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-00209",
       }},
       {"07967src", {
            "VUID-vkCmdCopyImage-srcSubresource-07967",
            "VUID-VkCopyImageInfo2-srcSubresource-07967",
            "VUID-VkCopyImageToImageInfoEXT-srcSubresource-07967",
       }},
       {"07967dst", {
            "VUID-vkCmdCopyImage-dstSubresource-07967",
            "VUID-VkCopyImageInfo2-dstSubresource-07967",
            "VUID-VkCopyImageToImageInfoEXT-dstSubresource-07967",
       }},
       {"07968src", {
            "VUID-vkCmdCopyImage-srcSubresource-07968",
            "VUID-VkCopyImageInfo2-srcSubresource-07968",
            "VUID-VkCopyImageToImageInfoEXT-srcSubresource-07968",
       }},
       {"07968dst", {
            "VUID-vkCmdCopyImage-dstSubresource-07968",
            "VUID-VkCopyImageInfo2-dstSubresource-07968",
            "VUID-VkCopyImageToImageInfoEXT-dstSubresource-07968",
       }},
       {"00142", {
            "VUID-vkCmdCopyImage-aspectMask-00142",
            "VUID-VkCopyImageInfo2-aspectMask-00142",
            "VUID-VkCopyImageToImageInfoEXT-srcSubresource-09105",
       }},
       {"00143", {
            "VUID-vkCmdCopyImage-aspectMask-00143",
            "VUID-VkCopyImageInfo2-aspectMask-00143",
            "VUID-VkCopyImageToImageInfoEXT-dstSubresource-09105",
       }},
       {"00144", {
            "VUID-vkCmdCopyImage-srcOffset-00144",
            "VUID-VkCopyImageInfo2-srcOffset-00144",
            "VUID-VkCopyImageToImageInfoEXT-srcSubresource-07971",
       }},
       {"00145", {
            "VUID-vkCmdCopyImage-srcOffset-00145",
            "VUID-VkCopyImageInfo2-srcOffset-00145",
            "VUID-VkCopyImageToImageInfoEXT-srcSubresource-07972",
       }},
       {"00147", {
            "VUID-vkCmdCopyImage-srcOffset-00147",
            "VUID-VkCopyImageInfo2-srcOffset-00147",
            "VUID-VkCopyImageToImageInfoEXT-srcOffset-09104",
       }},
       {"00150", {
            "VUID-vkCmdCopyImage-dstOffset-00150",
            "VUID-VkCopyImageInfo2-dstOffset-00150",
            "VUID-VkCopyImageToImageInfoEXT-dstSubresource-07971",
       }},
       {"00151", {
            "VUID-vkCmdCopyImage-dstOffset-00151",
            "VUID-VkCopyImageInfo2-dstOffset-00151",
            "VUID-VkCopyImageToImageInfoEXT-dstSubresource-07972",
       }},
       {"00153", {
            "VUID-vkCmdCopyImage-dstOffset-00153",
            "VUID-VkCopyImageInfo2-dstOffset-00153",
            "VUID-VkCopyImageToImageInfoEXT-dstOffset-09104",
       }},
       {"07966src", {
            "VUID-vkCmdCopyImage-srcImage-07966",
            "VUID-VkCopyImageInfo2-srcImage-07966",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-07966",
       }},
       {"07966dst", {
            "VUID-vkCmdCopyImage-dstImage-07966",
            "VUID-VkCopyImageInfo2-dstImage-07966",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-07966",
       }},
       {"07969src", {
            "VUID-vkCmdCopyImage-srcImage-07969",
            "VUID-VkCopyImageInfo2-srcImage-07969",
            "VUID-VkCopyImageToImageInfoEXT-srcImage-07969",
       }},
       {"07969dst", {
            "VUID-vkCmdCopyImage-dstImage-07969",
            "VUID-VkCopyImageInfo2-dstImage-07969",
            "VUID-VkCopyImageToImageInfoEXT-dstImage-07969",
       }},
    };
    // clang-format on
    uint8_t index = 0;

    if (host_version) {
        index = 2;
    } else if (copy2) {
        index = 1;
    }
    return copy_image_vuid.at(id).at(index);
}

// Validate contents of a VkImageCopy or VkImageCopy2KHR struct
template <typename HandleT, typename RegionType>
bool CoreChecks::ValidateImageCopyData(const HandleT handle, const uint32_t regionCount, const RegionType *pRegions,
                                       const vvl::Image &src_image_state, const vvl::Image &dst_image_state, bool is_host,
                                       const Location &loc) const {
    bool skip = false;
    const bool is_2 = loc.function == Func::vkCmdCopyImage2 || loc.function == Func::vkCmdCopyImage2KHR;
    const char *vuid;

    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const RegionType region = pRegions[i];

        // For comp<->uncomp copies, the copy extent for the dest image must be adjusted
        const VkExtent3D src_copy_extent = region.extent;
        const VkExtent3D dst_copy_extent =
            GetAdjustedDestImageExtent(src_image_state.createInfo.format, dst_image_state.createInfo.format, region.extent);

        bool slice_override = false;
        uint32_t depth_slices = 0;

        // Special case for copying between a 1D/2D array and a 3D image
        // TBD: This seems like the only way to reconcile 3 mutually-exclusive VU checks for 2D/3D copies. Heads up.
        if ((VK_IMAGE_TYPE_3D == src_image_state.createInfo.imageType) &&
            (VK_IMAGE_TYPE_3D != dst_image_state.createInfo.imageType)) {
            depth_slices = region.dstSubresource.layerCount;  // Slice count from 2D subresource
            slice_override = (depth_slices != 1);
        } else if ((VK_IMAGE_TYPE_3D == dst_image_state.createInfo.imageType) &&
                   (VK_IMAGE_TYPE_3D != src_image_state.createInfo.imageType)) {
            depth_slices = region.srcSubresource.layerCount;  // Slice count from 2D subresource
            slice_override = (depth_slices != 1);
        }

        // Do all checks on source image
        if (src_image_state.createInfo.imageType == VK_IMAGE_TYPE_1D) {
            if ((0 != region.srcOffset.y) || (1 != src_copy_extent.height)) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("00146", is_2, is_host), objlist, region_loc,
                                 "srcOffset.y is %" PRId32 " and extent.height is %" PRIu32
                                 ". For 1D images these must "
                                 "be 0 and 1, respectively.",
                                 region.srcOffset.y, src_copy_extent.height);
            }
        }

        if (((src_image_state.createInfo.imageType == VK_IMAGE_TYPE_1D) ||
             ((src_image_state.createInfo.imageType == VK_IMAGE_TYPE_2D) && is_host)) &&
            ((0 != region.srcOffset.z) || (1 != src_copy_extent.depth))) {
            const LogObjectList objlist(handle, src_image_state.image());
            const char *image_type = is_host ? "1D or 2D" : "1D";
            skip |= LogError(GetImageCopyVUID("01785", is_2, is_host), objlist, region_loc,
                             "srcOffset.z is %" PRId32 " and extent.depth is %" PRIu32
                             ". For %s images "
                             "these must be 0 and 1, respectively.",
                             region.srcOffset.z, src_copy_extent.depth, image_type);
        }

        if ((src_image_state.createInfo.imageType == VK_IMAGE_TYPE_2D) && (0 != region.srcOffset.z) && (!is_host)) {
            const LogObjectList objlist(handle, src_image_state.image());
            vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-01787" : "VUID-vkCmdCopyImage-srcImage-01787";
            skip |= LogError(vuid, objlist, region_loc, "srcOffset.z is %" PRId32 ". For 2D images the z-offset must be 0.",
                             region.srcOffset.z);
        }

        {  // Used to be compressed checks, now apply to all
            const VkExtent3D block_size = vkuFormatTexelBlockExtent(src_image_state.createInfo.format);
            if (SafeModulo(region.srcOffset.x, block_size.width) != 0) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("07278", is_2, is_host), objlist, region_loc,
                                 "srcOffset.x (%" PRId32
                                 ") must be a multiple of the blocked image's texel "
                                 "width (%" PRIu32 ").",
                                 region.srcOffset.x, block_size.width);
            }

            //  image offsets y must be multiple of block height
            if (SafeModulo(region.srcOffset.y, block_size.height) != 0) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("07279", is_2, is_host), objlist, region_loc,
                                 "srcOffset.y (%" PRId32
                                 ") must be a multiple of the blocked image's texel "
                                 "height (%" PRIu32 ").",
                                 region.srcOffset.y, block_size.height);
            }

            //  image offsets z must be multiple of block depth
            if (SafeModulo(region.srcOffset.z, block_size.depth) != 0) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("07280", is_2, is_host), objlist, region_loc,
                                 "srcOffset.z (%" PRId32
                                 ") must be a multiple of the blocked image's texel "
                                 "depth (%" PRIu32 ").",
                                 region.srcOffset.z, block_size.depth);
            }

            const VkExtent3D mip_extent = src_image_state.GetEffectiveSubresourceExtent(region.srcSubresource);
            if ((SafeModulo(src_copy_extent.width, block_size.width) != 0) &&
                (src_copy_extent.width + region.srcOffset.x != mip_extent.width)) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("01728", is_2, is_host), objlist, region_loc,
                                 "extent width (%" PRIu32
                                 ") must be a multiple of the blocked texture block "
                                 "width (%" PRIu32 "), or when added to srcOffset.x (%" PRId32
                                 ") must equal the image subresource width (%" PRIu32 ").",
                                 src_copy_extent.width, block_size.width, region.srcOffset.x, mip_extent.width);
            }

            // Extent height must be a multiple of block height, or extent+offset height must equal subresource height
            if ((SafeModulo(src_copy_extent.height, block_size.height) != 0) &&
                (src_copy_extent.height + region.srcOffset.y != mip_extent.height)) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("01729", is_2, is_host), objlist, region_loc,
                                 "extent height (%" PRIu32
                                 ") must be a multiple of the compressed texture block "
                                 "height (%" PRIu32 "), or when added to srcOffset.y (%" PRId32
                                 ") must equal the image subresource height (%" PRIu32 ").",
                                 src_copy_extent.height, block_size.height, region.srcOffset.y, mip_extent.height);
            }

            // Extent depth must be a multiple of block depth, or extent+offset depth must equal subresource depth
            uint32_t copy_depth = (slice_override ? depth_slices : src_copy_extent.depth);
            if ((SafeModulo(copy_depth, block_size.depth) != 0) && (copy_depth + region.srcOffset.z != mip_extent.depth)) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("01730", is_2, is_host), objlist, region_loc,
                                 "extent width (%" PRIu32
                                 ") must be a multiple of the compressed texture block "
                                 "depth (%" PRIu32 "), or when added to srcOffset.z (%" PRId32
                                 ") must equal the image subresource depth (%" PRIu32 ").",
                                 src_copy_extent.depth, block_size.depth, region.srcOffset.z, mip_extent.depth);
            }
        }

        // Do all checks on dest image
        if (dst_image_state.createInfo.imageType == VK_IMAGE_TYPE_1D) {
            if ((0 != region.dstOffset.y) || (1 != dst_copy_extent.height)) {
                const LogObjectList objlist(handle, dst_image_state.image());
                skip |= LogError(GetImageCopyVUID("00152", is_2, is_host), objlist, region_loc,
                                 "dstOffset.y is %" PRId32 " and dst_copy_extent.height is %" PRIu32
                                 ". For 1D images "
                                 "these must be 0 and 1, respectively.",
                                 region.dstOffset.y, dst_copy_extent.height);
            }
        }

        if (((dst_image_state.createInfo.imageType == VK_IMAGE_TYPE_1D) ||
             ((dst_image_state.createInfo.imageType == VK_IMAGE_TYPE_2D) && is_host)) &&
            ((0 != region.dstOffset.z) || (1 != dst_copy_extent.depth))) {
            const LogObjectList objlist(handle, dst_image_state.image());
            const char *image_type = is_host ? "1D or 2D" : "1D";
            skip |= LogError(GetImageCopyVUID("01786", is_2, is_host), objlist, region_loc,
                             "dstOffset.z is %" PRId32 " and extent.depth is %" PRIu32
                             ". For %s images these must be 0 "
                             "and 1, respectively.",
                             region.dstOffset.z, dst_copy_extent.depth, image_type);
        }

        if ((dst_image_state.createInfo.imageType == VK_IMAGE_TYPE_2D) && (0 != region.dstOffset.z) && !(is_host)) {
            const LogObjectList objlist(handle, dst_image_state.image());
            vuid = is_2 ? "VUID-VkCopyImageInfo2-dstImage-01788" : "VUID-vkCmdCopyImage-dstImage-01788";
            skip |= LogError(vuid, objlist, region_loc, "dstOffset.z is %" PRId32 ". For 2D images the z-offset must be 0.",
                             region.dstOffset.z);
        }

        // Handle difference between Maintenance 1
        if (IsExtEnabled(device_extensions.vk_khr_maintenance1) || is_host) {
            if (src_image_state.createInfo.imageType == VK_IMAGE_TYPE_3D) {
                const LogObjectList objlist(handle, src_image_state.image());
                if ((0 != region.srcSubresource.baseArrayLayer) || (1 != region.srcSubresource.layerCount)) {
                    skip |= LogError(GetImageCopyVUID("04443", is_2, is_host), objlist, region_loc,
                                     "srcSubresource.baseArrayLayer is %" PRIu32
                                     " and srcSubresource.layerCount "
                                     "is %" PRIu32 ". For VK_IMAGE_TYPE_3D images these must be 0 and 1, respectively.",
                                     region.srcSubresource.baseArrayLayer, region.srcSubresource.layerCount);
                }
            }
            if (dst_image_state.createInfo.imageType == VK_IMAGE_TYPE_3D) {
                const LogObjectList objlist(handle, dst_image_state.image());
                if ((0 != region.dstSubresource.baseArrayLayer) || (1 != region.dstSubresource.layerCount)) {
                    skip |= LogError(GetImageCopyVUID("04444", is_2, is_host), objlist, region_loc,
                                     "dstSubresource.baseArrayLayer is %" PRIu32
                                     " and dstSubresource.layerCount "
                                     "is %" PRIu32 ". For VK_IMAGE_TYPE_3D images these must be 0 and 1, respectively.",
                                     region.dstSubresource.baseArrayLayer, region.dstSubresource.layerCount);
                }
            }
        } else {  // Pre maint 1
            if (src_image_state.createInfo.imageType == VK_IMAGE_TYPE_3D ||
                dst_image_state.createInfo.imageType == VK_IMAGE_TYPE_3D) {
                if ((0 != region.srcSubresource.baseArrayLayer) || (1 != region.srcSubresource.layerCount)) {
                    const LogObjectList objlist(handle, src_image_state.image());
                    vuid = is_2 ? "VUID-VkCopyImageInfo2-apiVersion-07932" : "VUID-vkCmdCopyImage-apiVersion-07932";
                    skip |= LogError(vuid, objlist, region_loc,
                                     "srcSubresource.baseArrayLayer is %" PRIu32
                                     " and "
                                     "srcSubresource.layerCount is %" PRIu32
                                     ". For copies with either source or dest of type "
                                     "VK_IMAGE_TYPE_3D, these must be 0 and 1, respectively.",
                                     region.srcSubresource.baseArrayLayer, region.srcSubresource.layerCount);
                }
                if ((0 != region.dstSubresource.baseArrayLayer) || (1 != region.dstSubresource.layerCount)) {
                    const LogObjectList objlist(handle, dst_image_state.image());
                    vuid = is_2 ? "VUID-VkCopyImageInfo2-apiVersion-07932" : "VUID-vkCmdCopyImage-apiVersion-07932";
                    skip |= LogError(vuid, objlist, region_loc,
                                     "dstSubresource.baseArrayLayer is %" PRIu32
                                     " and "
                                     "dstSubresource.layerCount is %" PRIu32
                                     ". For copies with either source or dest of type "
                                     "VK_IMAGE_TYPE_3D, these must be 0 and 1, respectively.",
                                     region.dstSubresource.baseArrayLayer, region.dstSubresource.layerCount);
                }
            }
        }

        {
            const VkExtent3D block_size = vkuFormatTexelBlockExtent(dst_image_state.createInfo.format);
            //  image offsets x must be multiple of block width
            if (SafeModulo(region.dstOffset.x, block_size.width) != 0) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("07281", is_2, is_host), objlist, region_loc,
                                 "srcOffset.x (%" PRId32
                                 ") must be a multiple of the blocked image's texel "
                                 "width (%" PRIu32 ").",
                                 region.dstOffset.x, block_size.width);
            }

            //  image offsets y must be multiple of block height
            if (SafeModulo(region.dstOffset.y, block_size.height) != 0) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("07282", is_2, is_host), objlist, region_loc,
                                 "srcOffset.y (%" PRId32
                                 ") must be a multiple of the blocked image's texel "
                                 "height (%" PRIu32 ").",
                                 region.dstOffset.y, block_size.height);
            }

            //  image offsets z must be multiple of block depth
            if (SafeModulo(region.dstOffset.z, block_size.depth) != 0) {
                const LogObjectList objlist(handle, src_image_state.image());
                skip |= LogError(GetImageCopyVUID("07283", is_2, is_host), objlist, region_loc,
                                 "srcOffset.z (%" PRId32
                                 ") must be a multiple of the blocked image's texel "
                                 "depth (%" PRIu32 ").",
                                 region.dstOffset.z, block_size.depth);
            }

            const VkExtent3D mip_extent = dst_image_state.GetEffectiveSubresourceExtent(region.dstSubresource);
            if ((SafeModulo(dst_copy_extent.width, block_size.width) != 0) &&
                (dst_copy_extent.width + region.dstOffset.x != mip_extent.width)) {
                const LogObjectList objlist(handle, dst_image_state.image());
                skip |= LogError(GetImageCopyVUID("01732", is_2, is_host), objlist, region_loc,
                                 "dst_copy_extent width (%" PRIu32
                                 ") must be a multiple of the blocked texture "
                                 "block width (%" PRIu32 "), or when added to dstOffset.x (%" PRId32
                                 ") must equal the image subresource width (%" PRIu32 ").",
                                 dst_copy_extent.width, block_size.width, region.dstOffset.x, mip_extent.width);
            }

            // Extent height must be a multiple of block height, or dst_copy_extent+offset height must equal subresource height
            if ((SafeModulo(dst_copy_extent.height, block_size.height) != 0) &&
                (dst_copy_extent.height + region.dstOffset.y != mip_extent.height)) {
                const LogObjectList objlist(handle, dst_image_state.image());
                skip |= LogError(GetImageCopyVUID("01733", is_2, is_host), objlist, region_loc,
                                 "dst_copy_extent height (%" PRIu32
                                 ") must be a multiple of the compressed "
                                 "texture block height (%" PRIu32 "), or when added to dstOffset.y (%" PRId32
                                 ") must equal the image subresource "
                                 "height (%" PRIu32 ").",
                                 dst_copy_extent.height, block_size.height, region.dstOffset.y, mip_extent.height);
            }

            // Extent depth must be a multiple of block depth, or dst_copy_extent+offset depth must equal subresource depth
            uint32_t copy_depth = (slice_override ? depth_slices : dst_copy_extent.depth);
            if ((SafeModulo(copy_depth, block_size.depth) != 0) && (copy_depth + region.dstOffset.z != mip_extent.depth)) {
                const LogObjectList objlist(handle, dst_image_state.image());
                skip |= LogError(GetImageCopyVUID("01734", is_2, is_host), objlist, region_loc,
                                 "dst_copy_extent width (%" PRIu32
                                 ") must be a multiple of the compressed texture "
                                 "block depth (%" PRIu32 "), or when added to dstOffset.z (%" PRId32
                                 ") must equal the image subresource depth (%" PRIu32 ").",
                                 dst_copy_extent.depth, block_size.depth, region.dstOffset.z, mip_extent.depth);
            }
        }
    }
    return skip;
}

// Returns non-zero if offset and extent exceed image extents
static constexpr uint32_t kXBit = 1;
static constexpr uint32_t kYBit = 2;
static constexpr uint32_t kZBit = 4;
static uint32_t ExceedsBounds(const VkOffset3D *offset, const VkExtent3D *extent, const VkExtent3D *image_extent) {
    uint32_t result = 0;
    // Extents/depths cannot be negative but checks left in for clarity
    if ((offset->z + extent->depth > image_extent->depth) || (offset->z < 0) ||
        ((offset->z + static_cast<int32_t>(extent->depth)) < 0)) {
        result |= kZBit;
    }
    if ((offset->y + extent->height > image_extent->height) || (offset->y < 0) ||
        ((offset->y + static_cast<int32_t>(extent->height)) < 0)) {
        result |= kYBit;
    }
    if ((offset->x + extent->width > image_extent->width) || (offset->x < 0) ||
        ((offset->x + static_cast<int32_t>(extent->width)) < 0)) {
        result |= kXBit;
    }
    return result;
}
template <typename HandleT, typename RegionType>
bool CoreChecks::ValidateCopyImageCommon(HandleT handle, const vvl::Image &src_image_state, const vvl::Image &dst_image_state,
                                         uint32_t regionCount, const RegionType *pRegions, const Location &loc) const {
    bool skip = false;
    const bool is_2 = loc.function == Func::vkCmdCopyImage2 || loc.function == Func::vkCmdCopyImage2KHR;
    const bool is_host = loc.function == Func::vkCopyImageToImageEXT;

    const VkFormat src_format = src_image_state.createInfo.format;
    const VkFormat dst_format = dst_image_state.createInfo.format;
    auto src_image = src_image_state.image();
    auto dst_image = dst_image_state.image();
    const bool src_is_3d = (VK_IMAGE_TYPE_3D == src_image_state.createInfo.imageType);
    const bool dst_is_3d = (VK_IMAGE_TYPE_3D == dst_image_state.createInfo.imageType);

    const LogObjectList src_objlist(handle, src_image);
    const LogObjectList dst_objlist(handle, dst_image);
    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const Location src_subresource_loc = region_loc.dot(Field::srcSubresource);
        const Location dst_subresource_loc = region_loc.dot(Field::dstSubresource);
        const RegionType region = pRegions[i];

        // For comp/uncomp copies, the copy extent for the dest image must be adjusted
        VkExtent3D src_copy_extent = region.extent;
        VkExtent3D dst_copy_extent = GetAdjustedDestImageExtent(src_format, dst_format, region.extent);

        bool slice_override = false;
        uint32_t depth_slices = 0;

        // Special case for copying between a 1D/2D array and a 3D image
        // TBD: This seems like the only way to reconcile 3 mutually-exclusive VU checks for 2D/3D copies. Heads up.
        if (src_is_3d && !dst_is_3d) {
            depth_slices = region.dstSubresource.layerCount;  // Slice count from 2D subresource
            slice_override = (depth_slices != 1);
        } else if (dst_is_3d && !src_is_3d) {
            depth_slices = region.srcSubresource.layerCount;  // Slice count from 2D subresource
            slice_override = (depth_slices != 1);
        }

        skip |= ValidateImageSubresourceLayers(handle, &region.srcSubresource, src_subresource_loc);
        skip |= ValidateImageSubresourceLayers(handle, &region.dstSubresource, dst_subresource_loc);

        skip |= ValidateImageMipLevel(handle, src_image_state, region.srcSubresource.mipLevel,
                                      src_subresource_loc.dot(Field::mipLevel), GetImageCopyVUID("07967src", is_2, is_host));
        skip |= ValidateImageMipLevel(handle, dst_image_state, region.dstSubresource.mipLevel,
                                      dst_subresource_loc.dot(Field::mipLevel), GetImageCopyVUID("07967dst", is_2, is_host));
        skip |= ValidateImageArrayLayerRange(handle, src_image_state, region.srcSubresource.baseArrayLayer,
                                             region.srcSubresource.layerCount, src_subresource_loc,
                                             GetImageCopyVUID("07968src", is_2, is_host));
        skip |= ValidateImageArrayLayerRange(handle, dst_image_state, region.dstSubresource.baseArrayLayer,
                                             region.dstSubresource.layerCount, dst_subresource_loc,
                                             GetImageCopyVUID("07968dst", is_2, is_host));

        if (api_version < VK_API_VERSION_1_1) {
            if (!IsExtEnabled(device_extensions.vk_khr_maintenance1)) {
                // For each region the layerCount member of srcSubresource and dstSubresource must match
                if (region.srcSubresource.layerCount != region.dstSubresource.layerCount) {
                    const LogObjectList objlist(handle, src_image, dst_image);
                    const char *vuid =
                        (is_2 || is_host) ? "VUID-VkImageCopy2-apiVersion-07941" : "VUID-VkImageCopy-apiVersion-07941";
                    skip |= LogError(vuid, objlist, src_subresource_loc.dot(Field::layerCount),
                                     "(%" PRIu32 ") does not match %s (%" PRIu32 ").", region.srcSubresource.layerCount,
                                     dst_subresource_loc.dot(Field::layerCount).Fields().c_str(), region.dstSubresource.layerCount);
                }
            }
            if (!IsExtEnabled(device_extensions.vk_khr_sampler_ycbcr_conversion)) {
                // For each region the aspectMask member of srcSubresource and dstSubresource must match
                if (region.srcSubresource.aspectMask != region.dstSubresource.aspectMask) {
                    const LogObjectList objlist(handle, src_image, dst_image);
                    const char *vuid =
                        (is_2 || is_host) ? "VUID-VkImageCopy2-apiVersion-07940" : "VUID-VkImageCopy-apiVersion-07940";
                    skip |= LogError(vuid, objlist, src_subresource_loc.dot(Field::aspectMask), "(%s) does not match %s (%s).",
                                     string_VkImageAspectFlags(region.srcSubresource.aspectMask).c_str(),
                                     dst_subresource_loc.dot(Field::aspectMask).Fields().c_str(),
                                     string_VkImageAspectFlags(region.dstSubresource.aspectMask).c_str());
                }
            }
        }

        // For each region, the aspectMask member of srcSubresource must be present in the source image
        if (!VerifyAspectsPresent(region.srcSubresource.aspectMask, src_format)) {
            skip |= LogError(GetImageCopyVUID("00142", is_2, is_host), src_objlist, src_subresource_loc.dot(Field::aspectMask),
                             "(%s) cannot specify aspects not present in source image (%s).",
                             string_VkImageAspectFlags(region.srcSubresource.aspectMask).c_str(), string_VkFormat(src_format));
        }
        // For each region, the aspectMask member of dstSubresource must be present in the destination image
        if (!VerifyAspectsPresent(region.dstSubresource.aspectMask, dst_format)) {
            skip |= LogError(GetImageCopyVUID("00143", is_2, is_host), dst_objlist, dst_subresource_loc.dot(Field::aspectMask),
                             "(%s) cannot specify aspects not present in destination image (%s).",
                             string_VkImageAspectFlags(region.dstSubresource.aspectMask).c_str(), string_VkFormat(dst_format));
        }

        // Make sure not a empty region
        if (src_copy_extent.width == 0) {
            const LogObjectList objlist(handle, src_image);
            const char *vuid = (is_2 || is_host) ? "VUID-VkImageCopy2-extent-06668" : "VUID-VkImageCopy-extent-06668";
            skip |= LogError(vuid, objlist, region_loc.dot(Field::extent).dot(Field::width),
                             "is zero. (empty copies are not allowed).");
        }
        if (src_copy_extent.height == 0) {
            const LogObjectList objlist(handle, src_image);
            const char *vuid = (is_2 || is_host) ? "VUID-VkImageCopy2-extent-06669" : "VUID-VkImageCopy-extent-06669";
            skip |= LogError(vuid, objlist, region_loc.dot(Field::extent).dot(Field::height),
                             "is zero. (empty copies are not allowed).");
        }
        if (src_copy_extent.depth == 0) {
            const LogObjectList objlist(handle, src_image);
            const char *vuid = (is_2 || is_host) ? "VUID-VkImageCopy2-extent-06670" : "VUID-VkImageCopy-extent-06670";
            skip |= LogError(vuid, objlist, region_loc.dot(Field::extent).dot(Field::depth),
                             "is zero. (empty copies are not allowed).");
        }

        // Each dimension offset + extent limits must fall with image subresource extent
        VkExtent3D subresource_extent = src_image_state.GetEffectiveSubresourceExtent(region.srcSubresource);
        if (slice_override) src_copy_extent.depth = depth_slices;
        uint32_t extent_check = ExceedsBounds(&(region.srcOffset), &src_copy_extent, &subresource_extent);
        if (extent_check & kXBit) {
            skip |= LogError(GetImageCopyVUID("00144", is_2, is_host), src_objlist, region_loc.dot(Field::srcOffset).dot(Field::x),
                             "(%" PRId32 ") + extent (%" PRIu32 ") exceeds subResource width (%" PRIu32 ").", region.srcOffset.x,
                             src_copy_extent.width, subresource_extent.width);
        }

        if (extent_check & kYBit) {
            skip |= LogError(GetImageCopyVUID("00145", is_2, is_host), src_objlist, region_loc.dot(Field::srcOffset).dot(Field::y),
                             "(%" PRId32 ") + extent (%" PRIu32 ") exceeds subResource height (%" PRIu32 ").", region.srcOffset.y,
                             src_copy_extent.height, subresource_extent.height);
        }
        if (extent_check & kZBit) {
            skip |= LogError(GetImageCopyVUID("00147", is_2, is_host), src_objlist, region_loc.dot(Field::srcOffset).dot(Field::z),
                             "(%" PRId32 ") + extent (%" PRIu32 ") exceeds subResource depth (%" PRIu32 ").", region.srcOffset.z,
                             src_copy_extent.depth, subresource_extent.depth);
        }

        // Adjust dest extent if necessary
        subresource_extent = dst_image_state.GetEffectiveSubresourceExtent(region.dstSubresource);
        if (slice_override) dst_copy_extent.depth = depth_slices;

        extent_check = ExceedsBounds(&(region.dstOffset), &dst_copy_extent, &subresource_extent);
        if (extent_check & kXBit) {
            skip |= LogError(GetImageCopyVUID("00150", is_2, is_host), dst_objlist, region_loc.dot(Field::dstOffset).dot(Field::x),
                             "(%" PRId32 ") + extent (%" PRIu32 ") exceeds subResource width (%" PRIu32 ").", region.dstOffset.x,
                             dst_copy_extent.width, subresource_extent.width);
        }
        if (extent_check & kYBit) {
            skip |= LogError(GetImageCopyVUID("00151", is_2, is_host), dst_objlist, region_loc.dot(Field::dstOffset).dot(Field::y),
                             "(%" PRId32 ") + extent (%" PRIu32 ") exceeds subResource height (%" PRIu32 ").", region.dstOffset.y,
                             dst_copy_extent.height, subresource_extent.height);
        }
        if (extent_check & kZBit) {
            skip |= LogError(GetImageCopyVUID("00153", is_2, is_host), dst_objlist, region_loc.dot(Field::dstOffset).dot(Field::z),
                             "(%" PRId32 ") + extent (%" PRIu32 ") exceeds subResource depth (%" PRIu32 ").", region.dstOffset.z,
                             dst_copy_extent.depth, subresource_extent.depth);
        }
    }

    skip |= ValidateMemoryIsBoundToImage(src_objlist, src_image_state, loc.dot(Field::srcImage),
                                         GetImageCopyVUID("07966src", is_2, is_host));
    skip |= ValidateMemoryIsBoundToImage(dst_objlist, dst_image_state, loc.dot(Field::dstImage),
                                         GetImageCopyVUID("07966dst", is_2, is_host));

    if (src_image_state.createInfo.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
        skip |= LogError(GetImageCopyVUID("07969src", is_2, is_host), src_objlist, loc.dot(Field::srcImage),
                         "was created with flags including VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT");
    }
    if (dst_image_state.createInfo.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
        skip |= LogError(GetImageCopyVUID("07969dst", is_2, is_host), dst_objlist, loc.dot(Field::dstImage),
                         "was created with flags including VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT");
    }

    return skip;
}

template <typename RegionType>
bool CoreChecks::ValidateCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                      VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                      const RegionType *pRegions, const Location &loc) const {
    bool skip = false;
    auto cb_state_ptr = GetRead<vvl::CommandBuffer>(commandBuffer);
    auto src_image_state = Get<vvl::Image>(srcImage);
    auto dst_image_state = Get<vvl::Image>(dstImage);
    if (!cb_state_ptr || !src_image_state || !dst_image_state) {
        return skip;
    }
    const vvl::CommandBuffer &cb_state = *cb_state_ptr;
    const VkFormat src_format = src_image_state->createInfo.format;
    const VkFormat dst_format = dst_image_state->createInfo.format;
    const VkImageType src_image_type = src_image_state->createInfo.imageType;
    const VkImageType dst_image_type = dst_image_state->createInfo.imageType;
    const bool src_is_2d = (VK_IMAGE_TYPE_2D == src_image_type);
    const bool src_is_3d = (VK_IMAGE_TYPE_3D == src_image_type);
    const bool dst_is_2d = (VK_IMAGE_TYPE_2D == dst_image_type);
    const bool dst_is_3d = (VK_IMAGE_TYPE_3D == dst_image_type);
    const bool is_2 = loc.function == Func::vkCmdCopyImage2 || loc.function == Func::vkCmdCopyImage2KHR;

    const char *vuid;
    const Location src_image_loc = loc.dot(Field::srcImage);
    const Location dst_image_loc = loc.dot(Field::dstImage);

    const LogObjectList src_objlist(commandBuffer, srcImage);
    const LogObjectList dst_objlist(commandBuffer, dstImage);
    const LogObjectList all_objlist(commandBuffer, srcImage, dstImage);

    skip = ValidateImageCopyData(commandBuffer, regionCount, pRegions, *src_image_state, *dst_image_state, false, loc);
    skip = ValidateCopyImageCommon(commandBuffer, *src_image_state, *dst_image_state, regionCount, pRegions, loc);

    bool has_stencil_aspect = false;
    bool has_non_stencil_aspect = false;
    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const Location src_subresource_loc = region_loc.dot(Field::srcSubresource);
        const Location dst_subresource_loc = region_loc.dot(Field::dstSubresource);
        const RegionType &region = pRegions[i];

        // For comp/uncomp copies, the copy extent for the dest image must be adjusted
        VkExtent3D src_copy_extent = region.extent;
        VkExtent3D dst_copy_extent = GetAdjustedDestImageExtent(src_format, dst_format, region.extent);

        bool slice_override = false;
        uint32_t depth_slices = 0;

        // Special case for copying between a 1D/2D array and a 3D image
        // TBD: This seems like the only way to reconcile 3 mutually-exclusive VU checks for 2D/3D copies. Heads up.
        if (src_is_3d && !dst_is_3d) {
            depth_slices = region.dstSubresource.layerCount;  // Slice count from 2D subresource
            slice_override = (depth_slices != 1);
        } else if (dst_is_3d && !src_is_3d) {
            depth_slices = region.srcSubresource.layerCount;  // Slice count from 2D subresource
            slice_override = (depth_slices != 1);
        }

        if (IsExtEnabled(device_extensions.vk_khr_maintenance1)) {
            // No chance of mismatch if we're overriding depth slice count
            if (!slice_override) {
                // The number of depth slices in srcSubresource and dstSubresource must match
                // Depth comes from layerCount for 1D,2D resources, from extent.depth for 3D
                uint32_t src_slices = (src_is_3d ? src_copy_extent.depth : region.srcSubresource.layerCount);
                uint32_t dst_slices = (dst_is_3d ? dst_copy_extent.depth : region.dstSubresource.layerCount);
                if (src_slices == VK_REMAINING_ARRAY_LAYERS || dst_slices == VK_REMAINING_ARRAY_LAYERS) {
                    if (src_slices != VK_REMAINING_ARRAY_LAYERS) {
                        if (src_slices != (dst_image_state->createInfo.arrayLayers - region.dstSubresource.baseArrayLayer)) {
                            vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-08794" : "VUID-vkCmdCopyImage-srcImage-08794";
                            skip |=
                                LogError(vuid, dst_objlist, src_subresource_loc.dot(Field::layerCount),
                                         "(%" PRIu32 ") does not match dstImage arrayLayers (%" PRIu32
                                         ") minus baseArrayLayer (%" PRIu32 ").",
                                         src_slices, dst_image_state->createInfo.arrayLayers, region.dstSubresource.baseArrayLayer);
                        }
                    } else if (dst_slices != VK_REMAINING_ARRAY_LAYERS) {
                        if (dst_slices != (src_image_state->createInfo.arrayLayers - region.srcSubresource.baseArrayLayer)) {
                            vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-08794" : "VUID-vkCmdCopyImage-srcImage-08794";
                            skip |=
                                LogError(vuid, src_objlist, dst_subresource_loc.dot(Field::layerCount),
                                         "(%" PRIu32 ") does not match srcImage arrayLayers (%" PRIu32
                                         ") minus baseArrayLayer (%" PRIu32 ").",
                                         dst_slices, src_image_state->createInfo.arrayLayers, region.srcSubresource.baseArrayLayer);
                        }
                    }
                } else if (src_slices != dst_slices) {
                    vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-08793" : "VUID-vkCmdCopyImage-srcImage-08793";
                    skip |= LogError(vuid, all_objlist, region_loc, "%s (%" PRIu32 ") is different from %s (%" PRIu32 ").",
                                     src_is_3d ? "extent.depth" : "srcSubresource.layerCount", src_slices,
                                     dst_is_3d ? "extent.depth" : "dstSubresource.layerCount", dst_slices);
                }
            }
            // Maintenance 1 requires both while prior only required one to be 2D
            if ((src_is_2d && dst_is_2d) && (src_copy_extent.depth != 1)) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-01790" : "VUID-vkCmdCopyImage-srcImage-01790";
                skip |= LogError(vuid, all_objlist, region_loc,
                                 "both srcImage and dstImage are 2D and extent.depth is %" PRIu32 " and has to be 1",
                                 src_copy_extent.depth);
            }

            if (src_image_type != dst_image_type) {
                // if different, one must be 3D and the other 2D
                const bool valid = (src_is_2d && dst_is_3d) || (src_is_3d && dst_is_2d) || enabled_features.maintenance5;
                if (!valid) {
                    vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-07743" : "VUID-vkCmdCopyImage-srcImage-07743";
                    skip |=
                        LogError(vuid, all_objlist, region_loc,
                                 "srcImage type (%s) must be equal to dstImage type (%s) or else one must be 2D and the other 3D",
                                 string_VkImageType(src_image_type), string_VkImageType(dst_image_type));
                }
            }

            vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-01995" : "VUID-vkCmdCopyImage-srcImage-01995";
            skip |= ValidateImageFormatFeatureFlags(commandBuffer, *src_image_state, VK_FORMAT_FEATURE_2_TRANSFER_SRC_BIT,
                                                    src_image_loc, vuid);
            vuid = is_2 ? "VUID-VkCopyImageInfo2-dstImage-01996" : "VUID-vkCmdCopyImage-dstImage-01996";
            skip |= ValidateImageFormatFeatureFlags(commandBuffer, *dst_image_state, VK_FORMAT_FEATURE_2_TRANSFER_DST_BIT,
                                                    dst_image_loc, vuid);

            // Check if 2D with 3D and depth not equal to 2D layerCount
            if (src_is_2d && dst_is_3d && (src_copy_extent.depth != region.srcSubresource.layerCount)) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-01791" : "VUID-vkCmdCopyImage-srcImage-01791";
                skip |= LogError(vuid, all_objlist, region_loc,
                                 "srcImage is 2D, dstImage is 3D and extent.depth is %" PRIu32
                                 " and has to be "
                                 "srcSubresource.layerCount (%" PRIu32 ")",
                                 src_copy_extent.depth, region.srcSubresource.layerCount);
            } else if (src_is_3d && dst_is_2d && (src_copy_extent.depth != region.dstSubresource.layerCount)) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-dstImage-01792" : "VUID-vkCmdCopyImage-dstImage-01792";
                skip |= LogError(vuid, all_objlist, region_loc,
                                 "srcImage is 3D, dstImage is 2D and extent.depth is %" PRIu32
                                 " and has to be "
                                 "dstSubresource.layerCount (%" PRIu32 ")",
                                 src_copy_extent.depth, region.dstSubresource.layerCount);
            }
        } else {  // !vk_khr_maintenance1
            if ((src_is_2d || dst_is_2d) && (src_copy_extent.depth != 1)) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-apiVersion-08969" : "VUID-vkCmdCopyImage-apiVersion-08969";
                skip |= LogError(vuid, all_objlist, region_loc, "srcImage is %s is dstImage is %s but extent.depth is %" PRIu32 ".",
                                 string_VkImageType(src_image_type), string_VkImageType(dst_image_type), src_copy_extent.depth);
            }

            if (src_image_type != dst_image_type) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-apiVersion-07933" : "VUID-vkCmdCopyImage-apiVersion-07933";
                skip |= LogError(vuid, all_objlist, region_loc,
                                 "srcImage (%s) must be equal to dstImage (%s) without VK_KHR_maintenance1 enabled",
                                 string_VkImageType(src_image_type), string_VkImageType(dst_image_type));
            }
        }

        if ((!vkuFormatIsMultiplane(src_format)) && (!vkuFormatIsMultiplane(dst_format))) {
            // If neither image is multi-plane the aspectMask member of src and dst must match
            if (region.srcSubresource.aspectMask != region.dstSubresource.aspectMask) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-01551" : "VUID-vkCmdCopyImage-srcImage-01551";
                skip |= LogError(vuid, all_objlist, src_subresource_loc.dot(Field::aspectMask), "(%s) does not match %s (%s).",
                                 string_VkImageAspectFlags(region.srcSubresource.aspectMask).c_str(),
                                 dst_subresource_loc.dot(Field::aspectMask).Fields().c_str(),
                                 string_VkImageAspectFlags(region.dstSubresource.aspectMask).c_str());
            }
        } else {
            // Source image multiplane checks
            VkImageAspectFlags aspect = region.srcSubresource.aspectMask;
            if (vkuFormatIsMultiplane(src_format) && !IsOnlyOneValidPlaneAspect(src_format, aspect)) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-08713" : "VUID-vkCmdCopyImage-srcImage-08713";
                skip |= LogError(vuid, src_objlist, src_subresource_loc.dot(Field::aspectMask),
                                 "(%s) is invalid for multi-planar format %s.", string_VkImageAspectFlags(aspect).c_str(),
                                 string_VkFormat(src_format));
            }
            // Single-plane to multi-plane
            if ((!vkuFormatIsMultiplane(src_format)) && (vkuFormatIsMultiplane(dst_format)) &&
                (VK_IMAGE_ASPECT_COLOR_BIT != aspect)) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-dstImage-01557" : "VUID-vkCmdCopyImage-dstImage-01557";
                skip |=
                    LogError(vuid, all_objlist, src_subresource_loc.dot(Field::aspectMask),
                             "(%s) needs VK_IMAGE_ASPECT_COLOR_BIT\nsrcImage format %s\ndstImage format %s\n.",
                             string_VkImageAspectFlags(aspect).c_str(), string_VkFormat(src_format), string_VkFormat(dst_format));
            }

            // Dest image multiplane checks
            aspect = region.dstSubresource.aspectMask;
            if (vkuFormatIsMultiplane(dst_format) && !IsOnlyOneValidPlaneAspect(dst_format, aspect)) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-dstImage-08714" : "VUID-vkCmdCopyImage-dstImage-08714";
                skip |= LogError(vuid, dst_objlist, dst_subresource_loc.dot(Field::aspectMask),
                                 "(%s) is invalid for multi-planar format %s.", string_VkImageAspectFlags(aspect).c_str(),
                                 string_VkFormat(dst_format));
            }
            // Multi-plane to single-plane
            if ((vkuFormatIsMultiplane(src_format)) && (!vkuFormatIsMultiplane(dst_format)) &&
                (VK_IMAGE_ASPECT_COLOR_BIT != aspect)) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-01556" : "VUID-vkCmdCopyImage-srcImage-01556";
                skip |=
                    LogError(vuid, all_objlist, dst_subresource_loc.dot(Field::aspectMask),
                             "(%s) needs VK_IMAGE_ASPECT_COLOR_BIT\nsrcImage format %s\ndstImage format %s\n.",
                             string_VkImageAspectFlags(aspect).c_str(), string_VkFormat(src_format), string_VkFormat(dst_format));
            }
        }

        // The union of all source regions, and the union of all destination regions, specified by the elements of regions,
        // must not overlap in memory
        // Validation is only performed when source image is the same as destination image.
        // In the general case, the mapping between an image and its underlying memory is undefined,
        // so checking for memory overlaps is not possible.
        if (src_image_state->image() == dst_image_state->image()) {
            for (uint32_t j = 0; j < regionCount; j++) {
                if (auto intersection =
                        GetRegionIntersection(region, pRegions[j], src_image_type, vkuFormatIsMultiplane(src_format));
                    intersection.has_instersection) {
                    vuid = is_2 ? "VUID-VkCopyImageInfo2-pRegions-00124" : "VUID-vkCmdCopyImage-pRegions-00124";
                    skip |= LogError(vuid, all_objlist, loc,
                                     "pRegion[%" PRIu32 "] copy source overlaps with pRegions[%" PRIu32
                                     "] copy destination. Overlap info, with respect to image (%s): %s.",
                                     i, j, FormatHandle(srcImage).c_str(), intersection.String().c_str());
                }
            }
        }

        // Check for multi-plane format compatiblity
        if (vkuFormatIsMultiplane(src_format) || vkuFormatIsMultiplane(dst_format)) {
            const VkFormat src_plane_format =
                vkuFormatIsMultiplane(src_format)
                    ? vkuFindMultiplaneCompatibleFormat(src_format,
                                                        static_cast<VkImageAspectFlagBits>(region.srcSubresource.aspectMask))
                    : src_format;
            const VkFormat dst_plane_format =
                vkuFormatIsMultiplane(dst_format)
                    ? vkuFindMultiplaneCompatibleFormat(dst_format,
                                                        static_cast<VkImageAspectFlagBits>(region.dstSubresource.aspectMask))
                    : dst_format;
            const size_t src_format_size = vkuFormatElementSize(src_plane_format);
            const size_t dst_format_size = vkuFormatElementSize(dst_plane_format);

            // If size is still zero, then format is invalid and will be caught in another VU
            if ((src_format_size != dst_format_size) && (src_format_size != 0) && (dst_format_size != 0)) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-None-01549" : "VUID-vkCmdCopyImage-None-01549";
                skip |= LogError(vuid, all_objlist, region_loc,
                                 "srcImage format %s with aspectMask %s is not compatible with dstImage format %s aspectMask %s.",
                                 string_VkFormat(src_format), string_VkImageAspectFlags(region.srcSubresource.aspectMask).c_str(),
                                 string_VkFormat(dst_format), string_VkImageAspectFlags(region.dstSubresource.aspectMask).c_str());
            }
        }

        // track aspect mask in loop through regions
        if ((region.srcSubresource.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) {
            has_stencil_aspect = true;
        }
        if ((region.srcSubresource.aspectMask & (~VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
            has_non_stencil_aspect = true;
        }
    }

    // The formats of non-multiplane src_image and dst_image must be compatible. Formats are considered compatible if their texel
    // size in bytes is the same between both formats. For example, VK_FORMAT_R8G8B8A8_UNORM is compatible with VK_FORMAT_R32_UINT
    // because because both texels are 4 bytes in size.
    if (!vkuFormatIsMultiplane(src_format) && !vkuFormatIsMultiplane(dst_format)) {
        const char *compatible_vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-01548" : "VUID-vkCmdCopyImage-srcImage-01548";
        // Depth/stencil formats must match exactly.
        if (vkuFormatIsDepthOrStencil(src_format) || vkuFormatIsDepthOrStencil(dst_format)) {
            if (src_format != dst_format) {
                skip |= LogError(compatible_vuid, all_objlist, loc, "srcImage format (%s) is different from dstImage format (%s).",
                                 string_VkFormat(src_format), string_VkFormat(dst_format));
            }
        } else {
            if (vkuFormatElementSize(src_format) != vkuFormatElementSize(dst_format)) {
                skip |= LogError(compatible_vuid, all_objlist, loc,
                                 "srcImage format %s has size of %" PRIu32 " and dstImage format %s has size of %" PRIu32 ".",
                                 string_VkFormat(src_format), vkuFormatElementSize(src_format), string_VkFormat(dst_format),
                                 vkuFormatElementSize(dst_format));
            }
        }
    }

    if (vkuFormatIsCompressed(src_format) && vkuFormatIsCompressed(dst_format)) {
        auto src_block_extent = vkuFormatTexelBlockExtent(src_format);
        auto dst_block_extent = vkuFormatTexelBlockExtent(dst_format);
        if (src_block_extent.width != dst_block_extent.width || src_block_extent.height != dst_block_extent.height ||
            src_block_extent.depth != dst_block_extent.depth) {
            const char *compatible_vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-09247" : "VUID-vkCmdCopyImage-srcImage-09247";
            skip |= LogError(compatible_vuid, all_objlist, loc,
                             "srcImage format %s has texel block extent (w = %" PRIu32 ", h = %" PRIu32 ", d = %" PRIu32
                             ") and dstImage format %s has texel block extent (w = %" PRIu32 ", h = %" PRIu32 ", d = %" PRIu32 ").",
                             string_VkFormat(src_format), src_block_extent.width, src_block_extent.height, src_block_extent.depth,
                             string_VkFormat(dst_format), dst_block_extent.width, dst_block_extent.height, dst_block_extent.depth);
        }
    }

    // Validate that SRC & DST images have correct usage flags set
    if (!IsExtEnabled(device_extensions.vk_ext_separate_stencil_usage)) {
        vuid = is_2 ? "VUID-VkCopyImageInfo2-aspect-06662" : "VUID-vkCmdCopyImage-aspect-06662";
        skip |=
            ValidateImageUsageFlags(commandBuffer, *src_image_state, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, false, vuid, src_image_loc);
        vuid = is_2 ? "VUID-VkCopyImageInfo2-aspect-06663" : "VUID-vkCmdCopyImage-aspect-06663";
        skip |=
            ValidateImageUsageFlags(commandBuffer, *dst_image_state, VK_IMAGE_USAGE_TRANSFER_DST_BIT, false, vuid, dst_image_loc);
    } else {
        auto src_separate_stencil = vku::FindStructInPNextChain<VkImageStencilUsageCreateInfo>(src_image_state->createInfo.pNext);
        if (src_separate_stencil && has_stencil_aspect &&
            ((src_separate_stencil->stencilUsage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) == 0)) {
            vuid = is_2 ? "VUID-VkCopyImageInfo2-aspect-06664" : "VUID-vkCmdCopyImage-aspect-06664";
            skip =
                LogError(vuid, src_objlist, src_image_loc, "(%s) was created with %s but requires VK_IMAGE_USAGE_TRANSFER_SRC_BIT.",
                         FormatHandle(src_image_state->Handle()).c_str(),
                         string_VkImageUsageFlags(src_separate_stencil->stencilUsage).c_str());
        }
        if (!src_separate_stencil || has_non_stencil_aspect) {
            vuid = is_2 ? "VUID-VkCopyImageInfo2-aspect-06662" : "VUID-vkCmdCopyImage-aspect-06662";
            skip |= ValidateImageUsageFlags(commandBuffer, *src_image_state, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, false, vuid,
                                            src_image_loc);
        }

        auto dst_separate_stencil = vku::FindStructInPNextChain<VkImageStencilUsageCreateInfo>(dst_image_state->createInfo.pNext);
        if (dst_separate_stencil && has_stencil_aspect &&
            ((dst_separate_stencil->stencilUsage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0)) {
            vuid = is_2 ? "VUID-VkCopyImageInfo2-aspect-06665" : "VUID-vkCmdCopyImage-aspect-06665";
            skip =
                LogError(vuid, dst_objlist, dst_image_loc, "(%s) was created with %s but requires VK_IMAGE_USAGE_TRANSFER_DST_BIT.",
                         FormatHandle(dst_image_state->Handle()).c_str(),
                         string_VkImageUsageFlags(dst_separate_stencil->stencilUsage).c_str());
        }
        if (!dst_separate_stencil || has_non_stencil_aspect) {
            vuid = is_2 ? "VUID-vkCmdCopyImage-aspect-06663" : "VUID-vkCmdCopyImage-aspect-06663";
            skip |= ValidateImageUsageFlags(commandBuffer, *dst_image_state, VK_IMAGE_USAGE_TRANSFER_DST_BIT, false, vuid,
                                            dst_image_loc);
        }
    }

    // Source and dest image sample counts must match
    if (src_image_state->createInfo.samples != dst_image_state->createInfo.samples) {
        vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-00136" : "VUID-vkCmdCopyImage-srcImage-00136";
        skip |= LogError(vuid, all_objlist, src_image_loc, "was created with (%s) but the dstImage was created with (%s).",
                         string_VkSampleCountFlagBits(src_image_state->createInfo.samples),
                         string_VkSampleCountFlagBits(dst_image_state->createInfo.samples));
    }

    vuid = is_2 ? "VUID-vkCmdCopyImage2-commandBuffer-01825" : "VUID-vkCmdCopyImage-commandBuffer-01825";
    skip |= ValidateProtectedImage(cb_state, *src_image_state, src_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdCopyImage2-commandBuffer-01826" : "VUID-vkCmdCopyImage-commandBuffer-01826";
    skip |= ValidateProtectedImage(cb_state, *dst_image_state, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdCopyImage2-commandBuffer-01827" : "VUID-vkCmdCopyImage-commandBuffer-01827";
    skip |= ValidateUnprotectedImage(cb_state, *dst_image_state, dst_image_loc, vuid);

    skip |= ValidateCmd(cb_state, loc);

    if (!IsValueIn(srcImageLayout,
                   {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL})) {
        vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImageLayout-01917" : "VUID-vkCmdCopyImage-srcImageLayout-01917";
        const LogObjectList objlist(cb_state.commandBuffer(), src_image_state->Handle());
        skip |= LogError(vuid, objlist, loc.dot(Field::srcImageLayout), "is %s.", string_VkImageLayout(srcImageLayout));
    }

    if (!IsValueIn(dstImageLayout,
                   {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL})) {
        vuid = is_2 ? "VUID-VkCopyImageInfo2-dstImageLayout-01395" : "VUID-vkCmdCopyImage-dstImageLayout-01395";
        const LogObjectList objlist(cb_state.commandBuffer(), dst_image_state->Handle());
        skip |= LogError(vuid, objlist, loc.dot(Field::dstImageLayout), "is %s.", string_VkImageLayout(dstImageLayout));
    }

    const bool same_image = (src_image_state == dst_image_state);
    for (uint32_t i = 0; i < regionCount; ++i) {
        // When performing copy from and to same subresource, VK_IMAGE_LAYOUT_GENERAL is the only option
        const Location region_loc = loc.dot(Field::pRegions, i);
        const RegionType region = pRegions[i];
        const VkImageSubresourceLayers &src_subresource = region.srcSubresource;
        const VkImageSubresourceLayers &dst_subresource = region.dstSubresource;

        const bool same_subresource = (same_image && (src_subresource.mipLevel == dst_subresource.mipLevel) &&
                                       RangesIntersect(src_subresource.baseArrayLayer, src_subresource.layerCount,
                                                       dst_subresource.baseArrayLayer, dst_subresource.layerCount));
        if (same_subresource) {
            if (!IsValueIn(srcImageLayout, {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_GENERAL}) ||
                !IsValueIn(dstImageLayout, {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_GENERAL})) {
                vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImage-09460" : "VUID-vkCmdCopyImage-srcImage-09460";
                const LogObjectList objlist(cb_state.commandBuffer(), src_image_state->Handle());
                skip |= LogError(vuid, objlist, loc,
                                 "copying to same VkImage (miplevel = %u, srcLayer[%u,%u), dstLayer[%u,%u)), but srcImageLayout is "
                                 "%s and dstImageLayout is %s",
                                 src_subresource.mipLevel, src_subresource.baseArrayLayer,
                                 src_subresource.baseArrayLayer + src_subresource.layerCount, dst_subresource.baseArrayLayer,
                                 dst_subresource.baseArrayLayer + dst_subresource.layerCount, string_VkImageLayout(srcImageLayout),
                                 string_VkImageLayout(dstImageLayout));
            }
        }

        vuid = is_2 ? "VUID-VkCopyImageInfo2-srcImageLayout-00128" : "VUID-vkCmdCopyImage-srcImageLayout-00128";
        skip |= VerifyImageLayoutSubresource(cb_state, *src_image_state, src_subresource, srcImageLayout, src_image_loc, vuid);
        vuid = is_2 ? "VUID-VkCopyImageInfo2-dstImageLayout-00133" : "VUID-vkCmdCopyImage-dstImageLayout-00133";
        skip |= VerifyImageLayoutSubresource(cb_state, *dst_image_state, dst_subresource, dstImageLayout, dst_image_loc, vuid);
        skip |= ValidateCopyImageTransferGranularityRequirements(cb_state, *src_image_state, *dst_image_state, &region, region_loc);
    }

    return skip;
}

bool CoreChecks::PreCallValidateCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                             VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                             const VkImageCopy *pRegions, const ErrorObject &error_obj) const {
    return ValidateCmdCopyImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions,
                                error_obj.location);
}

bool CoreChecks::PreCallValidateCmdCopyImage2KHR(VkCommandBuffer commandBuffer, const VkCopyImageInfo2KHR *pCopyImageInfo,
                                                 const ErrorObject &error_obj) const {
    return PreCallValidateCmdCopyImage2(commandBuffer, pCopyImageInfo, error_obj);
}

bool CoreChecks::PreCallValidateCmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2 *pCopyImageInfo,
                                              const ErrorObject &error_obj) const {
    return ValidateCmdCopyImage(commandBuffer, pCopyImageInfo->srcImage, pCopyImageInfo->srcImageLayout, pCopyImageInfo->dstImage,
                                pCopyImageInfo->dstImageLayout, pCopyImageInfo->regionCount, pCopyImageInfo->pRegions,
                                error_obj.location.dot(Field::pCopyImageInfo));
}

void CoreChecks::PreCallRecordCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                           VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                           const VkImageCopy *pRegions, const RecordObject &record_obj) {
    StateTracker::PreCallRecordCmdCopyImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount,
                                            pRegions, record_obj);
    auto cb_state_ptr = GetWrite<vvl::CommandBuffer>(commandBuffer);
    auto src_image_state = Get<vvl::Image>(srcImage);
    auto dst_image_state = Get<vvl::Image>(dstImage);
    if (cb_state_ptr && src_image_state && dst_image_state) {
        // Make sure that all image slices are updated to correct layout
        for (uint32_t i = 0; i < regionCount; ++i) {
            cb_state_ptr->SetImageInitialLayout(*src_image_state, pRegions[i].srcSubresource, srcImageLayout);
            cb_state_ptr->SetImageInitialLayout(*dst_image_state, pRegions[i].dstSubresource, dstImageLayout);
        }
    }
}

void CoreChecks::RecordCmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2 *pCopyImageInfo) {
    auto cb_state_ptr = GetWrite<vvl::CommandBuffer>(commandBuffer);
    auto src_image_state = Get<vvl::Image>(pCopyImageInfo->srcImage);
    auto dst_image_state = Get<vvl::Image>(pCopyImageInfo->dstImage);
    if (cb_state_ptr && src_image_state && dst_image_state) {
        // Make sure that all image slices are updated to correct layout
        for (uint32_t i = 0; i < pCopyImageInfo->regionCount; ++i) {
            cb_state_ptr->SetImageInitialLayout(*src_image_state, pCopyImageInfo->pRegions[i].srcSubresource,
                                                pCopyImageInfo->srcImageLayout);
            cb_state_ptr->SetImageInitialLayout(*dst_image_state, pCopyImageInfo->pRegions[i].dstSubresource,
                                                pCopyImageInfo->dstImageLayout);
        }
    }
}

void CoreChecks::PreCallRecordCmdCopyImage2KHR(VkCommandBuffer commandBuffer, const VkCopyImageInfo2KHR *pCopyImageInfo,
                                               const RecordObject &record_obj) {
    PreCallRecordCmdCopyImage2(commandBuffer, pCopyImageInfo, record_obj);
}

void CoreChecks::PreCallRecordCmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2 *pCopyImageInfo,
                                            const RecordObject &record_obj) {
    StateTracker::PreCallRecordCmdCopyImage2(commandBuffer, pCopyImageInfo, record_obj);
    RecordCmdCopyImage2(commandBuffer, pCopyImageInfo);
}

template <typename RegionType>
void CoreChecks::RecordCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount,
                                     const RegionType *pRegions, const Location &loc) {
    const bool is_2 = loc.function == Func::vkCmdCopyBuffer2 || loc.function == Func::vkCmdCopyBuffer2KHR;
    const char *vuid = is_2 ? "VUID-VkCopyBufferInfo2-pRegions-00117" : "VUID-vkCmdCopyBuffer-pRegions-00117";

    auto src_buffer_state = Get<vvl::Buffer>(srcBuffer);
    auto dst_buffer_state = Get<vvl::Buffer>(dstBuffer);
    if (src_buffer_state->sparse || dst_buffer_state->sparse) {
        auto cb_state_ptr = Get<vvl::CommandBuffer>(commandBuffer);

        std::vector<sparse_container::range<VkDeviceSize>> src_ranges;
        std::vector<sparse_container::range<VkDeviceSize>> dst_ranges;

        for (uint32_t i = 0; i < regionCount; ++i) {
            const RegionType &region = pRegions[i];
            src_ranges.emplace_back(sparse_container::range<VkDeviceSize>{region.srcOffset, region.srcOffset + region.size});
            dst_ranges.emplace_back(sparse_container::range<VkDeviceSize>{region.dstOffset, region.dstOffset + region.size});
        }

        auto queue_submit_validation = [this, commandBuffer, src_buffer_state, dst_buffer_state, regionCount, src_ranges,
                                        dst_ranges, loc,
                                        vuid](const ValidationStateTracker &device_data, const class vvl::Queue &queue_state,
                                              const vvl::CommandBuffer &cb_state) -> bool {
            bool skip = false;
            for (uint32_t i = 0; i < regionCount; ++i) {
                const auto &src = src_ranges[i];
                for (uint32_t j = 0; j < regionCount; ++j) {
                    const auto &dst = dst_ranges[j];
                    if (const auto [memory, overlap_range] =
                            src_buffer_state->GetResourceMemoryOverlap(src, dst_buffer_state.get(), dst);
                        memory != VK_NULL_HANDLE) {
                        const LogObjectList objlist(commandBuffer, src_buffer_state->buffer(), dst_buffer_state->buffer(), memory);
                        skip |= this->LogError(vuid, objlist, loc,
                                               "Memory (%s) has copy overlap on range %s. Source "
                                               "buffer range is pRegions[%" PRIu32
                                               "] (%s), destination buffer range is pRegions[%" PRIu32 "] (%s).",
                                               FormatHandle(memory).c_str(), string_range(overlap_range).c_str(), i,
                                               string_range(src).c_str(), j, string_range(dst).c_str());
                    }
                }
            }

            return skip;
        };

        cb_state_ptr->queue_submit_functions.emplace_back(queue_submit_validation);
    }
}

void CoreChecks::PreCallRecordCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer,
                                            uint32_t regionCount, const VkBufferCopy *pRegions, const RecordObject &record_obj) {
    const Location loc(Func::vkCmdCopyBuffer);
    RecordCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, regionCount, pRegions, loc);
}

void CoreChecks::PreCallRecordCmdCopyBuffer2KHR(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2KHR *pCopyBufferInfo,
                                                const RecordObject &record_obj) {
    const Location loc(Func::vkCmdCopyBuffer2KHR);
    RecordCmdCopyBuffer(commandBuffer, pCopyBufferInfo->srcBuffer, pCopyBufferInfo->dstBuffer, pCopyBufferInfo->regionCount,
                        pCopyBufferInfo->pRegions, loc);
}

void CoreChecks::PreCallRecordCmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2 *pCopyBufferInfo,
                                             const RecordObject &record_obj) {
    const Location loc(Func::vkCmdCopyBuffer2);
    RecordCmdCopyBuffer(commandBuffer, pCopyBufferInfo->srcBuffer, pCopyBufferInfo->dstBuffer, pCopyBufferInfo->regionCount,
                        pCopyBufferInfo->pRegions, loc);
}

template <typename T>
VkImageSubresourceLayers GetImageSubresource(T data, bool is_src) {
    return data.imageSubresource;
}
template <>
VkImageSubresourceLayers GetImageSubresource<VkImageCopy2>(VkImageCopy2 data, bool is_src) {
    return is_src ? data.srcSubresource : data.dstSubresource;
}
template <typename T>
VkOffset3D GetOffset(T data, bool is_src) {
    return data.imageOffset;
}
template <>
VkOffset3D GetOffset<VkImageCopy2>(VkImageCopy2 data, bool is_src) {
    return is_src ? data.srcOffset : data.dstOffset;
}
template <typename T>
VkExtent3D GetExtent(T data) {
    return data.imageExtent;
}
template <>
VkExtent3D GetExtent<VkImageCopy2>(VkImageCopy2 data) {
    return data.extent;
}
template <typename HandleT, typename RegionType>
bool CoreChecks::ValidateImageBounds(const HandleT handle, const vvl::Image &image_state, const uint32_t regionCount,
                                     const RegionType *pRegions, const Location &loc, const char *vuid, bool is_src) const {
    bool skip = false;
    const VkImageCreateInfo *image_info = &(image_state.createInfo);

    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const RegionType region = pRegions[i];
        VkExtent3D extent = GetExtent(region);
        VkOffset3D offset = GetOffset(region, is_src);
        VkImageSubresourceLayers subresource_layout = GetImageSubresource(region, is_src);

        VkExtent3D image_extent = image_state.GetEffectiveSubresourceExtent(subresource_layout);

        // If we're using a blocked image format, valid extent is rounded up to multiple of block size (per
        // vkspec.html#_common_operation)
        if (vkuFormatIsBlockedImage(image_info->format)) {
            auto block_extent = vkuFormatTexelBlockExtent(image_info->format);
            if (image_extent.width % block_extent.width) {
                image_extent.width += (block_extent.width - (image_extent.width % block_extent.width));
            }
            if (image_extent.height % block_extent.height) {
                image_extent.height += (block_extent.height - (image_extent.height % block_extent.height));
            }
            if (image_extent.depth % block_extent.depth) {
                image_extent.depth += (block_extent.depth - (image_extent.depth % block_extent.depth));
            }
        }

        if (0 != ExceedsBounds(&offset, &extent, &image_extent)) {
            const LogObjectList objlist(handle, image_state.Handle());
            skip |= LogError(vuid, objlist, region_loc,
                             "exceeds image bounds\n"
                             "region extent (w = %" PRIu32 ", h = %" PRIu32 ", d = %" PRIu32
                             ")\n"
                             "region offset (x = %" PRId32 ", y = %" PRId32 ", z = %" PRId32
                             ")\n"
                             "image extent (w = %" PRIu32 ", h = %" PRIu32 ", d = %" PRIu32 ")\n",
                             extent.width, extent.height, extent.depth, offset.x, offset.y, offset.z, image_extent.width,
                             image_extent.height, image_extent.depth);
        }
    }

    return skip;
}

template <typename RegionType>
bool CoreChecks::ValidateBufferBounds(VkCommandBuffer cb, const vvl::Image &image_state, const vvl::Buffer &buff_state,
                                      uint32_t regionCount, const RegionType *pRegions, const Location &loc,
                                      const char *vuid) const {
    bool skip = false;

    const VkDeviceSize buffer_size = buff_state.createInfo.size;

    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const RegionType region = pRegions[i];
        const VkDeviceSize buffer_copy_size =
            GetBufferSizeFromCopyImage(region, image_state.createInfo.format, image_state.createInfo.arrayLayers);
        // This blocks against invalid VkBufferCopyImage that already have been caught elsewhere
        if (buffer_copy_size != 0) {
            const VkDeviceSize max_buffer_copy = buffer_copy_size + region.bufferOffset;
            if (buffer_size < max_buffer_copy) {
                const LogObjectList objlist(cb, buff_state.Handle());
                skip |= LogError(vuid, objlist, region_loc,
                                 "is trying to copy %" PRIu64 " bytes plus %" PRIu64
                                 " offset to/from the VkBuffer (%s) which exceeds the VkBuffer total size of %" PRIu64 " bytes.",
                                 buffer_copy_size, region.bufferOffset, FormatHandle(buff_state).c_str(), buffer_size);
            }
        }
    }

    return skip;
}

template <typename HandleT>
// Validate that an image's sampleCount matches the requirement for a specific API call
bool CoreChecks::ValidateImageSampleCount(const HandleT handle, const vvl::Image &image_state, VkSampleCountFlagBits sample_count,
                                          const Location &loc, const std::string &vuid) const {
    bool skip = false;
    if (image_state.createInfo.samples != sample_count) {
        const LogObjectList objlist(handle, image_state.Handle());
        skip = LogError(vuid, objlist, loc, "%s was created with a sample count of %s but must be %s.",
                        FormatHandle(image_state).c_str(), string_VkSampleCountFlagBits(image_state.createInfo.samples),
                        string_VkSampleCountFlagBits(sample_count));
    }
    return skip;
}

template <typename RegionType>
bool CoreChecks::ValidateImageBufferCopyMemoryOverlap(const vvl::CommandBuffer &cb_state, uint32_t regionCount,
                                                      const RegionType *pRegions, const vvl::Image &image_state,
                                                      const vvl::Buffer &buffer_state, const Location &loc, bool image_to_buffer,
                                                      bool is_2) const {
    bool skip = false;

    for (uint32_t i = 0; i < regionCount; ++i) {
        const RegionType &region = pRegions[i];
        auto texel_size = vkuFormatTexelSizeWithAspect(image_state.createInfo.format,
                                                       static_cast<VkImageAspectFlagBits>(region.imageSubresource.aspectMask));
        VkDeviceSize image_offset;
        if (image_state.createInfo.tiling == VK_IMAGE_TILING_LINEAR) {
            // Can only know actual offset for linearly tiled images
            VkImageSubresource isr = {};
            isr.arrayLayer = region.imageSubresource.baseArrayLayer;
            isr.aspectMask = region.imageSubresource.aspectMask;
            isr.mipLevel = region.imageSubresource.mipLevel;
            VkSubresourceLayout srl = {};
            DispatchGetImageSubresourceLayout(device, image_state.image(), &isr, &srl);
            if (image_state.createInfo.arrayLayers == 1) srl.arrayPitch = 0;
            if (image_state.createInfo.imageType != VK_IMAGE_TYPE_3D) srl.depthPitch = 0;
            image_offset = (region.imageSubresource.baseArrayLayer * srl.arrayPitch) +
                           static_cast<VkDeviceSize>((region.imageOffset.x * texel_size)) + (region.imageOffset.y * srl.rowPitch) +
                           (region.imageOffset.z * srl.depthPitch) + srl.offset;
        } else {
            image_offset =
                static_cast<VkDeviceSize>(region.imageOffset.x * region.imageOffset.y * region.imageOffset.z * texel_size);
        }
        VkDeviceSize copy_size;
        if (region.bufferRowLength != 0 && region.bufferImageHeight != 0) {
            copy_size = static_cast<VkDeviceSize>(((region.bufferRowLength * region.bufferImageHeight) * texel_size));
        } else {
            copy_size = static_cast<VkDeviceSize>(
                ((region.imageExtent.width * region.imageExtent.height * region.imageExtent.depth) * texel_size));
        }
        auto image_region = sparse_container::range<VkDeviceSize>{image_offset, image_offset + copy_size};
        auto buffer_region = sparse_container::range<VkDeviceSize>{region.bufferOffset, region.bufferOffset + copy_size};
        if (image_state.DoesResourceMemoryOverlap(image_region, &buffer_state, buffer_region)) {
            const Location region_loc = loc.dot(Field::pRegions, i);
            const LogObjectList objlist(cb_state.commandBuffer(), image_state.image());
            skip |= LogError(GetBufferMemoryImageCopyCommandVUID("00173", image_to_buffer, is_2), objlist, region_loc,
                             "Detected overlap between source and dest regions in memory.");
        }
    }
    return skip;
}

template <typename RegionType>
bool CoreChecks::ValidateCmdCopyImageToBuffer(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                              VkBuffer dstBuffer, uint32_t regionCount, const RegionType *pRegions,
                                              const Location &loc) const {
    bool skip = false;
    auto cb_state_ptr = GetRead<vvl::CommandBuffer>(commandBuffer);
    auto src_image_state = Get<vvl::Image>(srcImage);
    auto dst_buffer_state = Get<vvl::Buffer>(dstBuffer);
    if (!cb_state_ptr || !src_image_state || !dst_buffer_state) {
        return skip;
    }
    const vvl::CommandBuffer &cb_state = *cb_state_ptr;

    const bool is_2 = loc.function == Func::vkCmdCopyImageToBuffer2 || loc.function == Func::vkCmdCopyImageToBuffer2KHR;
    const char *vuid;
    const Location src_image_loc = loc.dot(Field::srcImage);
    const Location dst_buffer_loc = loc.dot(Field::dstBuffer);

    skip |= ValidateBufferImageCopyData(cb_state, regionCount, pRegions, *src_image_state, loc);

    skip |= ValidateCmd(cb_state, loc);

    // Command pool must support graphics, compute, or transfer operations
    const auto pool = cb_state.command_pool;

    VkQueueFlags queue_flags = physical_device_state->queue_family_properties[pool->queueFamilyIndex].queueFlags;

    if (0 == (queue_flags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT))) {
        const LogObjectList objlist(cb_state.createInfo.commandPool, commandBuffer, srcImage, dstBuffer);
        vuid = is_2 ? "VUID-vkCmdCopyImageToBuffer2-commandBuffer-cmdpool" : "VUID-vkCmdCopyImageToBuffer-commandBuffer-cmdpool";
        skip |= LogError(vuid, objlist, loc, "command buffer allocated from a pool with queue type %s.",
                         string_VkQueueFlags(queue_flags).c_str());
    }

    vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-pRegions-04566" : "VUID-vkCmdCopyImageToBuffer-imageSubresource-07970";
    skip |= ValidateImageBounds(commandBuffer, *src_image_state, regionCount, pRegions, loc, vuid, true);
    vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-pRegions-00183" : "VUID-vkCmdCopyImageToBuffer-pRegions-00183";
    skip |= ValidateBufferBounds(commandBuffer, *src_image_state, *dst_buffer_state, regionCount, pRegions, loc, vuid);

    vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-srcImage-07973" : "VUID-vkCmdCopyImageToBuffer-srcImage-07973";
    skip |= ValidateImageSampleCount(commandBuffer, *src_image_state, VK_SAMPLE_COUNT_1_BIT, src_image_loc, vuid);

    vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-srcImage-07966" : "VUID-vkCmdCopyImageToBuffer-srcImage-07966";
    skip |= ValidateMemoryIsBoundToImage(LogObjectList(commandBuffer, srcImage), *src_image_state, src_image_loc, vuid);
    vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-dstBuffer-00192" : "VUID-vkCmdCopyImageToBuffer-dstBuffer-00192";
    skip |= ValidateMemoryIsBoundToBuffer(commandBuffer, *dst_buffer_state, dst_buffer_loc, vuid);

    // Validate that SRC image & DST buffer have correct usage flags set
    vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-srcImage-00186" : "VUID-vkCmdCopyImageToBuffer-srcImage-00186";
    skip |= ValidateImageUsageFlags(commandBuffer, *src_image_state, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, true, vuid, src_image_loc);
    vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-dstBuffer-00191" : "VUID-vkCmdCopyImageToBuffer-dstBuffer-00191";
    skip |= ValidateBufferUsageFlags(LogObjectList(commandBuffer, dstBuffer), *dst_buffer_state, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
                                     true, vuid, dst_buffer_loc);
    vuid = is_2 ? "VUID-vkCmdCopyImageToBuffer2-commandBuffer-01831" : "VUID-vkCmdCopyImageToBuffer-commandBuffer-01831";
    skip |= ValidateProtectedImage(cb_state, *src_image_state, src_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdCopyImageToBuffer2-commandBuffer-01832" : "VUID-vkCmdCopyImageToBuffer-commandBuffer-01832";
    skip |= ValidateProtectedBuffer(cb_state, *dst_buffer_state, dst_buffer_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdCopyImageToBuffer2-commandBuffer-01833" : "VUID-vkCmdCopyImageToBuffer-commandBuffer-01833";
    skip |= ValidateUnprotectedBuffer(cb_state, *dst_buffer_state, dst_buffer_loc, vuid);

    // Validation for VK_EXT_fragment_density_map
    if (src_image_state->createInfo.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
        const LogObjectList objlist(commandBuffer, srcImage, dstBuffer);
        vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-srcImage-07969" : "VUID-vkCmdCopyImageToBuffer-srcImage-07969";
        skip |= LogError(vuid, objlist, src_image_loc, "was created with VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT.");
    }

    if (IsExtEnabled(device_extensions.vk_khr_maintenance1)) {
        vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-srcImage-01998" : "VUID-vkCmdCopyImageToBuffer-srcImage-01998";
        skip |= ValidateImageFormatFeatureFlags(commandBuffer, *src_image_state, VK_FORMAT_FEATURE_2_TRANSFER_SRC_BIT,
                                                src_image_loc, vuid);
    }

    if (!IsValueIn(srcImageLayout,
                   {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL})) {
        vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-srcImageLayout-01397" : "VUID-vkCmdCopyImageToBuffer-srcImageLayout-01397";
        const LogObjectList objlist(cb_state.commandBuffer(), src_image_state->Handle());
        skip |= LogError(vuid, objlist, loc.dot(Field::srcImageLayout), "is %s.", string_VkImageLayout(srcImageLayout));
    }

    for (uint32_t i = 0; i < regionCount; ++i) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const Location subresource_loc = region_loc.dot(Field::imageSubresource);
        const RegionType region = pRegions[i];
        skip |= ValidateImageSubresourceLayers(cb_state.commandBuffer(), &region.imageSubresource, subresource_loc);
        vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-srcImageLayout-00189" : "VUID-vkCmdCopyImageToBuffer-srcImageLayout-00189";
        skip |=
            VerifyImageLayoutSubresource(cb_state, *src_image_state, region.imageSubresource, srcImageLayout, src_image_loc, vuid);
        vuid = is_2 ? "VUID-vkCmdCopyImageToBuffer2-imageOffset-07747" : "VUID-vkCmdCopyImageToBuffer-imageOffset-07747";
        skip |= ValidateCopyBufferImageTransferGranularityRequirements(cb_state, *src_image_state, &region, region_loc, vuid);
        vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-imageSubresource-07967" : "VUID-vkCmdCopyImageToBuffer-imageSubresource-07967";
        skip |= ValidateImageMipLevel(commandBuffer, *src_image_state, region.imageSubresource.mipLevel,
                                      subresource_loc.dot(Field::mipLevel), vuid);
        vuid = is_2 ? "VUID-VkCopyImageToBufferInfo2-imageSubresource-07968" : "VUID-vkCmdCopyImageToBuffer-imageSubresource-07968";
        skip |= ValidateImageArrayLayerRange(commandBuffer, *src_image_state, region.imageSubresource.baseArrayLayer,
                                             region.imageSubresource.layerCount, subresource_loc, vuid);
    }

    // TODO 6898 - ValidateImageBufferCopyMemoryOverlap logic has issues
    // if (!skip && !dst_buffer_state->sparse && !src_image_state->sparse) {
    //     skip |= ValidateImageBufferCopyMemoryOverlap(*cb_state_ptr, regionCount, pRegions, *src_image_state, *dst_buffer_state,
    //     loc, true, is_2);
    // }
    return skip;
}

bool CoreChecks::PreCallValidateCmdCopyImageToBuffer(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                                     VkBuffer dstBuffer, uint32_t regionCount, const VkBufferImageCopy *pRegions,
                                                     const ErrorObject &error_obj) const {
    return ValidateCmdCopyImageToBuffer(commandBuffer, srcImage, srcImageLayout, dstBuffer, regionCount, pRegions,
                                        error_obj.location);
}

bool CoreChecks::PreCallValidateCmdCopyImageToBuffer2KHR(VkCommandBuffer commandBuffer,
                                                         const VkCopyImageToBufferInfo2KHR *pCopyImageToBufferInfo,
                                                         const ErrorObject &error_obj) const {
    return PreCallValidateCmdCopyImageToBuffer2(commandBuffer, pCopyImageToBufferInfo, error_obj);
}

bool CoreChecks::PreCallValidateCmdCopyImageToBuffer2(VkCommandBuffer commandBuffer,
                                                      const VkCopyImageToBufferInfo2 *pCopyImageToBufferInfo,
                                                      const ErrorObject &error_obj) const {
    return ValidateCmdCopyImageToBuffer(commandBuffer, pCopyImageToBufferInfo->srcImage, pCopyImageToBufferInfo->srcImageLayout,
                                        pCopyImageToBufferInfo->dstBuffer, pCopyImageToBufferInfo->regionCount,
                                        pCopyImageToBufferInfo->pRegions, error_obj.location.dot(Field::pCopyImageToBufferInfo));
}

void CoreChecks::PreCallRecordCmdCopyImageToBuffer(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                                   VkBuffer dstBuffer, uint32_t regionCount, const VkBufferImageCopy *pRegions,
                                                   const RecordObject &record_obj) {
    StateTracker::PreCallRecordCmdCopyImageToBuffer(commandBuffer, srcImage, srcImageLayout, dstBuffer, regionCount, pRegions,
                                                    record_obj);

    auto cb_state_ptr = GetWrite<vvl::CommandBuffer>(commandBuffer);
    auto src_image_state = Get<vvl::Image>(srcImage);
    if (cb_state_ptr && src_image_state) {
        // Make sure that all image slices record referenced layout
        for (uint32_t i = 0; i < regionCount; ++i) {
            cb_state_ptr->SetImageInitialLayout(*src_image_state, pRegions[i].imageSubresource, srcImageLayout);
        }
    }
}

void CoreChecks::PreCallRecordCmdCopyImageToBuffer2KHR(VkCommandBuffer commandBuffer,
                                                       const VkCopyImageToBufferInfo2KHR *pCopyImageToBufferInfo,
                                                       const RecordObject &record_obj) {
    PreCallRecordCmdCopyImageToBuffer2(commandBuffer, pCopyImageToBufferInfo, record_obj);
}

void CoreChecks::PreCallRecordCmdCopyImageToBuffer2(VkCommandBuffer commandBuffer,
                                                    const VkCopyImageToBufferInfo2 *pCopyImageToBufferInfo,
                                                    const RecordObject &record_obj) {
    StateTracker::PreCallRecordCmdCopyImageToBuffer2(commandBuffer, pCopyImageToBufferInfo, record_obj);

    auto cb_state_ptr = GetWrite<vvl::CommandBuffer>(commandBuffer);
    auto src_image_state = Get<vvl::Image>(pCopyImageToBufferInfo->srcImage);
    if (cb_state_ptr && src_image_state) {
        // Make sure that all image slices record referenced layout
        for (uint32_t i = 0; i < pCopyImageToBufferInfo->regionCount; ++i) {
            cb_state_ptr->SetImageInitialLayout(*src_image_state, pCopyImageToBufferInfo->pRegions[i].imageSubresource,
                                                pCopyImageToBufferInfo->srcImageLayout);
        }
    }
}

template <typename RegionType>
bool CoreChecks::ValidateCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage,
                                              VkImageLayout dstImageLayout, uint32_t regionCount, const RegionType *pRegions,
                                              const Location &loc) const {
    bool skip = false;
    auto cb_state_ptr = GetRead<vvl::CommandBuffer>(commandBuffer);
    auto src_buffer_state = Get<vvl::Buffer>(srcBuffer);
    auto dst_image_state = Get<vvl::Image>(dstImage);
    if (!cb_state_ptr || !src_buffer_state || !dst_image_state) {
        return skip;
    }
    const vvl::CommandBuffer &cb_state = *cb_state_ptr;

    const bool is_2 = loc.function == Func::vkCmdCopyBufferToImage2 || loc.function == Func::vkCmdCopyBufferToImage2KHR;
    const char *vuid;
    const Location src_buffer_loc = loc.dot(Field::srcBuffer);
    const Location dst_image_loc = loc.dot(Field::dstImage);

    skip |= ValidateBufferImageCopyData(cb_state, regionCount, pRegions, *dst_image_state, loc);

    skip |= ValidateCmd(cb_state, loc);

    vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-pRegions-04565" : "VUID-vkCmdCopyBufferToImage-imageSubresource-07970";
    skip |= ValidateImageBounds(commandBuffer, *dst_image_state, regionCount, pRegions, loc, vuid, false);
    vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-pRegions-00171" : "VUID-vkCmdCopyBufferToImage-pRegions-00171";
    skip |= ValidateBufferBounds(commandBuffer, *dst_image_state, *src_buffer_state, regionCount, pRegions, loc, vuid);

    vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-dstImage-07973" : "VUID-vkCmdCopyBufferToImage-dstImage-07973";
    skip |= ValidateImageSampleCount(cb_state.commandBuffer(), *dst_image_state, VK_SAMPLE_COUNT_1_BIT, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-srcBuffer-00176" : "VUID-vkCmdCopyBufferToImage-srcBuffer-00176";
    skip |= ValidateMemoryIsBoundToBuffer(commandBuffer, *src_buffer_state, src_buffer_loc, vuid);
    vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-dstImage-07966" : "VUID-vkCmdCopyBufferToImage-dstImage-07966";
    skip |= ValidateMemoryIsBoundToImage(LogObjectList(commandBuffer, dstImage), *dst_image_state, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-srcBuffer-00174" : "VUID-vkCmdCopyBufferToImage-srcBuffer-00174";
    skip |= ValidateBufferUsageFlags(LogObjectList(commandBuffer, srcBuffer), *src_buffer_state, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
                                     true, vuid, src_buffer_loc);
    vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-dstImage-00177" : "VUID-vkCmdCopyBufferToImage-dstImage-00177";
    skip |= ValidateImageUsageFlags(commandBuffer, *dst_image_state, VK_IMAGE_USAGE_TRANSFER_DST_BIT, true, vuid, dst_image_loc);
    vuid = is_2 ? "VUID-vkCmdCopyBufferToImage2-commandBuffer-01828" : "VUID-vkCmdCopyBufferToImage-commandBuffer-01828";
    skip |= ValidateProtectedBuffer(cb_state, *src_buffer_state, src_buffer_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdCopyBufferToImage2-commandBuffer-01829" : "VUID-vkCmdCopyBufferToImage-commandBuffer-01829";
    skip |= ValidateProtectedImage(cb_state, *dst_image_state, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdCopyBufferToImage-commandBuffer-01830" : "VUID-vkCmdCopyBufferToImage-commandBuffer-01830";
    skip |= ValidateUnprotectedImage(cb_state, *dst_image_state, dst_image_loc, vuid);

    // Validation for VK_EXT_fragment_density_map
    if (dst_image_state->createInfo.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
        const LogObjectList objlist(commandBuffer, srcBuffer, dstImage);
        vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-dstImage-07969" : "VUID-vkCmdCopyBufferToImage-dstImage-07969";
        skip |= LogError(vuid, objlist, dst_image_loc, "was created with VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT.");
    }

    if (IsExtEnabled(device_extensions.vk_khr_maintenance1)) {
        vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-dstImage-01997" : "VUID-vkCmdCopyBufferToImage-dstImage-01997";
        skip |= ValidateImageFormatFeatureFlags(commandBuffer, *dst_image_state, VK_FORMAT_FEATURE_2_TRANSFER_DST_BIT,
                                                dst_image_loc, vuid);
    }

    if (!IsValueIn(dstImageLayout,
                   {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL})) {
        vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-dstImageLayout-01396" : "VUID-vkCmdCopyBufferToImage-dstImageLayout-01396";
        const LogObjectList objlist(cb_state.commandBuffer(), dst_image_state->Handle());
        skip |= LogError(vuid, objlist, loc.dot(Field::dstImageLayout), "is %s.", string_VkImageLayout(dstImageLayout));
    }

    for (uint32_t i = 0; i < regionCount; ++i) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const Location subresource_loc = region_loc.dot(Field::imageSubresource);
        const RegionType region = pRegions[i];
        skip |= ValidateImageSubresourceLayers(cb_state.commandBuffer(), &region.imageSubresource, subresource_loc);
        vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-dstImageLayout-00180" : "VUID-vkCmdCopyBufferToImage-dstImageLayout-00180";
        skip |=
            VerifyImageLayoutSubresource(cb_state, *dst_image_state, region.imageSubresource, dstImageLayout, dst_image_loc, vuid);
        vuid = is_2 ? "VUID-vkCmdCopyBufferToImage2-imageOffset-07738" : "VUID-vkCmdCopyBufferToImage-imageOffset-07738";
        skip |= ValidateCopyBufferImageTransferGranularityRequirements(cb_state, *dst_image_state, &region, region_loc, vuid);
        vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-imageSubresource-07967" : "VUID-vkCmdCopyBufferToImage-imageSubresource-07967";
        skip |= ValidateImageMipLevel(commandBuffer, *dst_image_state, region.imageSubresource.mipLevel,
                                      subresource_loc.dot(Field::mipLevel), vuid);
        vuid = is_2 ? "VUID-VkCopyBufferToImageInfo2-imageSubresource-07968" : "VUID-vkCmdCopyBufferToImage-imageSubresource-07968";
        skip |= ValidateImageArrayLayerRange(commandBuffer, *dst_image_state, region.imageSubresource.baseArrayLayer,
                                             region.imageSubresource.layerCount, subresource_loc, vuid);

        const VkImageAspectFlags region_aspect_mask = region.imageSubresource.aspectMask;
        if ((region_aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
            std::stringstream ss;
            ss << "is " << string_VkImageAspectFlags(region_aspect_mask) << ", but is ";
            vuid = is_2 ? "VUID-vkCmdCopyBufferToImage2-commandBuffer-07739" : "VUID-vkCmdCopyBufferToImage-commandBuffer-07739";
            skip |= ValidateCmdQueueFlags(cb_state, subresource_loc.dot(Field::aspectMask), VK_QUEUE_GRAPHICS_BIT, vuid,
                                          ss.str().c_str());
        }
    }

    // TODO 6898 - ValidateImageBufferCopyMemoryOverlap logic has issues
    // if (!skip && !src_buffer_state->sparse && !dst_image_state->sparse){
    //     skip |= ValidateImageBufferCopyMemoryOverlap(*cb_state_ptr, regionCount, pRegions, *dst_image_state, *src_buffer_state,
    //     loc, false, is_2);
    // }

    return skip;
}

bool CoreChecks::PreCallValidateCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage,
                                                     VkImageLayout dstImageLayout, uint32_t regionCount,
                                                     const VkBufferImageCopy *pRegions, const ErrorObject &error_obj) const {
    return ValidateCmdCopyBufferToImage(commandBuffer, srcBuffer, dstImage, dstImageLayout, regionCount, pRegions,
                                        error_obj.location);
}

bool CoreChecks::PreCallValidateCmdCopyBufferToImage2KHR(VkCommandBuffer commandBuffer,
                                                         const VkCopyBufferToImageInfo2KHR *pCopyBufferToImageInfo,
                                                         const ErrorObject &error_obj) const {
    return PreCallValidateCmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo, error_obj);
}

bool CoreChecks::PreCallValidateCmdCopyBufferToImage2(VkCommandBuffer commandBuffer,
                                                      const VkCopyBufferToImageInfo2 *pCopyBufferToImageInfo,
                                                      const ErrorObject &error_obj) const {
    return ValidateCmdCopyBufferToImage(commandBuffer, pCopyBufferToImageInfo->srcBuffer, pCopyBufferToImageInfo->dstImage,
                                        pCopyBufferToImageInfo->dstImageLayout, pCopyBufferToImageInfo->regionCount,
                                        pCopyBufferToImageInfo->pRegions, error_obj.location.dot(Field::pCopyBufferToImageInfo));
}

void CoreChecks::PreCallRecordCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage,
                                                   VkImageLayout dstImageLayout, uint32_t regionCount,
                                                   const VkBufferImageCopy *pRegions, const RecordObject &record_obj) {
    StateTracker::PreCallRecordCmdCopyBufferToImage(commandBuffer, srcBuffer, dstImage, dstImageLayout, regionCount, pRegions,
                                                    record_obj);

    auto cb_state_ptr = GetWrite<vvl::CommandBuffer>(commandBuffer);
    auto dst_image_state = Get<vvl::Image>(dstImage);
    if (cb_state_ptr && dst_image_state) {
        // Make sure that all image slices are record referenced layout
        for (uint32_t i = 0; i < regionCount; ++i) {
            cb_state_ptr->SetImageInitialLayout(*dst_image_state, pRegions[i].imageSubresource, dstImageLayout);
        }
    }
}

void CoreChecks::PreCallRecordCmdCopyBufferToImage2KHR(VkCommandBuffer commandBuffer,
                                                       const VkCopyBufferToImageInfo2KHR *pCopyBufferToImageInfo2KHR,
                                                       const RecordObject &record_obj) {
    PreCallRecordCmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo2KHR, record_obj);
}

void CoreChecks::PreCallRecordCmdCopyBufferToImage2(VkCommandBuffer commandBuffer,
                                                    const VkCopyBufferToImageInfo2 *pCopyBufferToImageInfo,
                                                    const RecordObject &record_obj) {
    StateTracker::PreCallRecordCmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo, record_obj);

    auto cb_state_ptr = GetWrite<vvl::CommandBuffer>(commandBuffer);
    auto dst_image_state = Get<vvl::Image>(pCopyBufferToImageInfo->dstImage);
    if (cb_state_ptr && dst_image_state) {
        // Make sure that all image slices are record referenced layout
        for (uint32_t i = 0; i < pCopyBufferToImageInfo->regionCount; ++i) {
            cb_state_ptr->SetImageInitialLayout(*dst_image_state, pCopyBufferToImageInfo->pRegions[i].imageSubresource,
                                                pCopyBufferToImageInfo->dstImageLayout);
        }
    }
}

bool CoreChecks::UsageHostTransferCheck(VkDevice device, const vvl::Image &image_state, bool has_stencil, bool has_non_stencil,
                                        const char *vuid_09111, const char *vuid_09112, const char *vuid_09113,
                                        const Location &loc) const {
    bool skip = false;
    if (has_stencil) {
        const auto image_stencil_struct = vku::FindStructInPNextChain<VkImageStencilUsageCreateInfo>(image_state.createInfo.pNext);
        if (image_stencil_struct != nullptr) {
            if ((image_stencil_struct->stencilUsage & VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT) == 0) {
                LogObjectList objlist(device, image_state.image());
                skip |= LogError(
                    vuid_09112, objlist, loc,
                    "An element of pRegions has an aspectMask that includes "
                    "VK_IMAGE_ASPECT_STENCIL_BIT "
                    "and the image was created with separate stencil usage, but VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT was not "
                    "included in VkImageStencilUsageCreateInfo::stencilUsage used to create image");
            }
        } else {
            if ((image_state.createInfo.usage & VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT) == 0) {
                LogObjectList objlist(device, image_state.image());
                skip |= LogError(
                    vuid_09111, objlist, loc,
                    "An element of pRegions has an aspectMask that includes "
                    "VK_IMAGE_ASPECT_STENCIL_BIT and the "
                    "image was not created with separate stencil usage, but VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT was not included "
                    "in VkImageCreateInfo::usage used to create image");
            }
        }
    }
    if (has_non_stencil) {
        if ((image_state.createInfo.usage & VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT) == 0) {
            LogObjectList objlist(device, image_state.image());
            skip |= LogError(
                vuid_09113, objlist, loc,
                "An element of pRegions has an aspectMask that includes "
                "aspects other than VK_IMAGE_ASPECT_STENCIL_BIT, but  VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT was not included "
                "in VkImageCreateInfo::usage used to create image");
        }
    }
    return skip;
}

template <typename T>
VkImageLayout GetImageLayout(T data) {
    return VK_IMAGE_LAYOUT_UNDEFINED;
}
template <>
VkImageLayout GetImageLayout<VkCopyMemoryToImageInfoEXT>(VkCopyMemoryToImageInfoEXT data) {
    return data.dstImageLayout;
}
template <>
VkImageLayout GetImageLayout<VkCopyImageToMemoryInfoEXT>(VkCopyImageToMemoryInfoEXT data) {
    return data.srcImageLayout;
}
template <typename T>
VkImage GetImage(T data) {
    return VK_NULL_HANDLE;
}
template <>
VkImage GetImage<VkCopyMemoryToImageInfoEXT>(VkCopyMemoryToImageInfoEXT data) {
    return data.dstImage;
}
template <>
VkImage GetImage<VkCopyImageToMemoryInfoEXT>(VkCopyImageToMemoryInfoEXT data) {
    return data.srcImage;
}
template <typename InfoPointer>
bool CoreChecks::ValidateMemoryImageCopyCommon(VkDevice device, InfoPointer info_ptr, const Location &loc) const {
    bool skip = false;
    VkImage image = GetImage(*info_ptr);
    auto image_state = Get<vvl::Image>(image);
    auto image_layout = GetImageLayout(*info_ptr);
    auto regionCount = info_ptr->regionCount;
    const bool from_image = loc.function == Func::vkCopyImageToMemoryEXT;
    const Location image_loc = loc.dot(from_image ? Field::srcImage : Field::dstImage);
    const char *info_type = from_image ? "pCopyImageToMemoryInfo" : "pCopyMemoryToImageInfo";
    const char *source_or_destination = from_image ? "source" : "destination";
    const char *image_layout_vuid = from_image ? "VUID-VkCopyImageToMemoryInfoEXT-srcImageLayout-09064"
                                               : "VUID-VkCopyMemoryToImageInfoEXT-dstImageLayout-09059";

    if (!(enabled_features.hostImageCopy)) {
        const char *vuid =
            from_image ? "VUID-vkCopyImageToMemoryEXT-hostImageCopy-09063" : "VUID-vkCopyMemoryToImageEXT-hostImageCopy-09058";
        skip |= LogError(vuid, device, loc, "the hostImageCopy feature was not enabled");
    }

    skip |= ValidateHeterogeneousCopyData(device, regionCount, info_ptr->pRegions, *image_state, loc);
    skip |= ValidateMemoryIsBoundToImage(
        LogObjectList(device, image), *image_state, image_loc,
        from_image ? "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07966" : "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07966");

    if (image_state->sparse && (!image_state->HasFullRangeBound())) {
        const char *vuid =
            from_image ? "VUID-VkCopyImageToMemoryInfoEXT-srcImage-09109" : "VUID-VkCopyMemoryToImageInfoEXT-dstImage-09109";
        LogObjectList objlist(device, image_state->image());
        skip |= LogError(vuid, objlist, image_loc, "is a sparse image with no memory bound");
    }

    if (image_state->createInfo.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
        const char *vuid =
            from_image ? "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07969" : "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07969";
        const LogObjectList objlist(device, image);
        skip |= LogError(vuid, objlist, image_loc,
                         "must not have been created with flags containing "
                         "VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT");
    }
    skip |= ValidateImageBounds(device, *image_state, regionCount, info_ptr->pRegions, loc,
                                from_image ? "VUID-VkCopyImageToMemoryInfoEXT-imageSubresource-07970"
                                           : "VUID-VkCopyMemoryToImageInfoEXT-imageSubresource-07970",
                                from_image);

    skip |= ValidateImageSampleCount(
        device, *image_state, VK_SAMPLE_COUNT_1_BIT, image_loc,
        from_image ? "VUID-VkCopyImageToMemoryInfoEXT-srcImage-07973" : "VUID-VkCopyMemoryToImageInfoEXT-dstImage-07973");

    bool check_memcpy = (info_ptr->flags & VK_HOST_IMAGE_COPY_MEMCPY_EXT);
    bool has_stencil = false;
    bool has_non_stencil = false;
    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const Location subresource_loc = region_loc.dot(Field::imageSubresource);
        const auto region = info_ptr->pRegions[i];

        skip |= ValidateImageMipLevel(device, *image_state, region.imageSubresource.mipLevel, subresource_loc.dot(Field::mipLevel),
                                      from_image ? "VUID-VkCopyImageToMemoryInfoEXT-imageSubresource-07967"
                                                 : "VUID-VkCopyMemoryToImageInfoEXT-imageSubresource-07967");
        skip |= ValidateImageArrayLayerRange(device, *image_state, region.imageSubresource.baseArrayLayer,
                                             region.imageSubresource.layerCount, subresource_loc,
                                             from_image ? "VUID-VkCopyImageToMemoryInfoEXT-imageSubresource-07968"
                                                        : "VUID-VkCopyMemoryToImageInfoEXT-imageSubresource-07968");
        skip |= ValidateImageSubresourceLayers(device, &region.imageSubresource, subresource_loc);
        if (region.imageSubresource.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) has_stencil = true;
        if (region.imageSubresource.aspectMask & ~VK_IMAGE_ASPECT_STENCIL_BIT) has_non_stencil = true;

        if (check_memcpy) {
            if (region.imageOffset.x != 0 || region.imageOffset.y != 0 || region.imageOffset.z != 0) {
                const char *vuid = from_image ? "VUID-VkCopyImageToMemoryInfoEXT-imageOffset-09114"
                                              : "VUID-VkCopyMemoryToImageInfoEXT-imageOffset-09114";
                LogObjectList objlist(device);
                LogError(vuid, objlist, loc,
                         "%s->flags contains VK_HOST_IMAGE_COPY_MEMCPY_EXT which "
                         "means that pRegions[%" PRIu32 "].imageOffset x(%" PRIu32 "), y(%" PRIu32 ") and z(%" PRIu32
                         ") must all be zero",
                         info_type, i, region.imageOffset.x, region.imageOffset.y, region.imageOffset.z);
            }
            const VkExtent3D subresource_extent = image_state->GetEffectiveSubresourceExtent(region.imageSubresource);
            if (!IsExtentEqual(region.imageExtent, subresource_extent)) {
                const char *vuid = from_image ? "VUID-VkCopyImageToMemoryInfoEXT-srcImage-09115"
                                              : "VUID-VkCopyMemoryToImageInfoEXT-dstImage-09115";
                LogObjectList objlist(device, image_state->image());
                skip |= LogError(vuid, objlist, loc,
                                 "pRegion[%" PRIu32 "].imageExtent (w=%" PRIu32 ", h=%" PRIu32 ", d=%" PRIu32
                                 ") must match the image's subresource "
                                 "extents (w=%" PRIu32 ", h=%" PRIu32 ", d=%" PRIu32
                                 ") %s->flags contains VK_HOST_IMAGE_COPY_MEMCPY_EXT",
                                 i, region.imageExtent.width, region.imageExtent.height, region.imageExtent.depth,
                                 subresource_extent.width, subresource_extent.height, subresource_extent.depth, info_type);
            }
            if ((region.memoryRowLength != 0) || (region.memoryImageHeight != 0)) {
                const char *vuid =
                    from_image ? "VUID-VkCopyImageToMemoryInfoEXT-flags-09394" : "VUID-VkCopyMemoryToImageInfoEXT-flags-09393";
                LogObjectList objlist(device, image_state->image());
                skip |= LogError(vuid, objlist, loc,
                                 "pRegion[%" PRIu32 "].memoryRowLength (%" PRIu32 "), and pRegion[%" PRIu32
                                 "].memoryImageHeight (%" PRIu32
                                 ") must both be zero if %s->flags contains VK_HOST_IMAGE_COPY_MEMCPY_EXT",
                                 i, region.memoryRowLength, i, region.memoryImageHeight, info_type);
            }
        }

        Field field = from_image ? Field::srcImageLayout : Field::dstImageLayout;
        skip |= ValidateHostCopyCurrentLayout(device, image_layout, region.imageSubresource, i, *image_state, region_loc.dot(field),
                                              source_or_destination, image_layout_vuid);
    }

    const char *vuid_09111 =
        from_image ? "VUID-VkCopyImageToMemoryInfoEXT-srcImage-09111" : "VUID-VkCopyMemoryToImageInfoEXT-dstImage-09111";
    const char *vuid_09112 =
        from_image ? "VUID-VkCopyImageToMemoryInfoEXT-srcImage-09112" : "VUID-VkCopyMemoryToImageInfoEXT-dstImage-09112";
    const char *vuid_09113 =
        from_image ? "VUID-VkCopyImageToMemoryInfoEXT-srcImage-09113" : "VUID-VkCopyMemoryToImageInfoEXT-dstImage-09113";
    skip |= UsageHostTransferCheck(device, *image_state, has_stencil, has_non_stencil, vuid_09111, vuid_09112, vuid_09113, loc);

    const auto &memory_states = image_state->GetBoundMemoryStates();
    for (const auto &state : memory_states) {
        // Image and host memory can't overlap unless the image memory is mapped
        if (state->mapped_range.size != 0) {
            const uint64_t mapped_size = (state->mapped_range.size == VK_WHOLE_SIZE)
                                             ? state->alloc_info.allocationSize
                                             : (state->mapped_range.offset + state->mapped_range.size);
            const void *mapped_end = static_cast<char *>(state->p_driver_data) + mapped_size;
            for (uint32_t i = 0; i < regionCount; i++) {
                const auto region = info_ptr->pRegions[i];
                auto element_size = vkuFormatElementSize(image_state->createInfo.format);
                uint64_t copy_size;
                if (region.memoryRowLength != 0 && region.memoryImageHeight != 0) {
                    copy_size = ((region.memoryRowLength * region.memoryImageHeight) * element_size);
                } else {
                    copy_size = ((region.imageExtent.width * region.imageExtent.height * region.imageExtent.depth) * element_size);
                }
                const void *copy_end = static_cast<const char *>(region.pHostPointer) + copy_size;

                if ((region.pHostPointer >= state->p_driver_data && region.pHostPointer < mapped_end) ||
                    (copy_end >= state->p_driver_data && copy_end < mapped_end) ||
                    (region.pHostPointer <= state->p_driver_data && copy_end > mapped_end)) {
                    const char *vuid =
                        from_image ? "VUID-VkImageToMemoryCopyEXT-pRegions-09067" : "VUID-VkMemoryToImageCopyEXT-pRegions-09062";
                    LogObjectList objlist(device, image_state->image());
                    skip |= LogError(vuid, objlist, loc.dot(Field::pRegions, i).dot(Field::pHostPointer),
                                     "points to memory spanning %p through %p, which overlaps with image memory"
                                     "mapped %p through %p",
                                     region.pHostPointer, copy_end, state->p_driver_data, mapped_end);
                }
            }
        }
    }

    return skip;
}

bool CoreChecks::ValidateHostCopyImageCreateInfos(VkDevice device, const vvl::Image &src_image_state,
                                                  const vvl::Image &dst_image_state, const Location &loc) const {
    bool skip = false;
    std::stringstream mismatch_stream{};
    const VkImageCreateInfo src_info = src_image_state.createInfo;
    const VkImageCreateInfo dst_info = dst_image_state.createInfo;

    if (src_info.flags != dst_info.flags) {
        mismatch_stream << "srcImage flags = " << string_VkImageCreateFlags(src_info.flags)
                        << " and dstImage flags = " << string_VkImageCreateFlags(dst_info.flags) << "\n";
    }
    if (src_info.imageType != dst_info.imageType) {
        mismatch_stream << "srcImage imageType = " << string_VkImageType(src_info.imageType)
                        << " and dstImage imageType = " << string_VkImageType(dst_info.imageType) << "\n";
    }
    if (src_info.format != dst_info.format) {
        mismatch_stream << "srcImage format = " << string_VkFormat(src_info.format)
                        << " and dstImage format = " << string_VkFormat(dst_info.format) << "\n";
    }
    if ((src_info.extent.width != dst_info.extent.width) || (src_info.extent.height != dst_info.extent.height) ||
        (src_info.extent.depth != dst_info.extent.depth)) {
        mismatch_stream << "srcImage extent.width = " << src_info.extent.width << " extent.height = " << src_info.extent.height
                        << " extent.depth = " << src_info.extent.depth << " but dstImage extent.width = " << dst_info.extent.width
                        << " extent.height = " << dst_info.extent.height << " extent.depth = " << dst_info.extent.depth << "\n";
    }
    if (src_info.mipLevels != dst_info.mipLevels) {
        mismatch_stream << "srcImage mipLevels = " << src_info.mipLevels << "and dstImage mipLevels = " << dst_info.mipLevels
                        << "\n";
    }
    if (src_info.arrayLayers != dst_info.arrayLayers) {
        mismatch_stream << "srcImage arrayLayers = " << src_info.arrayLayers
                        << " and dstImage arrayLayers = " << dst_info.arrayLayers << "\n";
    }
    if (src_info.samples != dst_info.samples) {
        mismatch_stream << "srcImage samples = " << string_VkSampleCountFlagBits(src_info.samples)
                        << " and dstImage samples = " << string_VkSampleCountFlagBits(dst_info.samples) << "\n";
    }
    if (src_info.tiling != dst_info.tiling) {
        mismatch_stream << "srcImage tiling = " << string_VkImageTiling(src_info.tiling)
                        << " and dstImage tiling = " << string_VkImageTiling(dst_info.tiling) << "\n";
    }
    if (src_info.usage != dst_info.usage) {
        mismatch_stream << "srcImage usage = " << string_VkImageUsageFlags(src_info.usage)
                        << " and dstImage usage = " << string_VkImageUsageFlags(dst_info.usage) << "\n";
    }
    if (src_info.sharingMode != dst_info.sharingMode) {
        mismatch_stream << "srcImage sharingMode = " << string_VkSharingMode(src_info.sharingMode)
                        << " and dstImage sharingMode = " << string_VkSharingMode(dst_info.sharingMode) << "\n";
    }
    if (src_info.initialLayout != dst_info.initialLayout) {
        mismatch_stream << "srcImage initialLayout = " << string_VkImageLayout(src_info.initialLayout)
                        << " and dstImage initialLayout = " << string_VkImageLayout(dst_info.initialLayout) << "\n";
    }

    if (mismatch_stream.str().length() > 0) {
        std::stringstream ss;
        ss << "The creation parameters for srcImage and dstImage differ:\n" << mismatch_stream.str();
        LogObjectList objlist(device, src_image_state.image(), dst_image_state.image());
        skip |= LogError("VUID-VkCopyImageToImageInfoEXT-srcImage-09069", objlist, loc, "%s.", ss.str().c_str());
    }
    return skip;
}

bool CoreChecks::ValidateHostCopyImageLayout(const VkDevice device, const VkImage image, const uint32_t layout_count,
                                             const VkImageLayout *supported_image_layouts, const VkImageLayout image_layout,
                                             const Location &loc, const char *supported_name, const char *vuid) const {
    for (uint32_t i = 0; i < layout_count; ++i) {
        if (supported_image_layouts[i] == image_layout) {
            return false;
        }
    }

    LogObjectList objlist(device, image);
    bool skip = LogError(vuid, objlist, loc,
                         "is %s which is not one of the layouts returned in "
                         "VkPhysicalDeviceHostImageCopyPropertiesEXT::%s",
                         string_VkImageLayout(image_layout), supported_name);

    return skip;
}

bool CoreChecks::PreCallValidateCopyMemoryToImageEXT(VkDevice device, const VkCopyMemoryToImageInfoEXT *pCopyMemoryToImageInfo,
                                                     const ErrorObject &error_obj) const {
    bool skip = false;
    const Location copy_loc = error_obj.location.dot(Field::pCopyMemoryToImageInfo);
    auto dst_image = pCopyMemoryToImageInfo->dstImage;
    auto image_state = Get<vvl::Image>(dst_image);

    skip |= ValidateMemoryImageCopyCommon(device, pCopyMemoryToImageInfo, copy_loc);
    auto *props = &phys_dev_ext_props.host_image_copy_props;
    skip |= ValidateHostCopyImageLayout(device, dst_image, props->copyDstLayoutCount, props->pCopyDstLayouts,
                                        pCopyMemoryToImageInfo->dstImageLayout, copy_loc.dot(Field::dstImageLayout),
                                        "pCopyDstLayouts", "VUID-VkCopyMemoryToImageInfoEXT-dstImageLayout-09060");
    return skip;
}

bool CoreChecks::PreCallValidateCopyImageToMemoryEXT(VkDevice device, const VkCopyImageToMemoryInfoEXT *pCopyImageToMemoryInfo,
                                                     const ErrorObject &error_obj) const {
    bool skip = false;
    const Location copy_loc = error_obj.location.dot(Field::pCopyImageToMemoryInfo);
    auto src_image = pCopyImageToMemoryInfo->srcImage;
    auto image_state = Get<vvl::Image>(src_image);

    skip |= ValidateMemoryImageCopyCommon(device, pCopyImageToMemoryInfo, copy_loc);
    auto *props = &phys_dev_ext_props.host_image_copy_props;
    skip |= ValidateHostCopyImageLayout(device, src_image, props->copySrcLayoutCount, props->pCopySrcLayouts,
                                        pCopyImageToMemoryInfo->srcImageLayout, copy_loc.dot(Field::srcImageLayout),
                                        "pCopySrcLayouts", "VUID-VkCopyImageToMemoryInfoEXT-srcImageLayout-09065");
    return skip;
}

bool CoreChecks::ValidateMemcpyExtents(VkDevice device, const VkImageCopy2 region, const vvl::Image &image_state, bool is_src,
                                       const Location &region_loc) const {
    bool skip = false;
    if (region.srcOffset.x != 0 || region.srcOffset.y != 0 || region.srcOffset.z != 0) {
        const char *vuid =
            is_src ? "VUID-VkCopyImageToImageInfoEXT-srcOffset-09114" : "VUID-VkCopyImageToImageInfoEXT-dstOffset-09114";
        Field field = is_src ? Field::srcOffset : Field::dstOffset;
        const LogObjectList objlist(device);
        skip |= LogError(vuid, objlist, region_loc.dot(field),
                         "is (x = %" PRIu32 ", y = %" PRIu32 ", z = %" PRIu32 ") but flags contains VK_HOST_IMAGE_COPY_MEMCPY_EXT.",
                         region.srcOffset.x, region.srcOffset.y, region.srcOffset.z);
    }
    if (!IsExtentEqual(region.extent, image_state.createInfo.extent)) {
        const char *vuid =
            is_src ? "VUID-VkCopyImageToImageInfoEXT-srcImage-09115" : "VUID-VkCopyImageToImageInfoEXT-dstImage-09115";
        const LogObjectList objlist(device, image_state.image());
        skip |= LogError(vuid, objlist, region_loc.dot(Field::imageExtent),
                         "(w = %" PRIu32 ", h = %" PRIu32 ", d = %" PRIu32
                         ") must match the image's subresource "
                         "extents (w = %" PRIu32 ", h = %" PRIu32 ", d = %" PRIu32
                         ") when VkCopyImageToImageInfoEXT->flags contains VK_HOST_IMAGE_COPY_MEMCPY_EXT",
                         region.extent.width, region.extent.height, region.extent.depth, image_state.createInfo.extent.width,
                         image_state.createInfo.extent.height, image_state.createInfo.extent.depth);
    }
    return skip;
}

bool CoreChecks::ValidateHostCopyMultiplane(VkDevice device, VkImageCopy2 region, const vvl::Image &image_state, bool is_src,
                                            const Location &region_loc) const {
    bool skip = false;
    auto aspect_mask = is_src ? region.srcSubresource.aspectMask : region.dstSubresource.aspectMask;
    if (vkuFormatPlaneCount(image_state.createInfo.format) == 2 &&
        (aspect_mask != VK_IMAGE_ASPECT_PLANE_0_BIT && aspect_mask != VK_IMAGE_ASPECT_PLANE_1_BIT)) {
        const char *vuid =
            is_src ? "VUID-VkCopyImageToImageInfoEXT-srcImage-07981" : "VUID-VkCopyImageToImageInfoEXT-dstImage-07981";
        Field field = is_src ? Field::srcSubresource : Field::dstSubresource;
        LogObjectList objlist(device, image_state.image());
        skip |= LogError(vuid, objlist, region_loc.dot(field), "is %s but %s has 2-plane format (%s).",
                         string_VkImageAspectFlags(aspect_mask).c_str(), is_src ? "srcImage" : "dstImage",
                         string_VkFormat(image_state.createInfo.format));
    }
    if (vkuFormatPlaneCount(image_state.createInfo.format) == 3 &&
        (aspect_mask != VK_IMAGE_ASPECT_PLANE_0_BIT && aspect_mask != VK_IMAGE_ASPECT_PLANE_1_BIT &&
         aspect_mask != VK_IMAGE_ASPECT_PLANE_2_BIT)) {
        const char *vuid =
            is_src ? "VUID-VkCopyImageToImageInfoEXT-srcImage-07981" : "VUID-VkCopyImageToImageInfoEXT-dstImage-07981";
        Field field = is_src ? Field::srcSubresource : Field::dstSubresource;
        LogObjectList objlist(device, image_state.image());
        skip |= LogError(vuid, objlist, region_loc.dot(field), "is %s but %s has 3-plane format (%s).",
                         string_VkImageAspectFlags(aspect_mask).c_str(), is_src ? "srcImage" : "dstImage",
                         string_VkFormat(image_state.createInfo.format));
    }
    return skip;
}

bool CoreChecks::PreCallValidateCopyImageToImageEXT(VkDevice device, const VkCopyImageToImageInfoEXT *pCopyImageToImageInfo,
                                                    const ErrorObject &error_obj) const {
    bool skip = false;
    auto info_ptr = pCopyImageToImageInfo;
    const Location loc = error_obj.location.dot(Field::pCopyImageToImageInfo);
    auto src_image_state = Get<vvl::Image>(info_ptr->srcImage);
    auto dst_image_state = Get<vvl::Image>(info_ptr->dstImage);
    // Formats are required to match, but check each image anyway
    auto src_plane_count = vkuFormatPlaneCount(src_image_state->createInfo.format);
    auto dst_plane_count = vkuFormatPlaneCount(dst_image_state->createInfo.format);
    bool check_multiplane = ((src_plane_count == 2 || src_plane_count == 3) || (dst_plane_count == 2 || dst_plane_count == 3));
    bool check_memcpy = (info_ptr->flags & VK_HOST_IMAGE_COPY_MEMCPY_EXT);
    auto regionCount = info_ptr->regionCount;
    auto pRegions = info_ptr->pRegions;

    if (!(enabled_features.hostImageCopy)) {
        skip |= LogError("VUID-vkCopyImageToImageEXT-hostImageCopy-09068", device, error_obj.location,
                         "the hostImageCopy feature was not enabled");
    }

    skip |= ValidateHostCopyImageCreateInfos(device, *src_image_state, *dst_image_state, error_obj.location);
    skip |= ValidateImageCopyData(device, regionCount, pRegions, *src_image_state, *dst_image_state, true, error_obj.location);
    skip |= ValidateCopyImageCommon(device, *src_image_state, *dst_image_state, regionCount, pRegions, error_obj.location);
    skip |= ValidateImageBounds(device, *src_image_state, regionCount, pRegions, loc,
                                "VUID-VkCopyImageToImageInfoEXT-srcSubresource-07970", true);
    skip |= ValidateImageBounds(device, *dst_image_state, regionCount, pRegions, loc,
                                "VUID-VkCopyImageToImageInfoEXT-dstSubresource-07970", false);
    auto *props = &phys_dev_ext_props.host_image_copy_props;
    skip |= ValidateHostCopyImageLayout(device, info_ptr->srcImage, props->copySrcLayoutCount, props->pCopySrcLayouts,
                                        info_ptr->srcImageLayout, loc.dot(Field::srcImageLayout), "pCopySrcLayouts",
                                        "VUID-VkCopyImageToImageInfoEXT-srcImageLayout-09072");
    skip |= ValidateHostCopyImageLayout(device, info_ptr->dstImage, props->copyDstLayoutCount, props->pCopyDstLayouts,
                                        info_ptr->dstImageLayout, loc.dot(Field::dstImageLayout), "pCopyDstLayouts",
                                        "VUID-VkCopyImageToImageInfoEXT-dstImageLayout-09073");

    if (src_image_state->sparse && (!src_image_state->HasFullRangeBound())) {
        LogObjectList objlist(device, src_image_state->image());
        skip |= LogError("VUID-VkCopyImageToImageInfoEXT-srcImage-09109", objlist, loc.dot(Field::srcImage),
                         "is a sparse image with no memory bound");
    }
    if (dst_image_state->sparse && (!dst_image_state->HasFullRangeBound())) {
        LogObjectList objlist(device, dst_image_state->image());
        skip |= LogError("VUID-VkCopyImageToImageInfoEXT-dstImage-09109", objlist, loc.dot(Field::dstImage),
                         "is a sparse image with no memory bound");
    }

    bool has_stencil = false;
    bool has_non_stencil = false;
    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const auto &region = info_ptr->pRegions[i];
        if (check_memcpy) {
            skip |= ValidateMemcpyExtents(device, region, *src_image_state, true, region_loc);
            skip |= ValidateMemcpyExtents(device, region, *dst_image_state, false, region_loc);
        }
        if (check_multiplane) {
            skip |= ValidateHostCopyMultiplane(device, region, *src_image_state, true, region_loc);
            skip |= ValidateHostCopyMultiplane(device, region, *dst_image_state, false, region_loc);
        }

        if ((region.srcSubresource.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) {
            has_stencil = true;
        }
        if ((region.srcSubresource.aspectMask & (~VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
            has_non_stencil = true;
        }

        skip |= ValidateHostCopyCurrentLayout(device, info_ptr->srcImageLayout, region.srcSubresource, i, *src_image_state,
                                              region_loc.dot(Field::srcImageLayout), "source",
                                              "VUID-VkCopyImageToImageInfoEXT-srcImageLayout-09070");
        skip |= ValidateHostCopyCurrentLayout(device, info_ptr->dstImageLayout, region.dstSubresource, i, *dst_image_state,
                                              region_loc.dot(Field::dstImageLayout), "destination",
                                              "VUID-VkCopyImageToImageInfoEXT-dstImageLayout-09071");
    }

    skip |= UsageHostTransferCheck(device, *src_image_state, has_stencil, has_non_stencil,
                                   "VUID-VkCopyImageToImageInfoEXT-srcImage-09111", "VUID-VkCopyImageToImageInfoEXT-srcImage-09112",
                                   "VUID-VkCopyImageToImageInfoEXT-srcImage-09113", error_obj.location);
    skip |= UsageHostTransferCheck(device, *dst_image_state, has_stencil, has_non_stencil,
                                   "VUID-VkCopyImageToImageInfoEXT-dstImage-09111", "VUID-VkCopyImageToImageInfoEXT-dstImage-09112",
                                   "VUID-VkCopyImageToImageInfoEXT-dstImage-09113", error_obj.location);
    return skip;
}

template <typename RegionType>
bool CoreChecks::ValidateCmdBlitImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                      VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                      const RegionType *pRegions, VkFilter filter, const Location &loc) const {
    bool skip = false;
    auto cb_state_ptr = GetRead<vvl::CommandBuffer>(commandBuffer);
    auto src_image_state = Get<vvl::Image>(srcImage);
    auto dst_image_state = Get<vvl::Image>(dstImage);
    if (!cb_state_ptr || !src_image_state || !src_image_state) {
        return skip;
    }

    const bool is_2 = loc.function == Func::vkCmdBlitImage2 || loc.function == Func::vkCmdBlitImage2KHR;
    const Location src_image_loc = loc.dot(Field::srcImage);
    const Location dst_image_loc = loc.dot(Field::dstImage);

    const LogObjectList src_objlist(commandBuffer, srcImage);
    const LogObjectList dst_objlist(commandBuffer, dstImage);
    const LogObjectList all_objlist(commandBuffer, srcImage, dstImage);

    const vvl::CommandBuffer &cb_state = *cb_state_ptr;
    skip |= ValidateCmd(cb_state, loc);

    const char *vuid;
    vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00233" : "VUID-vkCmdBlitImage-srcImage-00233";
    skip |= ValidateImageSampleCount(commandBuffer, *src_image_state, VK_SAMPLE_COUNT_1_BIT, src_image_loc, vuid);
    vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImage-00234" : "VUID-vkCmdBlitImage-dstImage-00234";
    skip |= ValidateImageSampleCount(commandBuffer, *dst_image_state, VK_SAMPLE_COUNT_1_BIT, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00220" : "VUID-vkCmdBlitImage-srcImage-00220";
    skip |= ValidateMemoryIsBoundToImage(src_objlist, *src_image_state, src_image_loc, vuid);
    vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImage-00225" : "VUID-vkCmdBlitImage-dstImage-00225";
    skip |= ValidateMemoryIsBoundToImage(dst_objlist, *dst_image_state, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00219" : "VUID-vkCmdBlitImage-srcImage-00219";
    skip |= ValidateImageUsageFlags(commandBuffer, *src_image_state, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, true, vuid, src_image_loc);
    vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImage-00224" : "VUID-vkCmdBlitImage-dstImage-00224";
    skip |= ValidateImageUsageFlags(commandBuffer, *dst_image_state, VK_IMAGE_USAGE_TRANSFER_DST_BIT, true, vuid, dst_image_loc);
    vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-01999" : "VUID-vkCmdBlitImage-srcImage-01999";
    skip |= ValidateImageFormatFeatureFlags(commandBuffer, *src_image_state, VK_FORMAT_FEATURE_2_BLIT_SRC_BIT, src_image_loc, vuid);
    vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImage-02000" : "VUID-vkCmdBlitImage-dstImage-02000";
    skip |= ValidateImageFormatFeatureFlags(commandBuffer, *dst_image_state, VK_FORMAT_FEATURE_2_BLIT_DST_BIT, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdBlitImage2-commandBuffer-01834" : "VUID-vkCmdBlitImage-commandBuffer-01834";
    skip |= ValidateProtectedImage(cb_state, *src_image_state, src_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdBlitImage2-commandBuffer-01835" : "VUID-vkCmdBlitImage-commandBuffer-01835";
    skip |= ValidateProtectedImage(cb_state, *dst_image_state, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdBlitImage2-commandBuffer-01836" : "VUID-vkCmdBlitImage-commandBuffer-01836";
    skip |= ValidateUnprotectedImage(cb_state, *dst_image_state, dst_image_loc, vuid);

    // Validation for VK_EXT_fragment_density_map
    if (src_image_state->createInfo.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImage-02545" : "VUID-vkCmdBlitImage-dstImage-02545";
        skip |= LogError(vuid, src_objlist, src_image_loc, "was created with VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT.");
    }
    if (dst_image_state->createInfo.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImage-02545" : "VUID-vkCmdBlitImage-dstImage-02545";
        skip |= LogError(vuid, dst_objlist, dst_image_loc, "was created with VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT.");
    }

    // TODO: Need to validate image layouts, which will include layout validation for shared presentable images

    VkFormat src_format = src_image_state->createInfo.format;
    VkFormat dst_format = dst_image_state->createInfo.format;
    VkImageType src_type = src_image_state->createInfo.imageType;
    VkImageType dst_type = dst_image_state->createInfo.imageType;

    if (VK_FILTER_LINEAR == filter) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-filter-02001" : "VUID-vkCmdBlitImage-filter-02001";
        skip |= ValidateImageFormatFeatureFlags(commandBuffer, *src_image_state,
                                                VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_LINEAR_BIT, src_image_loc, vuid);
    } else if (VK_FILTER_CUBIC_IMG == filter) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-filter-02002" : "VUID-vkCmdBlitImage-filter-02002";
        skip |= ValidateImageFormatFeatureFlags(commandBuffer, *src_image_state, VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_CUBIC_BIT,
                                                src_image_loc, vuid);
    }

    if (FormatRequiresYcbcrConversionExplicitly(src_format)) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-06421" : "VUID-vkCmdBlitImage-srcImage-06421";
        skip |= LogError(vuid, src_objlist, src_image_loc,
                         "format (%s) must not be one of the formats requiring sampler YCBCR "
                         "conversion for VK_IMAGE_ASPECT_COLOR_BIT image views",
                         string_VkFormat(src_format));
    }

    if (FormatRequiresYcbcrConversionExplicitly(dst_format)) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImage-06422" : "VUID-vkCmdBlitImage-dstImage-06422";
        skip |= LogError(vuid, dst_objlist, dst_image_loc,
                         "format (%s) must not be one of the formats requiring sampler YCBCR "
                         "conversion for VK_IMAGE_ASPECT_COLOR_BIT image views",
                         string_VkFormat(dst_format));
    }

    if ((VK_FILTER_CUBIC_IMG == filter) && (VK_IMAGE_TYPE_2D != src_type)) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-filter-00237" : "VUID-vkCmdBlitImage-filter-00237";
        skip |= LogError(vuid, src_objlist, loc.dot(Field::filter), "is VK_FILTER_CUBIC_IMG but srcImage was created with %s.",
                         string_VkImageType(src_type));
    }

    // Validate consistency for unsigned formats
    if (vkuFormatIsUINT(src_format) != vkuFormatIsUINT(dst_format)) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00230" : "VUID-vkCmdBlitImage-srcImage-00230";
        skip |= LogError(vuid, all_objlist, loc, "srcImage format %s is different than dstImage format %s.",
                         string_VkFormat(src_format), string_VkFormat(dst_format));
    }

    // Validate consistency for signed formats
    if (vkuFormatIsSINT(src_format) != vkuFormatIsSINT(dst_format)) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00229" : "VUID-vkCmdBlitImage-srcImage-00229";
        skip |= LogError(vuid, all_objlist, loc, "srcImage format %s is different than dstImage format %s.",
                         string_VkFormat(src_format), string_VkFormat(dst_format));
    }

    // Validate filter for Depth/Stencil formats
    if (vkuFormatIsDepthOrStencil(src_format) && (filter != VK_FILTER_NEAREST)) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00232" : "VUID-vkCmdBlitImage-srcImage-00232";
        skip |= LogError(vuid, src_objlist, src_image_loc, "has depth-stencil format %s but filter is %s.",
                         string_VkFormat(src_format), string_VkFilter(filter));
    }

    // Validate aspect bits and formats for depth/stencil images
    if (vkuFormatIsDepthOrStencil(src_format) || vkuFormatIsDepthOrStencil(dst_format)) {
        if (src_format != dst_format) {
            vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00231" : "VUID-vkCmdBlitImage-srcImage-00231";
            skip |= LogError(vuid, all_objlist, loc, "srcImage format %s is different than dstImage format %s.",
                             string_VkFormat(src_format), string_VkFormat(dst_format));
        }
    }

    if (!IsValueIn(srcImageLayout,
                   {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL})) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImageLayout-01398" : "VUID-vkCmdBlitImage-srcImageLayout-01398";
        const LogObjectList objlist(cb_state.commandBuffer(), src_image_state->Handle());
        skip |= LogError(vuid, objlist, loc.dot(Field::srcImageLayout), "is %s.", string_VkImageLayout(srcImageLayout));
    }

    if (!IsValueIn(dstImageLayout,
                   {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL})) {
        vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImageLayout-01399" : "VUID-vkCmdBlitImage-dstImageLayout-01399";
        const LogObjectList objlist(cb_state.commandBuffer(), dst_image_state->Handle());
        skip |= LogError(vuid, objlist, loc.dot(Field::dstImageLayout), "is %s.", string_VkImageLayout(dstImageLayout));
    }

    const bool same_image = (src_image_state == dst_image_state);
    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const Location src_subresource_loc = region_loc.dot(Field::srcSubresource);
        const Location dst_subresource_loc = region_loc.dot(Field::dstSubresource);
        const RegionType region = pRegions[i];

        // When performing blit from and to same subresource, VK_IMAGE_LAYOUT_GENERAL is the only option
        const VkImageSubresourceLayers &src_subresource = region.srcSubresource;
        const VkImageSubresourceLayers &dst_subresource = region.dstSubresource;

        const bool same_subresource = (same_image && (src_subresource.mipLevel == dst_subresource.mipLevel) &&
                                       RangesIntersect(src_subresource.baseArrayLayer, src_subresource.layerCount,
                                                       dst_subresource.baseArrayLayer, dst_subresource.layerCount));
        if (same_subresource) {
            if (!IsValueIn(srcImageLayout, {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_GENERAL}) ||
                !IsValueIn(dstImageLayout, {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_GENERAL})) {
                vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-09459" : "VUID-vkCmdBlitImage-srcImage-09459";
                const LogObjectList objlist(cb_state.commandBuffer(), src_image_state->Handle());
                skip |= LogError(vuid, objlist, loc,
                                 "blitting to same same VkImage (miplevel = %u, srcLayer[%u,%u), dstLayer[%u,%u)), but "
                                 "srcImageLayout is %s and dstImageLayout is %s",
                                 src_subresource.mipLevel, src_subresource.baseArrayLayer,
                                 src_subresource.baseArrayLayer + src_subresource.layerCount, dst_subresource.baseArrayLayer,
                                 dst_subresource.baseArrayLayer + dst_subresource.layerCount, string_VkImageLayout(srcImageLayout),
                                 string_VkImageLayout(dstImageLayout));
            }
        }

        vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImageLayout-00221" : "VUID-vkCmdBlitImage-srcImageLayout-00221";
        skip |= VerifyImageLayoutSubresource(cb_state, *src_image_state, src_subresource, srcImageLayout, src_image_loc, vuid);
        vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImageLayout-00226" : "VUID-vkCmdBlitImage-dstImageLayout-00226";
        skip |= VerifyImageLayoutSubresource(cb_state, *dst_image_state, dst_subresource, dstImageLayout, dst_image_loc, vuid);
        skip |= ValidateImageSubresourceLayers(cb_state.commandBuffer(), &src_subresource, src_subresource_loc);
        skip |= ValidateImageSubresourceLayers(cb_state.commandBuffer(), &dst_subresource, dst_subresource_loc);
        vuid = is_2 ? "VUID-VkBlitImageInfo2-srcSubresource-01705" : "VUID-vkCmdBlitImage-srcSubresource-01705";
        skip |= ValidateImageMipLevel(commandBuffer, *src_image_state, src_subresource.mipLevel,
                                      src_subresource_loc.dot(Field::mipLevel), vuid);
        vuid = is_2 ? "VUID-VkBlitImageInfo2-dstSubresource-01706" : "VUID-vkCmdBlitImage-dstSubresource-01706";
        skip |= ValidateImageMipLevel(commandBuffer, *dst_image_state, dst_subresource.mipLevel,
                                      dst_subresource_loc.dot(Field::mipLevel), vuid);
        vuid = is_2 ? "VUID-VkBlitImageInfo2-srcSubresource-01707" : "VUID-vkCmdBlitImage-srcSubresource-01707";
        skip |= ValidateImageArrayLayerRange(commandBuffer, *src_image_state, src_subresource.baseArrayLayer,
                                             src_subresource.layerCount, src_subresource_loc, vuid);
        vuid = is_2 ? "VUID-VkBlitImageInfo2-dstSubresource-01708" : "VUID-vkCmdBlitImage-dstSubresource-01708";
        skip |= ValidateImageArrayLayerRange(commandBuffer, *dst_image_state, dst_subresource.baseArrayLayer,
                                             dst_subresource.layerCount, dst_subresource_loc, vuid);

        if (src_subresource.layerCount == VK_REMAINING_ARRAY_LAYERS || dst_subresource.layerCount == VK_REMAINING_ARRAY_LAYERS) {
            if (src_subresource.layerCount != VK_REMAINING_ARRAY_LAYERS) {
                if (src_subresource.layerCount != (dst_image_state->createInfo.arrayLayers - dst_subresource.baseArrayLayer)) {
                    vuid = is_2 ? "VUID-VkImageBlit2-layerCount-08801" : "VUID-VkImageBlit-layerCount-08801";
                    skip |= LogError(
                        vuid, dst_objlist, src_subresource_loc.dot(Field::layerCount),
                        "(%" PRIu32 ") does not match dstImage arrayLayers (%" PRIu32 ") minus baseArrayLayer (%" PRIu32 ").",
                        src_subresource.layerCount, dst_image_state->createInfo.arrayLayers, dst_subresource.baseArrayLayer);
                }
            } else if (dst_subresource.layerCount != VK_REMAINING_ARRAY_LAYERS) {
                if (dst_subresource.layerCount != (src_image_state->createInfo.arrayLayers - src_subresource.baseArrayLayer)) {
                    vuid = is_2 ? "VUID-VkImageBlit2-layerCount-08801" : "VUID-VkImageBlit-layerCount-08801";
                    skip |= LogError(
                        vuid, src_objlist, dst_subresource_loc.dot(Field::layerCount),
                        "(%" PRIu32 ") does not match srcImage arrayLayers (%" PRIu32 ") minus baseArrayLayer (%" PRIu32 ").",
                        dst_subresource.layerCount, src_image_state->createInfo.arrayLayers, src_subresource.baseArrayLayer);
                }
            }
        } else if (src_subresource.layerCount != dst_subresource.layerCount) {
            vuid = is_2 ? "VUID-VkImageBlit2-layerCount-08800" : "VUID-VkImageBlit-layerCount-08800";
            skip |= LogError(vuid, all_objlist, src_subresource_loc.dot(Field::layerCount),
                             "(%" PRIu32 ") does not match %s (%" PRIu32 ").", src_subresource.layerCount,
                             dst_subresource_loc.dot(Field::layerCount).Fields().c_str(), dst_subresource.layerCount);
        }

        if (src_subresource.aspectMask != dst_subresource.aspectMask) {
            vuid = is_2 ? "VUID-VkImageBlit2-aspectMask-00238" : "VUID-VkImageBlit-aspectMask-00238";
            skip |= LogError(vuid, all_objlist, src_subresource_loc.dot(Field::aspectMask), "(%s) does not match %s (%s).",
                             string_VkImageAspectFlags(src_subresource.aspectMask).c_str(),
                             dst_subresource_loc.dot(Field::aspectMask).Fields().c_str(),
                             string_VkImageAspectFlags(dst_subresource.aspectMask).c_str());
        }

        if (!VerifyAspectsPresent(src_subresource.aspectMask, src_format)) {
            vuid = is_2 ? "VUID-VkBlitImageInfo2-aspectMask-00241" : "VUID-vkCmdBlitImage-aspectMask-00241";
            skip |= LogError(vuid, src_objlist, src_subresource_loc.dot(Field::aspectMask),
                             "(%s) cannot specify aspects not present in source image (%s).",
                             string_VkImageAspectFlags(src_subresource.aspectMask).c_str(), string_VkFormat(src_format));
        }

        if (!VerifyAspectsPresent(dst_subresource.aspectMask, dst_format)) {
            vuid = is_2 ? "VUID-VkBlitImageInfo2-aspectMask-00242" : "VUID-vkCmdBlitImage-aspectMask-00242";
            skip |= LogError(vuid, dst_objlist, dst_subresource_loc.dot(Field::aspectMask),
                             "(%s) cannot specify aspects not present in destination image (%s).",
                             string_VkImageAspectFlags(src_subresource.aspectMask).c_str(), string_VkFormat(src_format));
        }

        // Validate source image offsets
        VkExtent3D src_extent = src_image_state->GetEffectiveSubresourceExtent(src_subresource);
        if (VK_IMAGE_TYPE_1D == src_type) {
            if ((0 != region.srcOffsets[0].y) || (1 != region.srcOffsets[1].y)) {
                vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00245" : "VUID-vkCmdBlitImage-srcImage-00245";
                skip |=
                    LogError(vuid, src_objlist, region_loc,
                             "srcOffsets[0].y is %" PRId32 " and srcOffsets[1].y is %" PRId32 " but srcImage is VK_IMAGE_TYPE_1D.",
                             region.srcOffsets[0].y, region.srcOffsets[1].y);
            }
        }

        if ((VK_IMAGE_TYPE_1D == src_type) || (VK_IMAGE_TYPE_2D == src_type)) {
            if ((0 != region.srcOffsets[0].z) || (1 != region.srcOffsets[1].z)) {
                vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00247" : "VUID-vkCmdBlitImage-srcImage-00247";
                skip |= LogError(vuid, src_objlist, region_loc,
                                 "srcOffsets[0].z is %" PRId32 " and srcOffsets[1].z is %" PRId32 " but srcImage is %s.",
                                 region.srcOffsets[0].z, region.srcOffsets[1].z, string_VkImageType(src_type));
            }
        }

        bool oob = false;
        if ((region.srcOffsets[0].x < 0) || (region.srcOffsets[0].x > static_cast<int32_t>(src_extent.width)) ||
            (region.srcOffsets[1].x < 0) || (region.srcOffsets[1].x > static_cast<int32_t>(src_extent.width))) {
            oob = true;
            vuid = is_2 ? "VUID-VkBlitImageInfo2-srcOffset-00243" : "VUID-vkCmdBlitImage-srcOffset-00243";
            skip |= LogError(vuid, src_objlist, region_loc,
                             "srcOffsets[0].x is %" PRId32 " and srcOffsets[1].x is %" PRId32
                             " which exceed srcSubresource width extent (%" PRIu32 ").",
                             region.srcOffsets[0].x, region.srcOffsets[1].x, src_extent.width);
        }
        if ((region.srcOffsets[0].y < 0) || (region.srcOffsets[0].y > static_cast<int32_t>(src_extent.height)) ||
            (region.srcOffsets[1].y < 0) || (region.srcOffsets[1].y > static_cast<int32_t>(src_extent.height))) {
            oob = true;
            vuid = is_2 ? "VUID-VkBlitImageInfo2-srcOffset-00244" : "VUID-vkCmdBlitImage-srcOffset-00244";
            skip |= LogError(vuid, src_objlist, region_loc,
                             "srcOffsets[0].y is %" PRId32 " and srcOffsets[1].y is %" PRId32
                             " which exceed srcSubresource height extent (%" PRIu32 ").",
                             region.srcOffsets[0].y, region.srcOffsets[1].y, src_extent.height);
        }
        if ((region.srcOffsets[0].z < 0) || (region.srcOffsets[0].z > static_cast<int32_t>(src_extent.depth)) ||
            (region.srcOffsets[1].z < 0) || (region.srcOffsets[1].z > static_cast<int32_t>(src_extent.depth))) {
            oob = true;
            vuid = is_2 ? "VUID-VkBlitImageInfo2-srcOffset-00246" : "VUID-vkCmdBlitImage-srcOffset-00246";
            skip |= LogError(vuid, src_objlist, region_loc,
                             "srcOffsets[0].z is %" PRId32 " and srcOffsets[1].z is %" PRId32
                             " which exceed srcSubresource depth extent (%" PRIu32 ").",
                             region.srcOffsets[0].z, region.srcOffsets[1].z, src_extent.depth);
        }
        if (oob) {
            vuid = is_2 ? "VUID-VkBlitImageInfo2-pRegions-00215" : "VUID-vkCmdBlitImage-pRegions-00215";
            skip |= LogError(vuid, src_objlist, region_loc, "source image blit region exceeds image dimensions.");
        }

        // Validate dest image offsets
        VkExtent3D dst_extent = dst_image_state->GetEffectiveSubresourceExtent(dst_subresource);
        if (VK_IMAGE_TYPE_1D == dst_type) {
            if ((0 != region.dstOffsets[0].y) || (1 != region.dstOffsets[1].y)) {
                vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImage-00250" : "VUID-vkCmdBlitImage-dstImage-00250";
                skip |=
                    LogError(vuid, dst_objlist, region_loc,
                             "dstOffsets[0].y is %" PRId32 " and dstOffsets[1].y is %" PRId32 " but dstImage is VK_IMAGE_TYPE_1D.",
                             region.dstOffsets[0].y, region.dstOffsets[1].y);
            }
        }

        if ((VK_IMAGE_TYPE_1D == dst_type) || (VK_IMAGE_TYPE_2D == dst_type)) {
            if ((0 != region.dstOffsets[0].z) || (1 != region.dstOffsets[1].z)) {
                vuid = is_2 ? "VUID-VkBlitImageInfo2-dstImage-00252" : "VUID-vkCmdBlitImage-dstImage-00252";
                skip |= LogError(vuid, dst_objlist, region_loc,
                                 "dstOffsets[0].z is %" PRId32 " and dstOffsets[1].z is %" PRId32 " but dstImage is %s.",
                                 region.dstOffsets[0].z, region.dstOffsets[1].z, string_VkImageType(dst_type));
            }
        }

        oob = false;
        if ((region.dstOffsets[0].x < 0) || (region.dstOffsets[0].x > static_cast<int32_t>(dst_extent.width)) ||
            (region.dstOffsets[1].x < 0) || (region.dstOffsets[1].x > static_cast<int32_t>(dst_extent.width))) {
            oob = true;
            vuid = is_2 ? "VUID-VkBlitImageInfo2-dstOffset-00248" : "VUID-vkCmdBlitImage-dstOffset-00248";
            skip |= LogError(vuid, dst_objlist, region_loc,
                             "dstOffsets[0].x is %" PRId32 " and dstOffsets[1].x is %" PRId32
                             " which exceed dstSubresource width extent (%" PRIu32 ").",
                             region.dstOffsets[0].x, region.dstOffsets[1].x, dst_extent.width);
        }
        if ((region.dstOffsets[0].y < 0) || (region.dstOffsets[0].y > static_cast<int32_t>(dst_extent.height)) ||
            (region.dstOffsets[1].y < 0) || (region.dstOffsets[1].y > static_cast<int32_t>(dst_extent.height))) {
            oob = true;
            vuid = is_2 ? "VUID-VkBlitImageInfo2-dstOffset-00249" : "VUID-vkCmdBlitImage-dstOffset-00249";
            skip |= LogError(vuid, dst_objlist, region_loc,
                             "dstOffsets[0].y is %" PRId32 " and dstOffsets[1].y is %" PRId32
                             " which exceed dstSubresource height extent (%" PRIu32 ").",
                             region.dstOffsets[0].x, region.dstOffsets[1].x, dst_extent.height);
        }
        if ((region.dstOffsets[0].z < 0) || (region.dstOffsets[0].z > static_cast<int32_t>(dst_extent.depth)) ||
            (region.dstOffsets[1].z < 0) || (region.dstOffsets[1].z > static_cast<int32_t>(dst_extent.depth))) {
            oob = true;
            vuid = is_2 ? "VUID-VkBlitImageInfo2-dstOffset-00251" : "VUID-vkCmdBlitImage-dstOffset-00251";
            skip |= LogError(vuid, dst_objlist, region_loc,
                             "dstOffsets[0].z is %" PRId32 " and dstOffsets[1].z is %" PRId32
                             " which exceed dstSubresource depth extent (%" PRIu32 ").",
                             region.dstOffsets[0].z, region.dstOffsets[1].z, dst_extent.depth);
        }
        if (oob) {
            vuid = is_2 ? "VUID-VkBlitImageInfo2-pRegions-00216" : "VUID-vkCmdBlitImage-pRegions-00216";
            skip |= LogError(vuid, dst_objlist, region_loc, "destination image blit region exceeds image dimensions.");
        }

        if ((VK_IMAGE_TYPE_3D == src_type) || (VK_IMAGE_TYPE_3D == dst_type)) {
            if ((0 != src_subresource.baseArrayLayer) || (1 != src_subresource.layerCount) ||
                (0 != dst_subresource.baseArrayLayer) || (1 != dst_subresource.layerCount)) {
                vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00240" : "VUID-vkCmdBlitImage-srcImage-00240";
                skip |= LogError(vuid, all_objlist, region_loc,
                                 "srcImage %s\n"
                                 "dstImage %s\n"
                                 "srcSubresource (baseArrayLayer = %" PRIu32 ", layerCount = %" PRIu32
                                 ")\n"
                                 "dstSubresource (baseArrayLayer = %" PRIu32 ", layerCount = %" PRIu32 ")\n",
                                 string_VkImageType(src_type), string_VkImageType(dst_type), src_subresource.baseArrayLayer,
                                 src_subresource.layerCount, dst_subresource.baseArrayLayer, dst_subresource.layerCount);
            }
        }

        // The union of all source regions, and the union of all destination regions, specified by the elements of regions,
        // must not overlap in memory
        if (srcImage == dstImage) {
            for (uint32_t j = 0; j < regionCount; j++) {
                if (RegionIntersectsBlit(&region, &pRegions[j], src_image_state->createInfo.imageType,
                                         vkuFormatIsMultiplane(src_format))) {
                    vuid = is_2 ? "VUID-VkBlitImageInfo2-pRegions-00217" : "VUID-vkCmdBlitImage-pRegions-00217";
                    skip |=
                        LogError(vuid, all_objlist, loc, "pRegion[%" PRIu32 "] src overlaps with pRegions[%" PRIu32 "] dst.", i, j);
                }
            }
        }
    }
    return skip;
}

bool CoreChecks::PreCallValidateCmdBlitImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                             VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                             const VkImageBlit *pRegions, VkFilter filter, const ErrorObject &error_obj) const {
    return ValidateCmdBlitImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter,
                                error_obj.location);
}

bool CoreChecks::PreCallValidateCmdBlitImage2KHR(VkCommandBuffer commandBuffer, const VkBlitImageInfo2KHR *pBlitImageInfo,
                                                 const ErrorObject &error_obj) const {
    return PreCallValidateCmdBlitImage2(commandBuffer, pBlitImageInfo, error_obj);
}

bool CoreChecks::PreCallValidateCmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2 *pBlitImageInfo,
                                              const ErrorObject &error_obj) const {
    return ValidateCmdBlitImage(commandBuffer, pBlitImageInfo->srcImage, pBlitImageInfo->srcImageLayout, pBlitImageInfo->dstImage,
                                pBlitImageInfo->dstImageLayout, pBlitImageInfo->regionCount, pBlitImageInfo->pRegions,
                                pBlitImageInfo->filter, error_obj.location.dot(Field::pBlitImageInfo));
}

template <typename RegionType>
void CoreChecks::RecordCmdBlitImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage,
                                    VkImageLayout dstImageLayout, uint32_t regionCount, const RegionType *pRegions,
                                    VkFilter filter) {
    auto cb_state_ptr = GetWrite<vvl::CommandBuffer>(commandBuffer);
    auto src_image_state = Get<vvl::Image>(srcImage);
    auto dst_image_state = Get<vvl::Image>(dstImage);
    if (cb_state_ptr && src_image_state && dst_image_state) {
        // Make sure that all image slices are updated to correct layout
        for (uint32_t i = 0; i < regionCount; ++i) {
            cb_state_ptr->SetImageInitialLayout(*src_image_state, pRegions[i].srcSubresource, srcImageLayout);
            cb_state_ptr->SetImageInitialLayout(*dst_image_state, pRegions[i].dstSubresource, dstImageLayout);
        }
    }
}

void CoreChecks::PreCallRecordCmdBlitImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                           VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                           const VkImageBlit *pRegions, VkFilter filter, const RecordObject &record_obj) {
    StateTracker::PreCallRecordCmdBlitImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount,
                                            pRegions, filter, record_obj);
    RecordCmdBlitImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter);
}

void CoreChecks::PreCallRecordCmdBlitImage2KHR(VkCommandBuffer commandBuffer, const VkBlitImageInfo2KHR *pBlitImageInfo,
                                               const RecordObject &record_obj) {
    PreCallRecordCmdBlitImage2(commandBuffer, pBlitImageInfo, record_obj);
}

void CoreChecks::PreCallRecordCmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2KHR *pBlitImageInfo,
                                            const RecordObject &record_obj) {
    StateTracker::PreCallRecordCmdBlitImage2(commandBuffer, pBlitImageInfo, record_obj);
    RecordCmdBlitImage(commandBuffer, pBlitImageInfo->srcImage, pBlitImageInfo->srcImageLayout, pBlitImageInfo->dstImage,
                       pBlitImageInfo->dstImageLayout, pBlitImageInfo->regionCount, pBlitImageInfo->pRegions,
                       pBlitImageInfo->filter);
}

template <typename RegionType>
bool CoreChecks::ValidateCmdResolveImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                         VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                         const RegionType *pRegions, const Location &loc) const {
    bool skip = false;
    auto cb_state_ptr = GetRead<vvl::CommandBuffer>(commandBuffer);
    auto src_image_state = Get<vvl::Image>(srcImage);
    auto dst_image_state = Get<vvl::Image>(dstImage);
    if (!cb_state_ptr || !src_image_state || !dst_image_state) {
        return skip;
    }

    const bool is_2 = loc.function == Func::vkCmdResolveImage2 || loc.function == Func::vkCmdResolveImage2KHR;
    const char *vuid;
    const Location src_image_loc = loc.dot(Field::srcImage);
    const Location dst_image_loc = loc.dot(Field::dstImage);

    const LogObjectList src_objlist(commandBuffer, srcImage);
    const LogObjectList dst_objlist(commandBuffer, dstImage);
    const LogObjectList all_objlist(commandBuffer, srcImage, dstImage);

    const vvl::CommandBuffer &cb_state = *cb_state_ptr;
    vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImage-00256" : "VUID-vkCmdResolveImage-srcImage-00256";
    skip |= ValidateMemoryIsBoundToImage(src_objlist, *src_image_state, src_image_loc, vuid);
    vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImage-00258" : "VUID-vkCmdResolveImage-dstImage-00258";
    skip |= ValidateMemoryIsBoundToImage(dst_objlist, *dst_image_state, dst_image_loc, vuid);
    skip |= ValidateCmd(cb_state, loc);
    vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImage-02003" : "VUID-vkCmdResolveImage-dstImage-02003";
    skip |= ValidateImageFormatFeatureFlags(commandBuffer, *dst_image_state, VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT,
                                            dst_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdResolveImage2-commandBuffer-01837" : "VUID-vkCmdResolveImage-commandBuffer-01837";
    skip |= ValidateProtectedImage(cb_state, *src_image_state, src_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdResolveImage2-commandBuffer-01838" : "VUID-vkCmdResolveImage-commandBuffer-01838";
    skip |= ValidateProtectedImage(cb_state, *dst_image_state, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-vkCmdResolveImage2-commandBuffer-01839" : "VUID-vkCmdResolveImage-commandBuffer-01839";
    skip |= ValidateUnprotectedImage(cb_state, *dst_image_state, dst_image_loc, vuid);
    vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImage-06762" : "VUID-vkCmdResolveImage-srcImage-06762";
    skip |= ValidateImageUsageFlags(commandBuffer, *src_image_state, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, true, vuid, src_image_loc);
    vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImage-06763" : "VUID-vkCmdResolveImage-srcImage-06763";
    skip |=
        ValidateImageFormatFeatureFlags(commandBuffer, *src_image_state, VK_FORMAT_FEATURE_TRANSFER_SRC_BIT, src_image_loc, vuid);
    vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImage-06764" : "VUID-vkCmdResolveImage-dstImage-06764";
    skip |= ValidateImageUsageFlags(commandBuffer, *dst_image_state, VK_IMAGE_USAGE_TRANSFER_DST_BIT, true, vuid, dst_image_loc);
    vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImage-06765" : "VUID-vkCmdResolveImage-dstImage-06765";
    skip |=
        ValidateImageFormatFeatureFlags(commandBuffer, *dst_image_state, VK_FORMAT_FEATURE_TRANSFER_DST_BIT, dst_image_loc, vuid);

    // Validation for VK_EXT_fragment_density_map
    if (src_image_state->createInfo.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
        vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImage-02546" : "VUID-vkCmdResolveImage-dstImage-02546";
        skip |= LogError(vuid, src_objlist, src_image_loc,
                         "must not have been created with flags containing "
                         "VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT");
    }
    if (dst_image_state->createInfo.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
        vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImage-02546" : "VUID-vkCmdResolveImage-dstImage-02546";
        skip |= LogError(vuid, dst_objlist, dst_image_loc,
                         "must not have been created with flags containing "
                         "VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT");
    }

    if (!IsValueIn(srcImageLayout,
                   {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL})) {
        vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImageLayout-01400" : "VUID-vkCmdResolveImage-srcImageLayout-01400";
        const LogObjectList objlist(cb_state.commandBuffer(), src_image_state->Handle());
        skip |= LogError(vuid, objlist, loc.dot(Field::srcImageLayout), "is %s.", string_VkImageLayout(srcImageLayout));
    }

    if (!IsValueIn(dstImageLayout,
                   {VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL})) {
        vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImageLayout-01401" : "VUID-vkCmdResolveImage-dstImageLayout-01401";
        const LogObjectList objlist(cb_state.commandBuffer(), dst_image_state->Handle());
        skip |= LogError(vuid, objlist, loc.dot(Field::dstImageLayout), "is %s.", string_VkImageLayout(dstImageLayout));
    }

    // For each region, the number of layers in the image subresource should not be zero
    // For each region, src and dest image aspect must be color only
    for (uint32_t i = 0; i < regionCount; i++) {
        const Location region_loc = loc.dot(Field::pRegions, i);
        const Location src_subresource_loc = region_loc.dot(Field::srcSubresource);
        const Location dst_subresource_loc = region_loc.dot(Field::dstSubresource);
        const RegionType region = pRegions[i];
        const VkImageSubresourceLayers &src_subresource = region.srcSubresource;
        const VkImageSubresourceLayers &dst_subresource = region.dstSubresource;

        skip |= ValidateImageSubresourceLayers(cb_state.commandBuffer(), &src_subresource, src_subresource_loc);
        skip |= ValidateImageSubresourceLayers(cb_state.commandBuffer(), &dst_subresource, dst_subresource_loc);
        vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImageLayout-00260" : "VUID-vkCmdResolveImage-srcImageLayout-00260";
        skip |= VerifyImageLayoutSubresource(cb_state, *src_image_state, src_subresource, srcImageLayout, src_image_loc, vuid);
        vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImageLayout-00262" : "VUID-vkCmdResolveImage-dstImageLayout-00262";
        skip |= VerifyImageLayoutSubresource(cb_state, *dst_image_state, dst_subresource, dstImageLayout, dst_image_loc, vuid);
        vuid = is_2 ? "VUID-VkResolveImageInfo2-srcSubresource-01709" : "VUID-vkCmdResolveImage-srcSubresource-01709";
        skip |= ValidateImageMipLevel(commandBuffer, *src_image_state, src_subresource.mipLevel,
                                      src_subresource_loc.dot(Field::mipLevel), vuid);
        vuid = is_2 ? "VUID-VkResolveImageInfo2-dstSubresource-01710" : "VUID-vkCmdResolveImage-dstSubresource-01710";
        skip |= ValidateImageMipLevel(commandBuffer, *dst_image_state, dst_subresource.mipLevel,
                                      dst_subresource_loc.dot(Field::mipLevel), vuid);
        vuid = is_2 ? "VUID-VkResolveImageInfo2-srcSubresource-01711" : "VUID-vkCmdResolveImage-srcSubresource-01711";
        skip |= ValidateImageArrayLayerRange(commandBuffer, *src_image_state, src_subresource.baseArrayLayer,
                                             src_subresource.layerCount, src_subresource_loc, vuid);
        vuid = is_2 ? "VUID-VkResolveImageInfo2-dstSubresource-01712" : "VUID-vkCmdResolveImage-dstSubresource-01712";
        skip |= ValidateImageArrayLayerRange(commandBuffer, *dst_image_state, dst_subresource.baseArrayLayer,
                                             dst_subresource.layerCount, dst_subresource_loc, vuid);

        if (src_subresource.layerCount == VK_REMAINING_ARRAY_LAYERS || dst_subresource.layerCount == VK_REMAINING_ARRAY_LAYERS) {
            if (src_subresource.layerCount != VK_REMAINING_ARRAY_LAYERS) {
                if (src_subresource.layerCount != (dst_image_state->createInfo.arrayLayers - dst_subresource.baseArrayLayer)) {
                    vuid = is_2 ? "VUID-VkImageResolve2-layerCount-08804" : "VUID-VkImageResolve-layerCount-08804";
                    skip |= LogError(
                        vuid, dst_objlist, src_subresource_loc.dot(Field::layerCount),
                        "(%" PRIu32 ") does not match dstImage arrayLayers (%" PRIu32 ") minus baseArrayLayer (%" PRIu32 ").",
                        src_subresource.layerCount, dst_image_state->createInfo.arrayLayers, dst_subresource.baseArrayLayer);
                }
            } else if (dst_subresource.layerCount != VK_REMAINING_ARRAY_LAYERS) {
                if (dst_subresource.layerCount != (src_image_state->createInfo.arrayLayers - src_subresource.baseArrayLayer)) {
                    vuid = is_2 ? "VUID-VkImageResolve2-layerCount-08804" : "VUID-VkImageResolve-layerCount-08804";
                    skip |= LogError(
                        vuid, src_objlist, dst_subresource_loc.dot(Field::layerCount),
                        "(%" PRIu32 ") does not match srcImage arrayLayers (%" PRIu32 ") minus baseArrayLayer (%" PRIu32 ").",
                        dst_subresource.layerCount, src_image_state->createInfo.arrayLayers, src_subresource.baseArrayLayer);
                }
            }
        } else if (src_subresource.layerCount != dst_subresource.layerCount) {
            vuid = is_2 ? "VUID-VkImageResolve2-layerCount-08803" : "VUID-VkImageResolve-layerCount-08803";
            skip |= LogError(vuid, all_objlist, src_subresource_loc.dot(Field::layerCount),
                             "(%" PRIu32 ") does not match %s (%" PRIu32 ").", region.srcSubresource.layerCount,
                             dst_subresource_loc.dot(Field::layerCount).Fields().c_str(), region.dstSubresource.layerCount);
        }
        // For each region, src and dest image aspect must be color only
        if ((src_subresource.aspectMask != VK_IMAGE_ASPECT_COLOR_BIT) ||
            (dst_subresource.aspectMask != VK_IMAGE_ASPECT_COLOR_BIT)) {
            vuid = is_2 ? "VUID-VkImageResolve2-aspectMask-00266" : "VUID-VkImageResolve-aspectMask-00266";
            skip |= LogError(vuid, all_objlist, src_subresource_loc.dot(Field::aspectMask),
                             "(%s) and dstSubresource.aspectMask (%s) must only be VK_IMAGE_ASPECT_COLOR_BIT.",
                             string_VkImageAspectFlags(src_subresource.aspectMask).c_str(),
                             string_VkImageAspectFlags(dst_subresource.aspectMask).c_str());
        }

        const VkImageType src_image_type = src_image_state->createInfo.imageType;
        const VkImageType dst_image_type = dst_image_state->createInfo.imageType;

        if (VK_IMAGE_TYPE_3D == dst_image_type) {
            if (src_subresource.layerCount != 1) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImage-04446" : "VUID-vkCmdResolveImage-srcImage-04446";
                skip |= LogError(vuid, src_objlist, src_subresource_loc.dot(Field::layerCount),
                                 "is %" PRIu32 " but dstImage is 3D.", src_subresource.layerCount);
            }
            if ((dst_subresource.baseArrayLayer != 0) || (dst_subresource.layerCount != 1)) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImage-04447" : "VUID-vkCmdResolveImage-srcImage-04447";
                skip |= LogError(vuid, dst_objlist, dst_subresource_loc.dot(Field::baseArrayLayer),
                                 "is %" PRIu32 " and layerCount is %" PRIu32 " but dstImage 3D.", dst_subresource.baseArrayLayer,
                                 dst_subresource.layerCount);
            }
        }

        if (VK_IMAGE_TYPE_1D == src_image_type) {
            if ((region.srcOffset.y != 0) || (region.extent.height != 1)) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImage-00271" : "VUID-vkCmdResolveImage-srcImage-00271";
                skip |= LogError(vuid, src_objlist, region_loc,
                                 "srcOffset.y is %" PRId32 ", extent.height is %" PRIu32 ", but srcImage (%s) is 1D.",
                                 region.srcOffset.y, region.extent.height, FormatHandle(src_image_state->image()).c_str());
            }
        }
        if ((VK_IMAGE_TYPE_1D == src_image_type) || (VK_IMAGE_TYPE_2D == src_image_type)) {
            if ((region.srcOffset.z != 0) || (region.extent.depth != 1)) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImage-00273" : "VUID-vkCmdResolveImage-srcImage-00273";
                skip |= LogError(vuid, src_objlist, region_loc,
                                 "srcOffset.z is %" PRId32 ", extent.depth is %" PRIu32 ", but srcImage (%s) is 2D.",
                                 region.srcOffset.z, region.extent.depth, FormatHandle(src_image_state->image()).c_str());
            }
        }

        if (VK_IMAGE_TYPE_1D == dst_image_type) {
            if ((region.dstOffset.y != 0) || (region.extent.height != 1)) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImage-00276" : "VUID-vkCmdResolveImage-dstImage-00276";
                skip |= LogError(vuid, dst_objlist, region_loc,
                                 "dstOffset.y is %" PRId32 ", extent.height is %" PRIu32 ", but dstImage (%s) is 1D.",
                                 region.dstOffset.y, region.extent.height, FormatHandle(dst_image_state->image()).c_str());
            }
        }
        if ((VK_IMAGE_TYPE_1D == dst_image_type) || (VK_IMAGE_TYPE_2D == dst_image_type)) {
            if ((region.dstOffset.z != 0) || (region.extent.depth != 1)) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImage-00278" : "VUID-vkCmdResolveImage-dstImage-00278";
                skip |= LogError(vuid, dst_objlist, region_loc,
                                 "dstOffset.z is %" PRId32 ", extent.depth is %" PRIu32 ", but dstImage (%s) is 2D.",
                                 region.dstOffset.z, region.extent.depth, FormatHandle(dst_image_state->image()).c_str());
            }
        }

        // Each srcImage dimension offset + extent limits must fall with image subresource extent
        VkExtent3D subresource_extent = src_image_state->GetEffectiveSubresourceExtent(src_subresource);
        // MipLevel bound is checked already and adding extra errors with a "subresource extent of zero" is confusing to
        // developer
        if (src_subresource.mipLevel < src_image_state->createInfo.mipLevels) {
            uint32_t extent_check = ExceedsBounds(&(region.srcOffset), &(region.extent), &subresource_extent);
            if ((extent_check & kXBit) != 0) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-srcOffset-00269" : "VUID-vkCmdResolveImage-srcOffset-00269";
                skip |= LogError(vuid, src_objlist, region_loc,
                                 "srcOffset.x (%" PRId32 ") + extent.width (%" PRIu32
                                 ") exceeds srcSubresource.extent.width (%" PRIu32 ").",
                                 region.srcOffset.x, region.extent.width, subresource_extent.width);
            }

            if ((extent_check & kYBit) != 0) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-srcOffset-00270" : "VUID-vkCmdResolveImage-srcOffset-00270";
                skip |= LogError(vuid, src_objlist, region_loc,
                                 "srcOffset.x (%" PRId32 ") + extent.height (%" PRIu32
                                 ") exceeds srcSubresource.extent.height (%" PRIu32 ").",
                                 region.srcOffset.y, region.extent.height, subresource_extent.height);
            }

            if ((extent_check & kZBit) != 0) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-srcOffset-00272" : "VUID-vkCmdResolveImage-srcOffset-00272";
                skip |= LogError(vuid, src_objlist, region_loc,
                                 "srcOffset.x (%" PRId32 ") + extent.depth (%" PRIu32
                                 ") exceeds srcSubresource.extent.depth (%" PRIu32 ").",
                                 region.srcOffset.z, region.extent.depth, subresource_extent.depth);
            }
        }

        // Each dstImage dimension offset + extent limits must fall with image subresource extent
        subresource_extent = dst_image_state->GetEffectiveSubresourceExtent(dst_subresource);
        // MipLevel bound is checked already and adding extra errors with a "subresource extent of zero" is confusing to
        // developer
        if (dst_subresource.mipLevel < dst_image_state->createInfo.mipLevels) {
            uint32_t extent_check = ExceedsBounds(&(region.dstOffset), &(region.extent), &subresource_extent);
            if ((extent_check & kXBit) != 0) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-dstOffset-00274" : "VUID-vkCmdResolveImage-dstOffset-00274";
                skip |= LogError(vuid, dst_objlist, region_loc,
                                 "dstOffset.x (%" PRId32 ") + extent.width (%" PRIu32
                                 ") exceeds dstSubresource.extent.width (%" PRIu32 ").",
                                 region.dstOffset.x, region.extent.width, subresource_extent.width);
            }

            if ((extent_check & kYBit) != 0) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-dstOffset-00275" : "VUID-vkCmdResolveImage-dstOffset-00275";
                skip |= LogError(vuid, dst_objlist, region_loc,
                                 "dstOffset.x (%" PRId32 ") + extent.height (%" PRIu32
                                 ") exceeds dstSubresource.extent.height (%" PRIu32 ").",
                                 region.dstOffset.x, region.extent.height, subresource_extent.height);
            }

            if ((extent_check & kZBit) != 0) {
                vuid = is_2 ? "VUID-VkResolveImageInfo2-dstOffset-00277" : "VUID-vkCmdResolveImage-dstOffset-00277";
                skip |= LogError(vuid, dst_objlist, region_loc,
                                 "dstOffset.x (%" PRId32 ") + extent.depth (%" PRIu32
                                 ") exceeds dstSubresource.extent.depth (%" PRIu32 ").",
                                 region.dstOffset.x, region.extent.depth, subresource_extent.depth);
            }
        }
    }

    if (src_image_state->createInfo.format != dst_image_state->createInfo.format) {
        vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImage-01386" : "VUID-vkCmdResolveImage-srcImage-01386";
        skip |= LogError(vuid, all_objlist, src_image_loc, "was created with format %s but dstImage format is %s.",
                         string_VkFormat(src_image_state->createInfo.format), string_VkFormat(dst_image_state->createInfo.format));
    }
    if (src_image_state->createInfo.samples == VK_SAMPLE_COUNT_1_BIT) {
        vuid = is_2 ? "VUID-VkResolveImageInfo2-srcImage-00257" : "VUID-vkCmdResolveImage-srcImage-00257";
        skip |= LogError(vuid, src_objlist, src_image_loc, "was created with sample count VK_SAMPLE_COUNT_1_BIT.");
    }
    if (dst_image_state->createInfo.samples != VK_SAMPLE_COUNT_1_BIT) {
        vuid = is_2 ? "VUID-VkResolveImageInfo2-dstImage-00259" : "VUID-vkCmdResolveImage-dstImage-00259";
        skip |= LogError(vuid, dst_objlist, dst_image_loc, "was created with sample count (%s) (not VK_SAMPLE_COUNT_1_BIT).",
                         string_VkSampleCountFlagBits(dst_image_state->createInfo.samples));
    }
    return skip;
}

bool CoreChecks::PreCallValidateCmdResolveImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                                VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                                const VkImageResolve *pRegions, const ErrorObject &error_obj) const {
    return ValidateCmdResolveImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions,
                                   error_obj.location);
}

bool CoreChecks::PreCallValidateCmdResolveImage2KHR(VkCommandBuffer commandBuffer, const VkResolveImageInfo2KHR *pResolveImageInfo,
                                                    const ErrorObject &error_obj) const {
    return PreCallValidateCmdResolveImage2(commandBuffer, pResolveImageInfo, error_obj);
}

bool CoreChecks::PreCallValidateCmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2 *pResolveImageInfo,
                                                 const ErrorObject &error_obj) const {
    return ValidateCmdResolveImage(commandBuffer, pResolveImageInfo->srcImage, pResolveImageInfo->srcImageLayout,
                                   pResolveImageInfo->dstImage, pResolveImageInfo->dstImageLayout, pResolveImageInfo->regionCount,
                                   pResolveImageInfo->pRegions, error_obj.location.dot(Field::pResolveImageInfo));
}
