layers: add multiplane image formats and checks

Add support for new formats added by sampler_YCbCr and mainlined in
Vulkan 1.1. Adds single- and multi-plane formats to lookup utility
fxns. Adds checks for CmdCopyImage VUIDS that were added or modified.

Change-Id: I70279bd8d667bf50d53fce752b53bb7e2e0a2d67
diff --git a/layers/buffer_validation.cpp b/layers/buffer_validation.cpp
index 9abc47b..1dfa221 100644
--- a/layers/buffer_validation.cpp
+++ b/layers/buffer_validation.cpp
@@ -1213,27 +1213,29 @@
     return result;
 }
 
-// Returns true if two VkImageCopy structures overlap
-static bool RegionIntersects(const VkImageCopy *src, const VkImageCopy *dst, VkImageType type, bool is_multiplane) {
+// Returns true if source area of first copy region intersects dest area of second region
+// It is assumed that these are copy regions within a single image (otherwise no possibility of collision)
+static bool RegionIntersects(const VkImageCopy *rgn0, const VkImageCopy *rgn1, VkImageType type, bool is_multiplane) {
     bool result = false;
 
-    if (is_multiplane && (src->srcSubresource.aspectMask != dst->dstSubresource.aspectMask)) {
+    // Separate planes within a multiplane image cannot intersect
+    if (is_multiplane && (rgn0->srcSubresource.aspectMask != rgn1->dstSubresource.aspectMask)) {
         return result;
     }
 
-    if ((src->srcSubresource.mipLevel == dst->dstSubresource.mipLevel) &&
-        (RangesIntersect(src->srcSubresource.baseArrayLayer, src->srcSubresource.layerCount, dst->dstSubresource.baseArrayLayer,
-                         dst->dstSubresource.layerCount))) {
+    if ((rgn0->srcSubresource.mipLevel == rgn1->dstSubresource.mipLevel) &&
+        (RangesIntersect(rgn0->srcSubresource.baseArrayLayer, rgn0->srcSubresource.layerCount, rgn1->dstSubresource.baseArrayLayer,
+                         rgn1->dstSubresource.layerCount))) {
         result = true;
         switch (type) {
             case VK_IMAGE_TYPE_3D:
-                result &= RangesIntersect(src->srcOffset.z, src->extent.depth, dst->dstOffset.z, dst->extent.depth);
+                result &= RangesIntersect(rgn0->srcOffset.z, rgn0->extent.depth, rgn1->dstOffset.z, rgn1->extent.depth);
             // Intentionally fall through to 2D case
             case VK_IMAGE_TYPE_2D:
-                result &= RangesIntersect(src->srcOffset.y, src->extent.height, dst->dstOffset.y, dst->extent.height);
+                result &= RangesIntersect(rgn0->srcOffset.y, rgn0->extent.height, rgn1->dstOffset.y, rgn1->extent.height);
             // Intentionally fall through to 1D case
             case VK_IMAGE_TYPE_1D:
-                result &= RangesIntersect(src->srcOffset.x, src->extent.width, dst->dstOffset.x, dst->extent.width);
+                result &= RangesIntersect(rgn0->srcOffset.x, rgn0->extent.width, rgn1->dstOffset.x, rgn1->extent.width);
                 break;
             default:
                 // Unrecognized or new IMAGE_TYPE enums will be caught in parameter_validation
@@ -1577,51 +1579,56 @@
             }
         }
 
-        // Checks that apply only to compressed images
-        if (FormatIsCompressed(src_state->createInfo.format)) {
+        // Source checks that apply only to compressed images (or to _422 images if ycbcr enabled)
+        bool ext_ycbcr = GetDeviceExtensions(device_data)->vk_khr_sampler_ycbcr_conversion;
+        if (FormatIsCompressed(src_state->createInfo.format) ||
+            (ext_ycbcr && FormatIsSinglePlane_422(src_state->createInfo.format))) {
             const VkExtent3D block_size = FormatCompressedTexelBlockExtent(src_state->createInfo.format);
-
             //  image offsets must be multiples of block dimensions
             if ((SafeModulo(region.srcOffset.x, block_size.width) != 0) ||
                 (SafeModulo(region.srcOffset.y, block_size.height) != 0) ||
                 (SafeModulo(region.srcOffset.z, block_size.depth) != 0)) {
+                UNIQUE_VALIDATION_ERROR_CODE vuid = ext_ycbcr ? VALIDATION_ERROR_09c00d7e : VALIDATION_ERROR_09c0013a;
                 skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT,
-                                HandleToUint64(src_state->image), VALIDATION_ERROR_09c0013a,
+                                HandleToUint64(src_state->image), vuid,
                                 "vkCmdCopyImage(): pRegion[%d] srcOffset (%d, %d) must be multiples of the compressed image's "
-                                "texel width & height (%d, %d)..",
+                                "texel width & height (%d, %d).",
                                 i, region.srcOffset.x, region.srcOffset.y, block_size.width, block_size.height);
             }
 
             const VkExtent3D mip_extent = GetImageSubresourceExtent(src_state, &(region.srcSubresource));
             if ((SafeModulo(src_copy_extent.width, block_size.width) != 0) &&
                 (src_copy_extent.width + region.srcOffset.x != mip_extent.width)) {
+                UNIQUE_VALIDATION_ERROR_CODE vuid = ext_ycbcr ? VALIDATION_ERROR_09c00d80 : VALIDATION_ERROR_09c0013c;
                 skip |=
                     log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT,
-                            HandleToUint64(src_state->image), VALIDATION_ERROR_09c0013c,
+                            HandleToUint64(src_state->image), vuid,
                             "vkCmdCopyImage(): pRegion[%d] extent width (%d) must be a multiple of the compressed texture block "
-                            "width (%d), or when added to srcOffset.x (%d) must equal the image subresource width (%d)..",
+                            "width (%d), or when added to srcOffset.x (%d) must equal the image subresource width (%d).",
                             i, 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)) {
+                UNIQUE_VALIDATION_ERROR_CODE vuid = ext_ycbcr ? VALIDATION_ERROR_09c00d82 : VALIDATION_ERROR_09c0013e;
                 skip |=
                     log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT,
-                            HandleToUint64(src_state->image), VALIDATION_ERROR_09c0013e,
+                            HandleToUint64(src_state->image), vuid,
                             "vkCmdCopyImage(): pRegion[%d] extent height (%d) must be a multiple of the compressed texture block "
-                            "height (%d), or when added to srcOffset.y (%d) must equal the image subresource height (%d)..",
+                            "height (%d), or when added to srcOffset.y (%d) must equal the image subresource height (%d).",
                             i, 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)) {
+                UNIQUE_VALIDATION_ERROR_CODE vuid = ext_ycbcr ? VALIDATION_ERROR_09c00d84 : VALIDATION_ERROR_09c00140;
                 skip |=
                     log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT,
-                            HandleToUint64(src_state->image), VALIDATION_ERROR_09c00140,
+                            HandleToUint64(src_state->image), vuid,
                             "vkCmdCopyImage(): pRegion[%d] extent width (%d) must be a multiple of the compressed texture block "
-                            "depth (%d), or when added to srcOffset.z (%d) must equal the image subresource depth (%d)..",
+                            "depth (%d), or when added to srcOffset.z (%d) must equal the image subresource depth (%d).",
                             i, src_copy_extent.depth, block_size.depth, region.srcOffset.z, mip_extent.depth);
             }
         }  // Compressed
@@ -1689,51 +1696,56 @@
             }
         }
 
-        // Checks that apply only to compressed images
-        if (FormatIsCompressed(dst_state->createInfo.format)) {
+        // Dest checks that apply only to compressed images (or to _422 images if ycbcr enabled)
+        if (FormatIsCompressed(dst_state->createInfo.format) ||
+            (ext_ycbcr && FormatIsSinglePlane_422(dst_state->createInfo.format))) {
             const VkExtent3D block_size = FormatCompressedTexelBlockExtent(dst_state->createInfo.format);
 
             //  image offsets must be multiples of block dimensions
             if ((SafeModulo(region.dstOffset.x, block_size.width) != 0) ||
                 (SafeModulo(region.dstOffset.y, block_size.height) != 0) ||
                 (SafeModulo(region.dstOffset.z, block_size.depth) != 0)) {
+                UNIQUE_VALIDATION_ERROR_CODE vuid = ext_ycbcr ? VALIDATION_ERROR_09c00d86 : VALIDATION_ERROR_09c00144;
                 skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT,
-                                HandleToUint64(dst_state->image), VALIDATION_ERROR_09c00144,
+                                HandleToUint64(dst_state->image), vuid,
                                 "vkCmdCopyImage(): pRegion[%d] dstOffset (%d, %d) must be multiples of the compressed image's "
-                                "texel width & height (%d, %d)..",
+                                "texel width & height (%d, %d).",
                                 i, region.dstOffset.x, region.dstOffset.y, block_size.width, block_size.height);
             }
 
             const VkExtent3D mip_extent = GetImageSubresourceExtent(dst_state, &(region.dstSubresource));
             if ((SafeModulo(dst_copy_extent.width, block_size.width) != 0) &&
                 (dst_copy_extent.width + region.dstOffset.x != mip_extent.width)) {
+                UNIQUE_VALIDATION_ERROR_CODE vuid = ext_ycbcr ? VALIDATION_ERROR_09c00d88 : VALIDATION_ERROR_09c00146;
                 skip |=
                     log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT,
-                            HandleToUint64(dst_state->image), VALIDATION_ERROR_09c00146,
+                            HandleToUint64(dst_state->image), vuid,
                             "vkCmdCopyImage(): pRegion[%d] dst_copy_extent width (%d) must be a multiple of the compressed texture "
-                            "block width (%d), or when added to dstOffset.x (%d) must equal the image subresource width (%d)..",
+                            "block width (%d), or when added to dstOffset.x (%d) must equal the image subresource width (%d).",
                             i, 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)) {
+                UNIQUE_VALIDATION_ERROR_CODE vuid = ext_ycbcr ? VALIDATION_ERROR_09c00d8a : VALIDATION_ERROR_09c00148;
                 skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT,
-                                HandleToUint64(dst_state->image), VALIDATION_ERROR_09c00148,
+                                HandleToUint64(dst_state->image), vuid,
                                 "vkCmdCopyImage(): pRegion[%d] dst_copy_extent height (%d) must be a multiple of the compressed "
                                 "texture block height (%d), or when added to dstOffset.y (%d) must equal the image subresource "
-                                "height (%d)..",
+                                "height (%d).",
                                 i, 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)) {
+                UNIQUE_VALIDATION_ERROR_CODE vuid = ext_ycbcr ? VALIDATION_ERROR_09c00d8c : VALIDATION_ERROR_09c0014a;
                 skip |=
                     log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT,
-                            HandleToUint64(dst_state->image), VALIDATION_ERROR_09c0014a,
+                            HandleToUint64(dst_state->image), vuid,
                             "vkCmdCopyImage(): pRegion[%d] dst_copy_extent width (%d) must be a multiple of the compressed texture "
-                            "block depth (%d), or when added to dstOffset.z (%d) must equal the image subresource depth (%d)..",
+                            "block depth (%d), or when added to dstOffset.z (%d) must equal the image subresource depth (%d).",
                             i, dst_copy_extent.depth, block_size.depth, region.dstOffset.z, mip_extent.depth);
             }
         }  // Compressed
@@ -1741,6 +1753,77 @@
     return skip;
 }
 
+// vkCmdCopyImage checks that only apply if the multiplane extension is enabled
+bool CopyImageMultiplaneValidation(const layer_data *dev_data, VkCommandBuffer command_buffer, const IMAGE_STATE *src_image_state,
+                                   const IMAGE_STATE *dst_image_state, const VkImageCopy region) {
+    bool skip = false;
+    const debug_report_data *report_data = core_validation::GetReportData(dev_data);
+
+    // Neither image is multiplane
+    if ((!FormatIsMultiplane(src_image_state->createInfo.format)) && (!FormatIsMultiplane(dst_image_state->createInfo.format))) {
+        // If neither image is multi-plane the aspectMask member of src and dst must match
+        if (region.srcSubresource.aspectMask != region.dstSubresource.aspectMask) {
+            std::stringstream ss;
+            ss << "vkCmdCopyImage: Copy between non-multiplane images with differing aspectMasks ( 0x" << std::hex
+               << region.srcSubresource.aspectMask << " and 0x" << region.dstSubresource.aspectMask << " )";
+            skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            HandleToUint64(command_buffer), VALIDATION_ERROR_09c00c1e, "%s.", ss.str().c_str());
+        }
+    } else {
+        // Source image multiplane checks
+        uint32_t planes = FormatPlaneCount(src_image_state->createInfo.format);
+        VkImageAspectFlags aspect = region.srcSubresource.aspectMask;
+        if ((2 == planes) && (aspect != VK_IMAGE_ASPECT_PLANE_0_BIT_KHR) && (aspect != VK_IMAGE_ASPECT_PLANE_1_BIT_KHR)) {
+            std::stringstream ss;
+            ss << "vkCmdCopyImage: Source image aspect mask (0x" << std::hex << aspect << ") is invalid for 2-plane format";
+            skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            HandleToUint64(command_buffer), VALIDATION_ERROR_09c00c20, "%s.", ss.str().c_str());
+        }
+        if ((3 == planes) && (aspect != VK_IMAGE_ASPECT_PLANE_0_BIT_KHR) && (aspect != VK_IMAGE_ASPECT_PLANE_1_BIT_KHR) &&
+            (aspect != VK_IMAGE_ASPECT_PLANE_2_BIT_KHR)) {
+            std::stringstream ss;
+            ss << "vkCmdCopyImage: Source image aspect mask (0x" << std::hex << aspect << ") is invalid for 3-plane format";
+            skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            HandleToUint64(command_buffer), VALIDATION_ERROR_09c00c22, "%s.", ss.str().c_str());
+        }
+        // Single-plane to multi-plane
+        if ((!FormatIsMultiplane(src_image_state->createInfo.format)) && (FormatIsMultiplane(dst_image_state->createInfo.format)) &&
+            (VK_IMAGE_ASPECT_COLOR_BIT != aspect)) {
+            std::stringstream ss;
+            ss << "vkCmdCopyImage: Source image aspect mask (0x" << std::hex << aspect << ") is not VK_IMAGE_ASPECT_COLOR_BIT";
+            skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            HandleToUint64(command_buffer), VALIDATION_ERROR_09c00c2a, "%s.", ss.str().c_str());
+        }
+
+        // Dest image multiplane checks
+        planes = FormatPlaneCount(dst_image_state->createInfo.format);
+        aspect = region.dstSubresource.aspectMask;
+        if ((2 == planes) && (aspect != VK_IMAGE_ASPECT_PLANE_0_BIT_KHR) && (aspect != VK_IMAGE_ASPECT_PLANE_1_BIT_KHR)) {
+            std::stringstream ss;
+            ss << "vkCmdCopyImage: Dest image aspect mask (0x" << std::hex << aspect << ") is invalid for 2-plane format";
+            skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            HandleToUint64(command_buffer), VALIDATION_ERROR_09c00c24, "%s.", ss.str().c_str());
+        }
+        if ((3 == planes) && (aspect != VK_IMAGE_ASPECT_PLANE_0_BIT_KHR) && (aspect != VK_IMAGE_ASPECT_PLANE_1_BIT_KHR) &&
+            (aspect != VK_IMAGE_ASPECT_PLANE_2_BIT_KHR)) {
+            std::stringstream ss;
+            ss << "vkCmdCopyImage: Dest image aspect mask (0x" << std::hex << aspect << ") is invalid for 3-plane format";
+            skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            HandleToUint64(command_buffer), VALIDATION_ERROR_09c00c26, "%s.", ss.str().c_str());
+        }
+        // Multi-plane to single-plane
+        if ((FormatIsMultiplane(src_image_state->createInfo.format)) && (!FormatIsMultiplane(dst_image_state->createInfo.format)) &&
+            (VK_IMAGE_ASPECT_COLOR_BIT != aspect)) {
+            std::stringstream ss;
+            ss << "vkCmdCopyImage: Dest image aspect mask (0x" << std::hex << aspect << ") is not VK_IMAGE_ASPECT_COLOR_BIT";
+            skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            HandleToUint64(command_buffer), VALIDATION_ERROR_09c00c28, "%s.", ss.str().c_str());
+        }
+    }
+
+    return skip;
+}
+
 bool PreCallValidateCmdCopyImage(layer_data *device_data, GLOBAL_CB_NODE *cb_node, IMAGE_STATE *src_image_state,
                                  IMAGE_STATE *dst_image_state, uint32_t region_count, const VkImageCopy *regions,
                                  VkImageLayout src_image_layout, VkImageLayout dst_image_layout) {
@@ -1817,6 +1900,11 @@
             }
         }
 
+        // Do multiplane-specific checks, if extension enabled
+        if (GetDeviceExtensions(device_data)->vk_khr_sampler_ycbcr_conversion) {
+            skip |= CopyImageMultiplaneValidation(device_data, command_buffer, src_image_state, dst_image_state, region);
+        }
+
         if (!GetDeviceExtensions(device_data)->vk_khr_sampler_ycbcr_conversion) {
             // not multi-plane, the aspectMask member of srcSubresource and dstSubresource must match
             if (region.srcSubresource.aspectMask != region.dstSubresource.aspectMask) {
diff --git a/layers/vk_format_utils.cpp b/layers/vk_format_utils.cpp
index 83029fe..9784dd5 100644
--- a/layers/vk_format_utils.cpp
+++ b/layers/vk_format_utils.cpp
@@ -229,7 +229,40 @@
     {VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG,  {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_PVRTC1_2BPP_BIT}},
     {VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG,  {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_PVRTC1_4BPP_BIT}},
     {VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG,  {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_PVRTC2_2BPP_BIT}},
-    {VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG,  {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_PVRTC2_4BPP_BIT}}
+    {VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG,  {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_PVRTC2_4BPP_BIT}},
+    /* KHR_sampler_YCbCr_conversion */
+    {VK_FORMAT_G8B8G8R8_422_UNORM_KHR,                          {4, 4, VK_FORMAT_COMPATIBILITY_CLASS_32BIT_G8B8G8R8}},
+    {VK_FORMAT_B8G8R8G8_422_UNORM_KHR,                          {4, 4, VK_FORMAT_COMPATIBILITY_CLASS_32BIT_B8G8R8G8}},
+    {VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16_KHR,          {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_64BIT_R10G10B10A10}},
+    {VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16_KHR,      {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_64BIT_G10B10G10R10}},
+    {VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16_KHR,      {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_64BIT_B10G10R10G10}},
+    {VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16_KHR,          {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_64BIT_R12G12B12A12}},
+    {VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16_KHR,      {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_64BIT_G12B12G12R12}},
+    {VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16_KHR,      {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_64BIT_B12G12R12G12}},
+    {VK_FORMAT_G16B16G16R16_422_UNORM_KHR,                      {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_64BIT_G16B16G16R16}},
+    {VK_FORMAT_B16G16R16G16_422_UNORM_KHR,                      {8, 4, VK_FORMAT_COMPATIBILITY_CLASS_64BIT_B16G16R16G16}},
+#if 0   // TBD - Figure out what size means for multi-planar formats
+    {VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM_KHR,                   {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_8BIT_3PLANE_420}},
+    {VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR,                    {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_8BIT_2PLANE_420}},
+    {VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM_KHR,                   {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_8BIT_3PLANE_422}},
+    {VK_FORMAT_G8_B8R8_2PLANE_422_UNORM_KHR,                    {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_8BIT_2PLANE_422}},
+    {VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM_KHR,                   {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_8BIT_3PLANE_444}},
+    {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16_KHR,  {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_10BIT_3PLANE_420}},
+    {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16_KHR,   {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_10BIT_2PLANE_420}},
+    {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16_KHR,  {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_10BIT_3PLANE_422}},
+    {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16_KHR,   {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_10BIT_2PLANE_422}},
+    {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16_KHR,  {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_10BIT_3PLANE_444}},
+    {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16_KHR,  {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_12BIT_3PLANE_420}},
+    {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16_KHR,   {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_12BIT_2PLANE_420}},
+    {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16_KHR,  {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_12BIT_3PLANE_422}},
+    {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16_KHR,   {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_12BIT_2PLANE_422}},
+    {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16_KHR,  {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_12BIT_3PLANE_444}},
+    {VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM_KHR,                {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_16BIT_3PLANE_420}},
+    {VK_FORMAT_G16_B16R16_2PLANE_420_UNORM_KHR,                 {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_16BIT_2PLANE_420}},
+    {VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM_KHR,                {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_16BIT_3PLANE_422}},
+    {VK_FORMAT_G16_B16R16_2PLANE_422_UNORM_KHR,                 {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_16BIT_2PLANE_422}},
+    {VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM_KHR,                {0, 3, VK_FORMAT_COMPATIBILITY_CLASS_16BIT_3PLANE_444}}
+#endif
 };
 
 // Renable formatting
@@ -349,6 +382,27 @@
     return found;
 }
 
+// Single-plane "_422" formats are treated as 2x1 compressed (for copies)
+VK_LAYER_EXPORT bool FormatIsSinglePlane_422(VkFormat format) {
+    bool found = false;
+
+    switch (format) {
+        case VK_FORMAT_G8B8G8R8_422_UNORM_KHR:
+        case VK_FORMAT_B8G8R8G8_422_UNORM_KHR:
+        case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16_KHR:
+        case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16_KHR:
+        case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16_KHR:
+        case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16_KHR:
+        case VK_FORMAT_G16B16G16R16_422_UNORM_KHR:
+        case VK_FORMAT_B16G16R16G16_422_UNORM_KHR:
+            found = true;
+            break;
+        default:
+            break;
+    }
+    return found;
+}
+
 // Return true if format is compressed
 VK_LAYER_EXPORT bool FormatIsCompressed(VkFormat format) {
     return (FormatIsCompressed_ASTC_LDR(format) || FormatIsCompressed_BC(format) || FormatIsCompressed_ETC2_EAC(format) ||
@@ -858,6 +912,18 @@
         case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG:
             block_size = {4, 4, 1};
             break;
+        // With KHR_sampler_ycbcr_conversion, these formats are treated as 2x1 compressed (for copies)
+        case VK_FORMAT_G8B8G8R8_422_UNORM_KHR:
+        case VK_FORMAT_B8G8R8G8_422_UNORM_KHR:
+        case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16_KHR:
+        case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16_KHR:
+        case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16_KHR:
+        case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16_KHR:
+        case VK_FORMAT_G16B16G16R16_422_UNORM_KHR:
+        case VK_FORMAT_B16G16R16G16_422_UNORM_KHR:
+            block_size = {2, 1, 1};
+            break;
+
         default:
             break;
     }
@@ -878,7 +944,7 @@
         case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM_KHR:
         case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM_KHR:
         case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM_KHR:
-            return 3u;
+            return 3;
             break;
         case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR:
         case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM_KHR:
@@ -888,10 +954,10 @@
         case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16_KHR:
         case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM_KHR:
         case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM_KHR:
-            return 2u;
+            return 2;
             break;
         default:
-            return 1u;
+            return 1;
             break;
     }
 }
@@ -931,3 +997,88 @@
     }
     return result;
 }
+
+struct VULKAN_PER_PLANE_COMPATIBILITY {
+    uint32_t width_divisor;
+    uint32_t height_divisor;
+    VkFormat compatible_format;
+};
+
+struct VULKAN_MULTIPLANE_COMPATIBILITY {
+    VULKAN_PER_PLANE_COMPATIBILITY per_plane[VK_MULTIPLANE_FORMAT_MAX_PLANES];
+};
+
+// Source: Vulkan spec Table 45. Plane Format Compatibility Table
+// clang-format off
+const std::map<VkFormat, VULKAN_MULTIPLANE_COMPATIBILITY> vk_multiplane_compatibility_map {
+    { VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM_KHR,                  { { { 1, 1, VK_FORMAT_R8_UNORM },
+                                                                    { 2, 2, VK_FORMAT_R8_UNORM },
+                                                                    { 2, 2, VK_FORMAT_R8_UNORM } } } },
+    { VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR,                   { { { 1, 1, VK_FORMAT_R8_UNORM },
+                                                                    { 2, 2, VK_FORMAT_R8G8_UNORM },
+                                                                    { 1, 1, VK_FORMAT_UNDEFINED } } } },
+    { VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM_KHR,                  { { { 1, 1, VK_FORMAT_R8_UNORM },
+                                                                    { 2, 1, VK_FORMAT_R8_UNORM },
+                                                                    { 2, 1, VK_FORMAT_R8_UNORM } } } },
+    { VK_FORMAT_G8_B8R8_2PLANE_422_UNORM_KHR,                   { { { 1, 1, VK_FORMAT_R8_UNORM },
+                                                                    { 2, 1, VK_FORMAT_R8G8_UNORM },
+                                                                    { 1, 1, VK_FORMAT_UNDEFINED } } } },
+    { VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM_KHR,                  { { { 1, 1, VK_FORMAT_R8_UNORM },
+                                                                    { 1, 1, VK_FORMAT_R8_UNORM },
+                                                                    { 1, 1, VK_FORMAT_R8_UNORM } } } },
+    { VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16_KHR, { { { 1, 1, VK_FORMAT_R10X6_UNORM_PACK16_KHR },
+                                                                    { 2, 2, VK_FORMAT_R10X6_UNORM_PACK16_KHR },
+                                                                    { 2, 2, VK_FORMAT_R10X6_UNORM_PACK16_KHR } } } },
+    { VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16_KHR,  { { { 1, 1, VK_FORMAT_R10X6_UNORM_PACK16_KHR },
+                                                                    { 2, 2, VK_FORMAT_R10X6G10X6_UNORM_2PACK16_KHR },
+                                                                    { 1, 1, VK_FORMAT_UNDEFINED } } } },
+    { VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16_KHR, { { { 1, 1, VK_FORMAT_R10X6_UNORM_PACK16_KHR },
+                                                                    { 2, 1, VK_FORMAT_R10X6_UNORM_PACK16_KHR },
+                                                                    { 2, 1, VK_FORMAT_R10X6_UNORM_PACK16_KHR } } } },
+    { VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16_KHR,  { { { 1, 1, VK_FORMAT_R10X6_UNORM_PACK16_KHR },
+                                                                    { 2, 1, VK_FORMAT_R10X6G10X6_UNORM_2PACK16_KHR },
+                                                                    { 1, 1, VK_FORMAT_UNDEFINED } } } },
+    { VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16_KHR, { { { 1, 1, VK_FORMAT_R10X6_UNORM_PACK16_KHR },
+                                                                    { 1, 1, VK_FORMAT_R10X6_UNORM_PACK16_KHR },
+                                                                    { 1, 1, VK_FORMAT_R10X6_UNORM_PACK16_KHR } } } },
+    { VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16_KHR, { { { 1, 1, VK_FORMAT_R12X4_UNORM_PACK16_KHR },
+                                                                    { 2, 2, VK_FORMAT_R12X4_UNORM_PACK16_KHR },
+                                                                    { 2, 2, VK_FORMAT_R12X4_UNORM_PACK16_KHR } } } },
+    { VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16_KHR,  { { { 1, 1, VK_FORMAT_R12X4_UNORM_PACK16_KHR },
+                                                                    { 2, 2, VK_FORMAT_R12X4G12X4_UNORM_2PACK16_KHR },
+                                                                    { 1, 1, VK_FORMAT_UNDEFINED } } } },
+    { VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16_KHR, { { { 1, 1, VK_FORMAT_R12X4_UNORM_PACK16_KHR },
+                                                                    { 2, 1, VK_FORMAT_R12X4_UNORM_PACK16_KHR },
+                                                                    { 2, 1, VK_FORMAT_R12X4_UNORM_PACK16_KHR } } } },
+    { VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16_KHR,  { { { 1, 1, VK_FORMAT_R12X4_UNORM_PACK16_KHR },
+                                                                    { 2, 1, VK_FORMAT_R12X4G12X4_UNORM_2PACK16_KHR },
+                                                                    { 1, 1, VK_FORMAT_UNDEFINED } } } },
+    { VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16_KHR, { { { 1, 1, VK_FORMAT_R12X4_UNORM_PACK16_KHR },
+                                                                    { 1, 1, VK_FORMAT_R12X4_UNORM_PACK16_KHR },
+                                                                    { 1, 1, VK_FORMAT_R12X4_UNORM_PACK16_KHR } } } },
+    { VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM_KHR,               { { { 1, 1, VK_FORMAT_R16_UNORM },
+                                                                    { 2, 2, VK_FORMAT_R16_UNORM },
+                                                                    { 2, 2, VK_FORMAT_R16_UNORM } } } },
+    { VK_FORMAT_G16_B16R16_2PLANE_420_UNORM_KHR,                { { { 1, 1, VK_FORMAT_R16_UNORM },
+                                                                    { 2, 2, VK_FORMAT_R16G16_UNORM },
+                                                                    { 1, 1, VK_FORMAT_UNDEFINED } } } },
+    { VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM_KHR,               { { { 1, 1, VK_FORMAT_R16_UNORM },
+                                                                    { 2, 1, VK_FORMAT_R16_UNORM },
+                                                                    { 2, 1, VK_FORMAT_R16_UNORM } } } },
+    { VK_FORMAT_G16_B16R16_2PLANE_422_UNORM_KHR,                { { { 1, 1, VK_FORMAT_R16_UNORM },
+                                                                    { 2, 1, VK_FORMAT_R16G16_UNORM },
+                                                                    { 1, 1, VK_FORMAT_UNDEFINED } } } },
+    { VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM_KHR,               { { { 1, 1, VK_FORMAT_R16_UNORM },
+                                                                    { 1, 1, VK_FORMAT_R16_UNORM },
+                                                                    { 1, 1, VK_FORMAT_R16_UNORM } } } }
+};
+// clang-format on
+
+VK_LAYER_EXPORT VkFormat FindMultiplaneCompatibleFormat(VkFormat mp_fmt, uint32_t plane) {
+    auto it = vk_multiplane_compatibility_map.find(mp_fmt);
+    if ((it == vk_multiplane_compatibility_map.end()) || (plane >= VK_MULTIPLANE_FORMAT_MAX_PLANES)) {
+        return VK_FORMAT_UNDEFINED;
+    }
+
+    return it->second.per_plane[plane].compatible_format;
+}
diff --git a/layers/vk_format_utils.h b/layers/vk_format_utils.h
index c530405..e76f9ec 100644
--- a/layers/vk_format_utils.h
+++ b/layers/vk_format_utils.h
@@ -38,6 +38,8 @@
 extern "C" {
 #endif
 
+#define VK_MULTIPLANE_FORMAT_MAX_PLANES 3
+
 typedef enum VkFormatCompatibilityClass {
     VK_FORMAT_COMPATIBILITY_CLASS_NONE_BIT = 0,
     VK_FORMAT_COMPATIBILITY_CLASS_8_BIT = 1,
@@ -88,7 +90,38 @@
     VK_FORMAT_COMPATIBILITY_CLASS_PVRTC1_4BPP_BIT = 46,
     VK_FORMAT_COMPATIBILITY_CLASS_PVRTC2_2BPP_BIT = 47,
     VK_FORMAT_COMPATIBILITY_CLASS_PVRTC2_4BPP_BIT = 48,
-    VK_FORMAT_COMPATIBILITY_CLASS_MAX_ENUM = 49
+    /* KHR_sampler_YCbCr_conversion */
+    VK_FORMAT_COMPATIBILITY_CLASS_32BIT_G8B8G8R8 = 49,
+    VK_FORMAT_COMPATIBILITY_CLASS_32BIT_B8G8R8G8 = 50,
+    VK_FORMAT_COMPATIBILITY_CLASS_64BIT_R10G10B10A10 = 51,
+    VK_FORMAT_COMPATIBILITY_CLASS_64BIT_G10B10G10R10 = 52,
+    VK_FORMAT_COMPATIBILITY_CLASS_64BIT_B10G10R10G10 = 53,
+    VK_FORMAT_COMPATIBILITY_CLASS_64BIT_R12G12B12A12 = 54,
+    VK_FORMAT_COMPATIBILITY_CLASS_64BIT_G12B12G12R12 = 55,
+    VK_FORMAT_COMPATIBILITY_CLASS_64BIT_B12G12R12G12 = 56,
+    VK_FORMAT_COMPATIBILITY_CLASS_64BIT_G16B16G16R16 = 57,
+    VK_FORMAT_COMPATIBILITY_CLASS_64BIT_B16G16R16G16 = 58,
+    VK_FORMAT_COMPATIBILITY_CLASS_8BIT_3PLANE_420 = 59,
+    VK_FORMAT_COMPATIBILITY_CLASS_8BIT_2PLANE_420 = 60,
+    VK_FORMAT_COMPATIBILITY_CLASS_8BIT_3PLANE_422 = 61,
+    VK_FORMAT_COMPATIBILITY_CLASS_8BIT_2PLANE_422 = 62,
+    VK_FORMAT_COMPATIBILITY_CLASS_8BIT_3PLANE_444 = 63,
+    VK_FORMAT_COMPATIBILITY_CLASS_10BIT_3PLANE_420 = 64,
+    VK_FORMAT_COMPATIBILITY_CLASS_10BIT_2PLANE_420 = 65,
+    VK_FORMAT_COMPATIBILITY_CLASS_10BIT_3PLANE_422 = 66,
+    VK_FORMAT_COMPATIBILITY_CLASS_10BIT_2PLANE_422 = 67,
+    VK_FORMAT_COMPATIBILITY_CLASS_10BIT_3PLANE_444 = 68,
+    VK_FORMAT_COMPATIBILITY_CLASS_12BIT_3PLANE_420 = 69,
+    VK_FORMAT_COMPATIBILITY_CLASS_12BIT_2PLANE_420 = 70,
+    VK_FORMAT_COMPATIBILITY_CLASS_12BIT_3PLANE_422 = 71,
+    VK_FORMAT_COMPATIBILITY_CLASS_12BIT_2PLANE_422 = 72,
+    VK_FORMAT_COMPATIBILITY_CLASS_12BIT_3PLANE_444 = 73,
+    VK_FORMAT_COMPATIBILITY_CLASS_16BIT_3PLANE_420 = 74,
+    VK_FORMAT_COMPATIBILITY_CLASS_16BIT_2PLANE_420 = 75,
+    VK_FORMAT_COMPATIBILITY_CLASS_16BIT_3PLANE_422 = 76,
+    VK_FORMAT_COMPATIBILITY_CLASS_16BIT_2PLANE_422 = 77,
+    VK_FORMAT_COMPATIBILITY_CLASS_16BIT_3PLANE_444 = 78,
+    VK_FORMAT_COMPATIBILITY_CLASS_MAX_ENUM = 79
 } VkFormatCompatibilityClass;
 
 VK_LAYER_EXPORT bool FormatIsDepthOrStencil(VkFormat format);
@@ -99,6 +132,7 @@
 VK_LAYER_EXPORT bool FormatIsCompressed_ASTC_LDR(VkFormat format);
 VK_LAYER_EXPORT bool FormatIsCompressed_BC(VkFormat format);
 VK_LAYER_EXPORT bool FormatIsCompressed_PVRTC(VkFormat format);
+VK_LAYER_EXPORT bool FormatIsSinglePlane_422(VkFormat format);
 VK_LAYER_EXPORT bool FormatIsNorm(VkFormat format);
 VK_LAYER_EXPORT bool FormatIsUNorm(VkFormat format);
 VK_LAYER_EXPORT bool FormatIsSNorm(VkFormat format);
@@ -117,6 +151,7 @@
 VK_LAYER_EXPORT size_t FormatSize(VkFormat format);
 VK_LAYER_EXPORT VkFormatCompatibilityClass FormatCompatibilityClass(VkFormat format);
 VK_LAYER_EXPORT VkDeviceSize SafeModulo(VkDeviceSize dividend, VkDeviceSize divisor);
+VK_LAYER_EXPORT VkFormat FindMultiplaneCompatibleFormat(VkFormat fmt, uint32_t plane);
 
 static inline bool FormatIsUndef(VkFormat format) { return (format == VK_FORMAT_UNDEFINED); }
 static inline bool FormatHasDepth(VkFormat format) { return (FormatIsDepthOnly(format) || FormatIsDepthAndStencil(format)); }