Repository: KhronosGroup/Vulkan-ValidationLayers
Pull request #9751: Move logic into Image/ImageView state and fix VK_REMAINING_ARRAY_LAYERS 
Author: login: spencer-lunarg
Created: 2025-03-21T18:46:53Z | Merged: 2025-03-21T19:19:14Z

@ziga-lunarg discovered in https://github.com/KhronosGroup/Vulkan-ValidationLayers/pull/9747 we messed up the Array Layers using `VK_REMAINING_ARRAY_LAYERS` (which you could only use in Blit/Resolve since maintenance5)

We have this "Normalize" logic for `VkImageSubresourceRange` but not for `VkImageSubresourceLayers` and this 

1. Move the Normalize functions to be Image/ImageView state function
2. Fix Resolve check like the other PR
3. Print better error message for the blit case


Diff:
diff --git a/layers/core_checks/cc_copy_blit_resolve.cpp b/layers/core_checks/cc_copy_blit_resolve.cpp
index bccf7cd7fe8..00bac51904f 100644
--- a/layers/core_checks/cc_copy_blit_resolve.cpp
+++ b/layers/core_checks/cc_copy_blit_resolve.cpp
@@ -3170,9 +3170,13 @@ bool CoreChecks::ValidateCmdBlitImage(VkCommandBuffer commandBuffer, VkImage src
         const VkImageSubresourceLayers &src_subresource = region.srcSubresource;
         const VkImageSubresourceLayers &dst_subresource = region.dstSubresource;
 
+        // Will resolve VK_REMAINING_ARRAY_LAYERS to actual value (some VUs just want the value)
+        const uint32_t normalized_src_layer_count = src_image_state->NormalizeLayerCount(src_subresource);
+        const uint32_t normalized_dst_layer_count = dst_image_state->NormalizeLayerCount(dst_subresource);
+
         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));
+                                       RangesIntersect(src_subresource.baseArrayLayer, normalized_src_layer_count,
+                                                       dst_subresource.baseArrayLayer, normalized_dst_layer_count));
         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})) {
@@ -3181,8 +3185,8 @@ bool CoreChecks::ValidateCmdBlitImage(VkCommandBuffer commandBuffer, VkImage src
                                  "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),
+                                 src_subresource.baseArrayLayer + normalized_src_layer_count, dst_subresource.baseArrayLayer,
+                                 dst_subresource.baseArrayLayer + normalized_dst_layer_count, string_VkImageLayout(srcImageLayout),
                                  string_VkImageLayout(dstImageLayout));
             }
         }
@@ -3359,56 +3363,62 @@ bool CoreChecks::ValidateCmdBlitImage(VkCommandBuffer commandBuffer, VkImage src
 
         // pre-maintenance8 both src/dst had to both match, after we only validate them independently
         if (!enabled_features.maintenance8 && (src_type == VK_IMAGE_TYPE_3D || dst_type == VK_IMAGE_TYPE_3D)) {
-            if ((src_subresource.baseArrayLayer != 0) || (src_subresource.layerCount != 1) ||
-                (dst_subresource.baseArrayLayer != 0) || (dst_subresource.layerCount != 1)) {
+            if ((src_subresource.baseArrayLayer != 0) || (normalized_src_layer_count != 1) ||
+                (dst_subresource.baseArrayLayer != 0) || (normalized_dst_layer_count != 1)) {
                 vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00240" : "VUID-vkCmdBlitImage-srcImage-00240";
                 skip |= LogError(vuid, all_objlist, region_loc,
+                                 "Using 3D image so only the first layer may be used\n"
                                  "srcImage %s\n"
                                  "dstImage %s\n"
-                                 "srcSubresource (baseArrayLayer = %" PRIu32 ", layerCount = %" PRIu32
-                                 ")\n"
-                                 "dstSubresource (baseArrayLayer = %" PRIu32 ", layerCount = %" PRIu32 ")\n",
+                                 "srcSubresource (baseArrayLayer = %" PRIu32
+                                 ", layerCount = %s)\n"
+                                 "dstSubresource (baseArrayLayer = %" PRIu32 ", layerCount = %s)\n",
                                  string_VkImageType(src_type), string_VkImageType(dst_type), src_subresource.baseArrayLayer,
-                                 src_subresource.layerCount, dst_subresource.baseArrayLayer, dst_subresource.layerCount);
+                                 string_LayerCount(src_image_state->create_info, src_subresource).c_str(),
+                                 dst_subresource.baseArrayLayer,
+                                 string_LayerCount(dst_image_state->create_info, dst_subresource).c_str());
             }
         } else if (enabled_features.maintenance8) {
-            const auto src_layer_count = (src_subresource.layerCount == VK_REMAINING_ARRAY_LAYERS)
-                                             ? (src_image_state->create_info.arrayLayers - src_subresource.baseArrayLayer)
-                                             : src_subresource.layerCount;
-            const auto dst_layer_count = (dst_subresource.layerCount == VK_REMAINING_ARRAY_LAYERS)
-                                             ? (dst_image_state->create_info.arrayLayers - dst_subresource.baseArrayLayer)
-                                             : dst_subresource.layerCount;
             if (src_type == VK_IMAGE_TYPE_3D) {
-                if (src_subresource.baseArrayLayer != 0 || src_layer_count != 1 || dst_layer_count != 1) {
+                if (src_subresource.baseArrayLayer != 0 || normalized_src_layer_count != 1 || normalized_dst_layer_count != 1) {
                     vuid = is_2 ? "VUID-VkBlitImageInfo2-maintenance8-10207" : "VUID-vkCmdBlitImage-maintenance8-10207";
-                    skip |= LogError(vuid, all_objlist, src_subresource_loc,
-                                     "(src baseArrayLayer = %" PRIu32 ",  src layerCount = %" PRIu32 ", dst layerCount = %" PRIu32
-                                     ") but srcImage is VK_IMAGE_TYPE_3D",
-                                     src_subresource.baseArrayLayer, src_layer_count, dst_layer_count);
+                    skip |= LogError(vuid, all_objlist, region_loc,
+                                     "the srcImage is VK_IMAGE_TYPE_3D so only can use its first layer\n"
+                                     "srcSubresource (baseArrayLayer = %" PRIu32
+                                     ", layerCount = %s)\n"
+                                     "dstSubresource.layerCount = %s\n",
+                                     src_subresource.baseArrayLayer,
+                                     string_LayerCount(src_image_state->create_info, src_subresource).c_str(),
+                                     string_LayerCount(dst_image_state->create_info, dst_subresource).c_str());
                 }
             } else if (uint32_t diff = static_cast<uint32_t>(abs(region.dstOffsets[0].z - region.dstOffsets[1].z));
-                       diff != src_layer_count) {
+                       diff != normalized_src_layer_count) {
                 vuid = is_2 ? "VUID-VkBlitImageInfo2-maintenance8-10579" : "VUID-vkCmdBlitImage-maintenance8-10579";
                 skip |= LogError(vuid, all_objlist, region_loc,
-                                 "has the absolute difference of dstOffsets[0].z (%" PRIu32 ") and dstOffsets[1].z (%" PRIu32
-                                 ") = %" PRIu32 ", which is not equal to srcSubresource.layerCount (%" PRIu32 ")",
-                                 region.dstOffsets[0].z, region.dstOffsets[1].z, diff, src_layer_count);
+                                 "has the absolute difference of %" PRIu32 " between dstOffsets[0].z (%" PRIu32
+                                 ") and dstOffsets[1].z (%" PRIu32 "), which is not equal to srcSubresource.layerCount (%s)",
+                                 diff, region.dstOffsets[0].z, region.dstOffsets[1].z,
+                                 string_LayerCount(src_image_state->create_info, src_subresource).c_str());
             }
             if (dst_type == VK_IMAGE_TYPE_3D) {
-                if (dst_subresource.baseArrayLayer != 0 || dst_layer_count != 1 || src_layer_count != 1) {
+                if (dst_subresource.baseArrayLayer != 0 || normalized_dst_layer_count != 1 || normalized_src_layer_count != 1) {
                     vuid = is_2 ? "VUID-VkBlitImageInfo2-maintenance8-10208" : "VUID-vkCmdBlitImage-maintenance8-10208";
-                    skip |= LogError(vuid, all_objlist, dst_subresource_loc,
-                                     "(dst baseArrayLayer = %" PRIu32 ", dst layerCount = %" PRIu32 ", src layerCount = %" PRIu32
-                                     ") but dstImage is VK_IMAGE_TYPE_3D",
-                                     dst_subresource.baseArrayLayer, dst_layer_count, src_layer_count);
+                    skip |= LogError(vuid, all_objlist, region_loc,
+                                     "the dstImage is VK_IMAGE_TYPE_3D so only can use its first layer\n"
+                                     "srcSubresource.layerCount = %s\n"
+                                     "dstSubresource (baseArrayLayer = %" PRIu32 ", layerCount = %s)\n",
+                                     string_LayerCount(src_image_state->create_info, src_subresource).c_str(),
+                                     dst_subresource.baseArrayLayer,
+                                     string_LayerCount(dst_image_state->create_info, dst_subresource).c_str());
                 }
             } else if (uint32_t diff = static_cast<uint32_t>(abs(region.srcOffsets[0].z - region.srcOffsets[1].z));
-                       diff != dst_layer_count) {
+                       diff != normalized_dst_layer_count) {
                 vuid = is_2 ? "VUID-VkBlitImageInfo2-maintenance8-10580" : "VUID-vkCmdBlitImage-maintenance8-10580";
                 skip |= LogError(vuid, all_objlist, region_loc,
-                                 "has the absolute difference of srcOffsets[0].z (%" PRIu32 ") and srcOffsets[1].z (%" PRIu32
-                                 ") = %" PRIu32 ", which is not equal to dstSubresource.layerCount (%" PRIu32 ")",
-                                 region.srcOffsets[0].z, region.srcOffsets[1].z, diff, dst_layer_count);
+                                 "has the absolute difference of %" PRIu32 " between srcOffsets[0].z (%" PRIu32
+                                 ") and srcOffsets[1].z (%" PRIu32 "), which is not equal to dstSubresource.layerCount (%s)",
+                                 diff, region.srcOffsets[0].z, region.srcOffsets[1].z,
+                                 string_LayerCount(dst_image_state->create_info, dst_subresource).c_str());
             }
         }
 
@@ -3645,16 +3655,22 @@ bool CoreChecks::ValidateCmdResolveImage(VkCommandBuffer commandBuffer, VkImage
         const VkImageType dst_image_type = dst_image_state->create_info.imageType;
 
         if (VK_IMAGE_TYPE_3D == dst_image_type) {
-            if (src_subresource.layerCount != 1) {
+            // Will resolve VK_REMAINING_ARRAY_LAYERS to actual value (some VUs just want the value)
+            const uint32_t normalized_src_layer_count = src_image_state->NormalizeLayerCount(src_subresource);
+            const uint32_t normalized_dst_layer_count = dst_image_state->NormalizeLayerCount(dst_subresource);
+
+            if (normalized_src_layer_count != 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);
+                                 "is %s but dstImage is VK_IMAGE_TYPE_3D.",
+                                 string_LayerCount(src_image_state->create_info, src_subresource).c_str());
             }
-            if ((dst_subresource.baseArrayLayer != 0) || (dst_subresource.layerCount != 1)) {
+            if ((dst_subresource.baseArrayLayer != 0) || (normalized_dst_layer_count != 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);
+                                 "is %" PRIu32 " and layerCount is %s but dstImage is VK_IMAGE_TYPE_3D.",
+                                 dst_subresource.baseArrayLayer,
+                                 string_LayerCount(dst_image_state->create_info, dst_subresource).c_str());
             }
         }
 
diff --git a/layers/core_checks/cc_descriptor.cpp b/layers/core_checks/cc_descriptor.cpp
index b75499322d8..ae0fa5f28dd 100644
--- a/layers/core_checks/cc_descriptor.cpp
+++ b/layers/core_checks/cc_descriptor.cpp
@@ -1215,7 +1215,7 @@ bool CoreChecks::ValidateImageUpdate(const vvl::ImageView &view_state, VkImageLa
     const LogObjectList objlist(view_state.Handle(), image_node->Handle());
     // KHR_maintenance1 allows rendering into 2D or 2DArray views which slice a 3D image,
     // but not binding them to descriptor sets.
-    if (view_state.IsDepthSliced() && image_node->create_info.imageType == VK_IMAGE_TYPE_3D) {
+    if (view_state.is_depth_sliced && image_node->create_info.imageType == VK_IMAGE_TYPE_3D) {
         // VK_EXT_image_2d_view_of_3d allows use of VIEW_TYPE_2D in descriptor
         if (view_state.create_info.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY) {
             skip |= LogError("VUID-VkDescriptorImageInfo-imageView-06712", objlist, image_info_loc.dot(Field::imageView),
diff --git a/layers/core_checks/cc_synchronization.cpp b/layers/core_checks/cc_synchronization.cpp
index 2fc0d3a6337..c85cbe8bb34 100644
--- a/layers/core_checks/cc_synchronization.cpp
+++ b/layers/core_checks/cc_synchronization.cpp
@@ -2043,7 +2043,7 @@ void CoreChecks::RecordBarrierValidationInfo(const Location &barrier_loc, vvl::C
     if (IsOwnershipTransfer(image_barrier)) {
         if (auto image = Get<vvl::Image>(image_barrier.image)) {
             ImageBarrier barrier = image_barrier;
-            barrier.subresourceRange = NormalizeSubresourceRange(image->create_info, image_barrier.subresourceRange);
+            barrier.subresourceRange = image->NormalizeSubresourceRange(image_barrier.subresourceRange);
 
             if (cb_state.IsReleaseOp(barrier) && !IsQueueFamilyExternal(barrier.dstQueueFamilyIndex)) {
                 barrier_sets.release.emplace(barrier);
diff --git a/layers/error_message/error_strings.h b/layers/error_message/error_strings.h
index 019fc40b935..afa2b0eb252 100644
--- a/layers/error_message/error_strings.h
+++ b/layers/error_message/error_strings.h
@@ -83,6 +83,18 @@
     return ss.str();
 }
 
+[[maybe_unused]] static std::string string_LayerCount(const VkImageCreateInfo &ci, VkImageSubresourceLayers const &resource) {
+    std::stringstream ss;
+    if (resource.layerCount == VK_REMAINING_ARRAY_LAYERS) {
+        const uint32_t layer_count = ci.arrayLayers - resource.baseArrayLayer;
+        ss << "VK_REMAINING_ARRAY_LAYERS [arrayLayers (" << ci.arrayLayers << ") - baseArrayLayer (" << resource.baseArrayLayer
+           << ") = " << layer_count << "]";
+    } else {
+        ss << resource.layerCount;
+    }
+    return ss.str();
+}
+
 [[maybe_unused]] static std::string string_VkPushConstantRange(VkPushConstantRange range) {
     std::stringstream ss;
     ss << "range [" << range.offset << ", " << (range.offset + range.size) << ") for "
diff --git a/layers/state_tracker/image_state.cpp b/layers/state_tracker/image_state.cpp
index c3fa4590940..d095479e082 100644
--- a/layers/state_tracker/image_state.cpp
+++ b/layers/state_tracker/image_state.cpp
@@ -23,68 +23,6 @@
 #include "state_tracker/shader_module.h"
 #include "generated/dispatch_functions.h"
 
-static VkImageSubresourceRange MakeImageFullRange(const VkImageCreateInfo &create_info) {
-    const auto format = create_info.format;
-    VkImageSubresourceRange init_range{0, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
-
-    if (vkuFormatIsColor(format) || vkuFormatIsMultiplane(format) || GetExternalFormat(create_info.pNext) != 0) {
-        init_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;  // Normalization will expand this for multiplane
-    } else {
-        init_range.aspectMask = (vkuFormatHasDepth(format) ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) |
-                                (vkuFormatHasStencil(format) ? VK_IMAGE_ASPECT_STENCIL_BIT : 0);
-    }
-    return NormalizeSubresourceRange(create_info, init_range);
-}
-
-VkImageSubresourceRange NormalizeSubresourceRange(const VkImageCreateInfo &image_create_info,
-                                                  const VkImageSubresourceRange &range) {
-    VkImageSubresourceRange norm = range;
-    norm.levelCount =
-        (range.levelCount == VK_REMAINING_MIP_LEVELS) ? (image_create_info.mipLevels - range.baseMipLevel) : range.levelCount;
-    norm.layerCount =
-        (range.layerCount == VK_REMAINING_ARRAY_LAYERS) ? (image_create_info.arrayLayers - range.baseArrayLayer) : range.layerCount;
-
-    // For multiplanar formats, IMAGE_ASPECT_COLOR is equivalent to adding the aspect of the individual planes
-    if (vkuFormatIsMultiplane(image_create_info.format)) {
-        if (norm.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
-            norm.aspectMask &= ~VK_IMAGE_ASPECT_COLOR_BIT;
-            norm.aspectMask |= (VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT);
-            if (vkuFormatPlaneCount(image_create_info.format) > 2) {
-                norm.aspectMask |= VK_IMAGE_ASPECT_PLANE_2_BIT;
-            }
-        }
-    }
-    return norm;
-}
-
-static bool IsDepthSliced(const VkImageCreateInfo &image_create_info, const VkImageViewCreateInfo &create_info) {
-    auto depth_slice_flag = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT | VK_IMAGE_CREATE_2D_VIEW_COMPATIBLE_BIT_EXT;
-    return ((image_create_info.flags & depth_slice_flag) != 0) &&
-           (create_info.viewType == VK_IMAGE_VIEW_TYPE_2D || create_info.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY);
-}
-
-VkImageSubresourceRange NormalizeSubresourceRange(const VkImageCreateInfo &image_create_info,
-                                                  const VkImageViewCreateInfo &create_info) {
-    auto subres_range = create_info.subresourceRange;
-
-    // if we're mapping a 3D image to a 2d image view, convert the view's subresource range to be compatible with the
-    // image's understanding of the world. From the VkImageSubresourceRange section of the Vulkan spec:
-    //
-    //     When the VkImageSubresourceRange structure is used to select a subset of the slices of a 3D image’s mip level in
-    //     order to create a 2D or 2D array image view of a 3D image created with VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT,
-    //     baseArrayLayer and layerCount specify the first slice index and the number of slices to include in the created
-    //     image view. Such an image view can be used as a framebuffer attachment that refers only to the specified range
-    //     of slices of the selected mip level. However, any layout transitions performed on such an attachment view during
-    //     a render pass instance still apply to the entire subresource referenced which includes all the slices of the
-    //     selected mip level.
-    //
-    if (IsDepthSliced(image_create_info, create_info)) {
-        subres_range.baseArrayLayer = 0;
-        subres_range.layerCount = 1;
-    }
-    return NormalizeSubresourceRange(image_create_info, subres_range);
-}
-
 static VkExternalMemoryHandleTypeFlags GetExternalHandleTypes(const VkImageCreateInfo *pCreateInfo) {
     const auto *external_memory_info = vku::FindStructInPNextChain<VkExternalMemoryImageCreateInfo>(pCreateInfo->pNext);
     return external_memory_info ? external_memory_info->handleTypes : 0;
@@ -181,7 +119,7 @@ Image::Image(const vvl::Device &dev_data, VkImage img, const VkImageCreateInfo *
       shared_presentable(false),
       layout_locked(false),
       ahb_format(GetExternalFormat(pCreateInfo->pNext)),
-      full_range{MakeImageFullRange(*pCreateInfo)},
+      full_range{MakeImageFullRange()},
       create_from_swapchain(GetSwapchain(pCreateInfo)),
       owned_by_swapchain(false),
       swapchain_image_index(0),
@@ -223,7 +161,7 @@ Image::Image(const vvl::Device &dev_data, VkImage img, const VkImageCreateInfo *
       shared_presentable(false),
       layout_locked(false),
       ahb_format(GetExternalFormat(pCreateInfo->pNext)),
-      full_range{MakeImageFullRange(*pCreateInfo)},
+      full_range{MakeImageFullRange()},
       create_from_swapchain(swapchain),
       owned_by_swapchain(true),
       swapchain_image_index(swapchain_index),
@@ -316,6 +254,44 @@ bool Image::IsCompatibleAliasing(const Image *other_image_state) const {
     return false;
 }
 
+VkImageSubresourceRange Image::NormalizeSubresourceRange(const VkImageSubresourceRange &range) const {
+    VkImageSubresourceRange norm = range;
+    norm.levelCount =
+        (range.levelCount == VK_REMAINING_MIP_LEVELS) ? (create_info.mipLevels - range.baseMipLevel) : range.levelCount;
+    norm.layerCount =
+        (range.layerCount == VK_REMAINING_ARRAY_LAYERS) ? (create_info.arrayLayers - range.baseArrayLayer) : range.layerCount;
+
+    // For multiplanar formats, IMAGE_ASPECT_COLOR is equivalent to adding the aspect of the individual planes
+    if (vkuFormatIsMultiplane(create_info.format)) {
+        if (norm.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
+            norm.aspectMask &= ~VK_IMAGE_ASPECT_COLOR_BIT;
+            norm.aspectMask |= (VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT);
+            if (vkuFormatPlaneCount(create_info.format) > 2) {
+                norm.aspectMask |= VK_IMAGE_ASPECT_PLANE_2_BIT;
+            }
+        }
+    }
+    return norm;
+}
+
+uint32_t Image::NormalizeLayerCount(const VkImageSubresourceLayers &resource) const {
+    return (resource.layerCount == VK_REMAINING_ARRAY_LAYERS) ? (create_info.arrayLayers - resource.baseArrayLayer)
+                                                              : resource.layerCount;
+}
+
+VkImageSubresourceRange Image::MakeImageFullRange() {
+    const auto format = create_info.format;
+    VkImageSubresourceRange init_range{0, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
+
+    if (vkuFormatIsColor(format) || vkuFormatIsMultiplane(format) || GetExternalFormat(create_info.pNext) != 0) {
+        init_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;  // Normalization will expand this for multiplane
+    } else {
+        init_range.aspectMask = (vkuFormatHasDepth(format) ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) |
+                                (vkuFormatHasStencil(format) ? VK_IMAGE_ASPECT_STENCIL_BIT : 0);
+    }
+    return NormalizeSubresourceRange(init_range);
+}
+
 void Image::SetInitialLayoutMap() {
     if (layout_range_map) {
         return;
@@ -428,24 +404,24 @@ static bool GetMetalExport(const VkImageViewCreateInfo *info) {
 
 namespace vvl {
 
-ImageView::ImageView(const std::shared_ptr<vvl::Image> &im, VkImageView handle, const VkImageViewCreateInfo *ci,
+ImageView::ImageView(const std::shared_ptr<vvl::Image> &image_state, VkImageView handle, const VkImageViewCreateInfo *ci,
                      VkFormatFeatureFlags2KHR ff, const VkFilterCubicImageViewImageFormatPropertiesEXT &cubic_props)
     : StateObject(handle, kVulkanObjectTypeImageView),
       safe_create_info(ci),
       create_info(*safe_create_info.ptr()),
-      normalized_subresource_range(::NormalizeSubresourceRange(im->create_info, *ci)),
-      range_generator(im->subresource_encoder, normalized_subresource_range),
-      samples(im->create_info.samples),
+      image_state(image_state),
+#ifdef VK_USE_PLATFORM_METAL_EXT
+      metal_imageview_export(GetMetalExport(ci)),
+#endif
+      is_depth_sliced(IsDepthSliced()),
+      normalized_subresource_range(NormalizeSubresourceRange()),
+      range_generator(image_state->subresource_encoder, normalized_subresource_range),
+      samples(image_state->create_info.samples),
       samplerConversion(GetSamplerConversion(ci)),
       filter_cubic_props(cubic_props),
       min_lod(GetImageViewMinLod(ci)),
       format_features(ff),
-      inherited_usage(GetInheritedUsage(ci, *im)),
-#ifdef VK_USE_PLATFORM_METAL_EXT
-      metal_imageview_export(GetMetalExport(ci)),
-#endif
-      image_state(im),
-      is_depth_sliced(::IsDepthSliced(im->create_info, *ci)) {
+      inherited_usage(GetInheritedUsage(ci, *image_state)) {
 }
 
 void ImageView::Destroy() {
@@ -456,8 +432,35 @@ void ImageView::Destroy() {
     StateObject::Destroy();
 }
 
+bool ImageView::IsDepthSliced() {
+    auto depth_slice_flag = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT | VK_IMAGE_CREATE_2D_VIEW_COMPATIBLE_BIT_EXT;
+    return ((image_state->create_info.flags & depth_slice_flag) != 0) &&
+           (create_info.viewType == VK_IMAGE_VIEW_TYPE_2D || create_info.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY);
+}
+
+VkImageSubresourceRange ImageView::NormalizeSubresourceRange() const {
+    auto subres_range = create_info.subresourceRange;
+
+    // if we're mapping a 3D image to a 2d image view, convert the view's subresource range to be compatible with the
+    // image's understanding of the world. From the VkImageSubresourceRange section of the Vulkan spec:
+    //
+    //     When the VkImageSubresourceRange structure is used to select a subset of the slices of a 3D image’s mip level in
+    //     order to create a 2D or 2D array image view of a 3D image created with VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT,
+    //     baseArrayLayer and layerCount specify the first slice index and the number of slices to include in the created
+    //     image view. Such an image view can be used as a framebuffer attachment that refers only to the specified range
+    //     of slices of the selected mip level. However, any layout transitions performed on such an attachment view during
+    //     a render pass instance still apply to the entire subresource referenced which includes all the slices of the
+    //     selected mip level.
+    //
+    if (is_depth_sliced) {
+        subres_range.baseArrayLayer = 0;
+        subres_range.layerCount = 1;
+    }
+    return image_state->NormalizeSubresourceRange(subres_range);
+}
+
 uint32_t ImageView::GetAttachmentLayerCount() const {
-    if (create_info.subresourceRange.layerCount == VK_REMAINING_ARRAY_LAYERS && !IsDepthSliced()) {
+    if (create_info.subresourceRange.layerCount == VK_REMAINING_ARRAY_LAYERS && !is_depth_sliced) {
         return image_state->create_info.arrayLayers;
     }
     return create_info.subresourceRange.layerCount;
diff --git a/layers/state_tracker/image_state.h b/layers/state_tracker/image_state.h
index 3b3afd87dfc..112484d57a9 100644
--- a/layers/state_tracker/image_state.h
+++ b/layers/state_tracker/image_state.h
@@ -42,10 +42,6 @@ static inline bool operator==(const VkImageSubresource &lhs, const VkImageSubres
     return (lhs.aspectMask == rhs.aspectMask) && (lhs.mipLevel == rhs.mipLevel) && (lhs.arrayLayer == rhs.arrayLayer);
 }
 
-VkImageSubresourceRange NormalizeSubresourceRange(const VkImageCreateInfo &image_create_info, const VkImageSubresourceRange &range);
-VkImageSubresourceRange NormalizeSubresourceRange(const VkImageCreateInfo &image_create_info,
-                                                  const VkImageViewCreateInfo &view_create_info);
-
 // Transfer VkImageSubresourceRange into VkImageSubresourceLayers struct
 static inline VkImageSubresourceLayers LayersFromRange(const VkImageSubresourceRange &subresource_range) {
     VkImageSubresourceLayers subresource_layers;
@@ -215,9 +211,8 @@ class Image : public Bindable, public SubStateManager<ImageSubState> {
         return GetEffectiveExtent(create_info, range.aspectMask, range.baseMipLevel);
     }
 
-    VkImageSubresourceRange NormalizeSubresourceRange(const VkImageSubresourceRange &range) const {
-        return ::NormalizeSubresourceRange(create_info, range);
-    }
+    VkImageSubresourceRange NormalizeSubresourceRange(const VkImageSubresourceRange &range) const;
+    uint32_t NormalizeLayerCount(const VkImageSubresourceLayers &resource) const;
 
     void SetInitialLayoutMap();
     void SetImageLayout(const VkImageSubresourceRange &range, VkImageLayout layout);
@@ -257,6 +252,8 @@ class Image : public Bindable, public SubStateManager<ImageSubState> {
     void NotifyInvalidate(const StateObject::NodeList &invalid_nodes, bool unlink) override;
 
   private:
+    VkImageSubresourceRange MakeImageFullRange();
+
     std::variant<std::monostate, BindableNoMemoryTracker, BindableLinearMemoryTracker, BindableSparseMemoryTracker,
                  BindableMultiplanarMemoryTracker>
         tracker_;
@@ -281,6 +278,13 @@ class ImageView : public StateObject {
     const vku::safe_VkImageViewCreateInfo safe_create_info;
     const VkImageViewCreateInfo &create_info;
 
+    std::shared_ptr<vvl::Image> image_state;
+
+#ifdef VK_USE_PLATFORM_METAL_EXT
+    const bool metal_imageview_export;
+#endif  // VK_USE_PLATFORM_METAL_EXT
+
+    const bool is_depth_sliced;
     const VkImageSubresourceRange normalized_subresource_range;
     const image_layout_map::RangeGenerator range_generator;
     const VkSampleCountFlagBits samples;
@@ -289,11 +293,6 @@ class ImageView : public StateObject {
     const float min_lod;
     const VkFormatFeatureFlags2KHR format_features;
     const VkImageUsageFlags inherited_usage;  // from spec #resources-image-inherited-usage
-#ifdef VK_USE_PLATFORM_METAL_EXT
-    const bool metal_imageview_export;
-#endif  // VK_USE_PLATFORM_METAL_EXT
-    std::shared_ptr<vvl::Image> image_state;
-    const bool is_depth_sliced;
 
     ImageView(const std::shared_ptr<vvl::Image> &image_state, VkImageView handle, const VkImageViewCreateInfo *ci,
               VkFormatFeatureFlags2KHR ff, const VkFilterCubicImageViewImageFormatPropertiesEXT &cubic_props);
@@ -315,11 +314,13 @@ class ImageView : public StateObject {
 
     void Destroy() override;
 
-    bool IsDepthSliced() const { return is_depth_sliced; }
-
     uint32_t GetAttachmentLayerCount() const;
 
     bool Invalid() const override { return Destroyed() || !image_state || image_state->Invalid(); }
+
+  private:
+    VkImageSubresourceRange NormalizeSubresourceRange() const;
+    bool IsDepthSliced();
 };
 
 struct SwapchainImage {
diff --git a/layers/sync/sync_commandbuffer.cpp b/layers/sync/sync_commandbuffer.cpp
index 2e81ed52948..a253db9f7c0 100644
--- a/layers/sync/sync_commandbuffer.cpp
+++ b/layers/sync/sync_commandbuffer.cpp
@@ -377,7 +377,7 @@ bool CommandBufferAccessContext::ValidateDispatchDrawDescriptorSet(VkPipelineBin
                         const auto *img_view_state = image_descriptor->GetImageViewState();
                         VkImageLayout image_layout = image_descriptor->GetImageLayout();
 
-                        if (img_view_state->IsDepthSliced()) {
+                        if (img_view_state->is_depth_sliced) {
                             // NOTE: 2D ImageViews of VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT Images are not allowed in
                             // Descriptors, unless VK_EXT_image_2d_view_of_3d is supported, which it isn't at the moment.
                             // See: VUID 00343
@@ -536,7 +536,7 @@ void CommandBufferAccessContext::RecordDispatchDrawDescriptorSet(VkPipelineBindP
                             continue;
                         }
                         const auto *img_view_state = image_descriptor->GetImageViewState();
-                        if (img_view_state->IsDepthSliced()) {
+                        if (img_view_state->is_depth_sliced) {
                             // NOTE: 2D ImageViews of VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT Images are not allowed in
                             // Descriptors, unless VK_EXT_image_2d_view_of_3d is supported, which it isn't at the moment.
                             // See: VUID 00343
@@ -937,7 +937,7 @@ bool CommandBufferAccessContext::ValidateClearAttachment(const Location &loc, co
         subresource_range.aspectMask = aspect;
 
         HazardResult hazard = current_context_->DetectHazard(
-            *info.attachment_view.image_state, subresource_range, offset, extent, info.attachment_view.IsDepthSliced(),
+            *info.attachment_view.image_state, subresource_range, offset, extent, info.attachment_view.is_depth_sliced,
             SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, SyncOrdering::kColorAttachment);
         if (hazard.IsHazard()) {
             std::stringstream ss;
@@ -964,7 +964,7 @@ bool CommandBufferAccessContext::ValidateClearAttachment(const Location &loc, co
             // vkCmdClearAttachments depth/stencil writes are executed by the EARLY_FRAGMENT_TESTS_BIT and LATE_FRAGMENT_TESTS_BIT
             // stages. The implementation tracks the most recent access, which happens in the LATE_FRAGMENT_TESTS_BIT stage.
             HazardResult hazard = current_context_->DetectHazard(
-                *info.attachment_view.image_state, info.subresource_range, offset, extent, info.attachment_view.IsDepthSliced(),
+                *info.attachment_view.image_state, info.subresource_range, offset, extent, info.attachment_view.is_depth_sliced,
                 SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, SyncOrdering::kDepthStencilAttachment);
 
             if (hazard.IsHazard()) {
diff --git a/layers/sync/sync_image.cpp b/layers/sync/sync_image.cpp
index e6ef1945e69..7771977c06b 100644
--- a/layers/sync/sync_image.cpp
+++ b/layers/sync/sync_image.cpp
@@ -81,7 +81,7 @@ ImageRangeGen syncval_state::ImageSubState::MakeImageRangeGen(const VkImageSubre
 
 ImageRangeGen syncval_state::MakeImageRangeGen(const vvl::ImageView &view) {
     const auto &sub_state = SubState(*view.image_state);
-    return sub_state.MakeImageRangeGen(view.normalized_subresource_range, view.IsDepthSliced());
+    return sub_state.MakeImageRangeGen(view.normalized_subresource_range, view.is_depth_sliced);
 }
 
 ImageRangeGen syncval_state::MakeImageRangeGen(const vvl::ImageView &view, const VkOffset3D &offset, const VkExtent3D &extent,
@@ -95,5 +95,5 @@ ImageRangeGen syncval_state::MakeImageRangeGen(const vvl::ImageView &view, const
         subresource_range.aspectMask = override_depth_stencil_aspect_mask;
     }
     const auto &sub_state = SubState(*view.image_state);
-    return sub_state.MakeImageRangeGen(subresource_range, offset, extent, view.IsDepthSliced());
+    return sub_state.MakeImageRangeGen(subresource_range, offset, extent, view.is_depth_sliced);
 }
diff --git a/layers/sync/sync_op.cpp b/layers/sync/sync_op.cpp
index 929488f13ff..8701a5d6d6d 100644
--- a/layers/sync/sync_op.cpp
+++ b/layers/sync/sync_op.cpp
@@ -337,7 +337,7 @@ void BarrierSet::MakeImageMemoryBarriers(const SyncValidator &sync_state, const
     image_memory_barriers.reserve(barrier_count);
     for (const auto [index, barrier] : vvl::enumerate(barriers, barrier_count)) {
         if (auto image = sync_state.Get<vvl::Image>(barrier.image)) {
-            auto subresource_range = NormalizeSubresourceRange(image->create_info, barrier.subresourceRange);
+            auto subresource_range = image->NormalizeSubresourceRange(barrier.subresourceRange);
             const SyncBarrier sync_barrier(barrier, src, dst);
             const bool layout_transition = barrier.oldLayout != barrier.newLayout;
             image_memory_barriers.emplace_back(image, sync_barrier, subresource_range, layout_transition, index);
@@ -353,7 +353,7 @@ void BarrierSet::MakeImageMemoryBarriers(const SyncValidator &sync_state, VkQueu
         auto dst = SyncExecScope::MakeDst(queue_flags, barrier.dstStageMask);
         auto image = sync_state.Get<vvl::Image>(barrier.image);
         if (image) {
-            auto subresource_range = NormalizeSubresourceRange(image->create_info, barrier.subresourceRange);
+            auto subresource_range = image->NormalizeSubresourceRange(barrier.subresourceRange);
             const SyncBarrier sync_barrier(barrier, src, dst);
             const bool layout_transition = barrier.oldLayout != barrier.newLayout;
             image_memory_barriers.emplace_back(image, sync_barrier, subresource_range, layout_transition, index);
diff --git a/tests/unit/command_positive.cpp b/tests/unit/command_positive.cpp
index fd599b0482c..b82cd107372 100644
--- a/tests/unit/command_positive.cpp
+++ b/tests/unit/command_positive.cpp
@@ -710,3 +710,47 @@ TEST_F(PositiveCommand, CommandBufferInheritanceInfoIgnoredPointer) {
     m_command_buffer.Begin(&begin_info);
     m_command_buffer.End();
 }
+
+TEST_F(PositiveCommand, ResolveImageImageTypeRemainingLayerCount) {
+    SetTargetApiVersion(VK_API_VERSION_1_1);
+    AddRequiredExtensions(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
+    AddRequiredFeature(vkt::Feature::maintenance5);
+    RETURN_IF_SKIP(Init());
+
+    if (!FormatFeaturesAreSupported(Gpu(), VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
+                                    VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) {
+        GTEST_SKIP() << "Required formats/features not supported";
+    }
+
+    VkImageCreateInfo image_create_info = vku::InitStructHelper();
+    image_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
+    image_create_info.extent = {32, 1, 1};
+    image_create_info.mipLevels = 1;
+    image_create_info.arrayLayers = 1;
+    image_create_info.samples = VK_SAMPLE_COUNT_4_BIT;  // guarantee support from sampledImageColorSampleCounts
+    image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
+    image_create_info.usage =
+        VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+    image_create_info.flags = 0;
+
+    image_create_info.imageType = VK_IMAGE_TYPE_2D;
+    vkt::Image src_image_2D(*m_device, image_create_info);
+
+    image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
+    image_create_info.imageType = VK_IMAGE_TYPE_3D;
+    image_create_info.extent = {32, 16, 16};
+    vkt::Image dst_image_3D(*m_device, image_create_info);
+
+    m_command_buffer.Begin();
+
+    VkImageResolve region;
+    region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, VK_REMAINING_ARRAY_LAYERS};
+    region.srcOffset = {0, 0, 0};
+    region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
+    region.dstOffset = {0, 0, 0};
+    region.extent = {1, 1, 1};
+
+    vk::CmdResolveImage(m_command_buffer.handle(), src_image_2D, VK_IMAGE_LAYOUT_GENERAL, dst_image_3D, VK_IMAGE_LAYOUT_GENERAL, 1,
+                        &region);
+    m_command_buffer.End();
+}
\ No newline at end of file
diff --git a/tests/unit/copy_buffer_image.cpp b/tests/unit/copy_buffer_image.cpp
index dc27673e3ad..f0a092a9015 100644
--- a/tests/unit/copy_buffer_image.cpp
+++ b/tests/unit/copy_buffer_image.cpp
@@ -3499,6 +3499,42 @@ TEST_F(NegativeCopyBufferImage, BlitInvalidDepth) {
     m_command_buffer.End();
 }
 
+TEST_F(NegativeCopyBufferImage, BlitDepthRemainingArrayLayers) {
+    SetTargetApiVersion(VK_API_VERSION_1_1);
+    AddRequiredExtensions(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
+    AddRequiredExtensions(VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
+    AddRequiredFeature(vkt::Feature::maintenance5);
+    AddRequiredFeature(vkt::Feature::maintenance8);
+    RETURN_IF_SKIP(Init());
+
+    auto image_ci = vkt::Image::ImageCreateInfo2D(32u, 32u, 1u, 2u, VK_FORMAT_R8G8B8A8_UNORM,
+                                                  VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
+    vkt::Image src_image(*m_device, image_ci);
+    vkt::Image dst_image(*m_device, image_ci);
+
+    image_ci.imageType = VK_IMAGE_TYPE_3D;
+    image_ci.arrayLayers = 1u;
+    image_ci.extent.depth = 2u;
+    vkt::Image src_image_3d(*m_device, image_ci);
+    vkt::Image dst_image_3d(*m_device, image_ci);
+
+    m_command_buffer.Begin();
+
+    VkImageBlit region;
+    region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, VK_REMAINING_ARRAY_LAYERS};
+    region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, VK_REMAINING_ARRAY_LAYERS};
+    region.srcOffsets[0] = {0, 0, 0};
+    region.srcOffsets[1] = {32, 32, 1};
+    region.dstOffsets[0] = {0, 0, 0};
+    region.dstOffsets[1] = {32, 32, 2};
+
+    m_errorMonitor->SetDesiredError("VUID-vkCmdBlitImage-maintenance8-10208");
+    vk::CmdBlitImage(m_command_buffer.handle(), src_image.handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image_3d,
+                     VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1u, &region, VK_FILTER_NEAREST);
+    m_errorMonitor->VerifyFound();
+    m_command_buffer.End();
+}
+
 TEST_F(NegativeCopyBufferImage, ImageCopyMissingSrcFormatFeature) {
     AddRequiredExtensions(VK_KHR_MAINTENANCE1_EXTENSION_NAME);
     RETURN_IF_SKIP(Init());
diff --git a/tests/unit/image.cpp b/tests/unit/image.cpp
index ffaef95a297..4506369a102 100644
--- a/tests/unit/image.cpp
+++ b/tests/unit/image.cpp
@@ -835,6 +835,9 @@ TEST_F(NegativeImage, BlitToDepth) {
 
 TEST_F(NegativeImage, BlitWithoutMaintenance8) {
     TEST_DESCRIPTION("Would be valid if Maintenance8 was added.");
+    SetTargetApiVersion(VK_API_VERSION_1_1);
+    AddRequiredFeature(vkt::Feature::maintenance5);
+    AddRequiredExtensions(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
     RETURN_IF_SKIP(Init());
 
     VkFormat format = VK_FORMAT_R32_SFLOAT;  // Need features ..BLIT_SRC_BIT & ..BLIT_DST_BIT
@@ -874,6 +877,13 @@ TEST_F(NegativeImage, BlitWithoutMaintenance8) {
     vk::CmdBlitImage(m_command_buffer.handle(), image_2d, VK_IMAGE_LAYOUT_GENERAL, image_3d, VK_IMAGE_LAYOUT_GENERAL, 1,
                      &blit_region, VK_FILTER_NEAREST);
     m_errorMonitor->VerifyFound();
+
+    blit_region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, VK_REMAINING_ARRAY_LAYERS};
+    m_errorMonitor->SetDesiredError("VUID-VkImageBlit-layerCount-08801");
+    m_errorMonitor->SetDesiredError("VUID-vkCmdBlitImage-srcImage-00240");
+    vk::CmdBlitImage(m_command_buffer.handle(), image_2d, VK_IMAGE_LAYOUT_GENERAL, image_3d, VK_IMAGE_LAYOUT_GENERAL, 1,
+                     &blit_region, VK_FILTER_NEAREST);
+    m_errorMonitor->VerifyFound();
     m_command_buffer.End();
 }
 


Reviews:
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:47:44Z
user: login: artem-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T19:02:11Z
user: login: artem-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T19:12:45Z
user: login: artem-lunarg
body: Nice improvement. One naming suggestion.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T19:15:03Z
user: login: spencer-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T19:19:10Z
user: login: artem-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T20:48:13Z

Review comments:
diff_hunk: @@ -3359,56 +3363,62 @@ bool CoreChecks::ValidateCmdBlitImage(VkCommandBuffer commandBuffer, VkImage src
 
         // pre-maintenance8 both src/dst had to both match, after we only validate them independently
         if (!enabled_features.maintenance8 && (src_type == VK_IMAGE_TYPE_3D || dst_type == VK_IMAGE_TYPE_3D)) {
-            if ((src_subresource.baseArrayLayer != 0) || (src_subresource.layerCount != 1) ||
-                (dst_subresource.baseArrayLayer != 0) || (dst_subresource.layerCount != 1)) {
+            if ((src_subresource.baseArrayLayer != 0) || (normalized_src_layer_count != 1) ||
+                (dst_subresource.baseArrayLayer != 0) || (normalized_dst_layer_count != 1)) {
                 vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00240" : "VUID-vkCmdBlitImage-srcImage-00240";
path: layers/core_checks/cc_copy_blit_resolve.cpp
user: login: spencer-lunarg
body: example, before and after

```
srcSubresource (baseArrayLayer = 1, layerCount = 3)
dstSubresource (baseArrayLayer = 0, layerCount = 1)
```

and now

```
srcSubresource (baseArrayLayer = 1, layerCount = VK_REMAINING_ARRAY_LAYERS [arrayLayers (4) - baseArrayLayer (1) = 3])
dstSubresource (baseArrayLayer = 0, layerCount = 1)
```
created_at: 2025-03-21T18:47:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3359,56 +3363,62 @@ bool CoreChecks::ValidateCmdBlitImage(VkCommandBuffer commandBuffer, VkImage src
 
         // pre-maintenance8 both src/dst had to both match, after we only validate them independently
         if (!enabled_features.maintenance8 && (src_type == VK_IMAGE_TYPE_3D || dst_type == VK_IMAGE_TYPE_3D)) {
-            if ((src_subresource.baseArrayLayer != 0) || (src_subresource.layerCount != 1) ||
-                (dst_subresource.baseArrayLayer != 0) || (dst_subresource.layerCount != 1)) {
+            if ((src_subresource.baseArrayLayer != 0) || (normalized_src_layer_count != 1) ||
+                (dst_subresource.baseArrayLayer != 0) || (normalized_dst_layer_count != 1)) {
                 vuid = is_2 ? "VUID-VkBlitImageInfo2-srcImage-00240" : "VUID-vkCmdBlitImage-srcImage-00240";
path: layers/core_checks/cc_copy_blit_resolve.cpp
user: login: artem-lunarg
body: that's really nice, these special constants are often a time bombs
created_at: 2025-03-21T19:02:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -215,9 +211,8 @@ class Image : public Bindable, public SubStateManager<ImageSubState> {
         return GetEffectiveExtent(create_info, range.aspectMask, range.baseMipLevel);
     }
 
-    VkImageSubresourceRange NormalizeSubresourceRange(const VkImageSubresourceRange &range) const {
-        return ::NormalizeSubresourceRange(create_info, range);
-    }
+    VkImageSubresourceRange NormalizeSubresourceRange(const VkImageSubresourceRange &range) const;
path: layers/state_tracker/image_state.h
user: login: artem-lunarg
body: Arno asked me to be an English substitute teacher for this evening. When `Normalize` is used on a member-function, it might suggest that some state is modified (yes, it's const but name suggests otherwise), so you can think the object stores Range and after calling this function the range will be updated. 

I have Vector3 class that has `void Vector3::Normalize()` that normalizes vector and returns nothing, and also `Vector3 Vector3::Normalized() const` that returns normalized copy. The suggestion to the above naming is to use Normalized too! Okay, I guess Arno is back.

p.s. seriously, if you think this naming suggestion is weird then it probably is.
created_at: 2025-03-21T19:12:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -215,9 +211,8 @@ class Image : public Bindable, public SubStateManager<ImageSubState> {
         return GetEffectiveExtent(create_info, range.aspectMask, range.baseMipLevel);
     }
 
-    VkImageSubresourceRange NormalizeSubresourceRange(const VkImageSubresourceRange &range) const {
-        return ::NormalizeSubresourceRange(create_info, range);
-    }
+    VkImageSubresourceRange NormalizeSubresourceRange(const VkImageSubresourceRange &range) const;
path: layers/state_tracker/image_state.h
user: login: spencer-lunarg
body: I only used Normalized because this was here, I would argue (partially because it is Friday, mostly because I just wanted to do a drive by fix and get to GPU-AV) that changing name is out side scope of PR and happy for a followup PR for a name that makes sense to everyone
created_at: 2025-03-21T19:19:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -215,9 +211,8 @@ class Image : public Bindable, public SubStateManager<ImageSubState> {
         return GetEffectiveExtent(create_info, range.aspectMask, range.baseMipLevel);
     }
 
-    VkImageSubresourceRange NormalizeSubresourceRange(const VkImageSubresourceRange &range) const {
-        return ::NormalizeSubresourceRange(create_info, range);
-    }
+    VkImageSubresourceRange NormalizeSubresourceRange(const VkImageSubresourceRange &range) const;
path: layers/state_tracker/image_state.h
user: login: artem-lunarg
body: Makes sense, I see this PR mostly moved the code.
created_at: 2025-03-21T20:48:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: ci-tester-lunarg
created_at: 2025-03-21T18:46:56Z
body: CI Vulkan-ValidationLayers build queued with queue ID 401448.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-03-21T18:50:01Z
body: CI Vulkan-ValidationLayers build # 19507 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-03-21T19:17:43Z
body: CI Vulkan-ValidationLayers build # 19507 passed.
author_association: COLLABORATOR