layers: LX458, Extend image extent validation for CmdCopyImage
authorMark Lobodzinski <mark@lunarg.com>
Tue, 29 Mar 2016 23:10:14 +0000 (17:10 -0600)
committerMark Lobodzinski <mark@lunarg.com>
Thu, 31 Mar 2016 23:12:55 +0000 (17:12 -0600)
Also added many of the CmdCopyImage valid usage checks.

Change-Id: I398adf18b48eccacbd8e44ce53e50f3bf43f58ad

layers/image.cpp
layers/image.h

index a95584e1e2259c0877d7470fa9b6376490ae500b..164cfb18fd4fbbafcc8e9041763072e9a95313c1 100644 (file)
  * Author: Tobin Ehlis <tobin@lunarg.com>
  */
 
+// Allow use of STL min and max functions in Windows
+#define NOMINMAX
+
 #include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
 #include <vector>
+#include <algorithm>
 #include <unordered_map>
 #include <memory>
 using namespace std;
@@ -649,71 +653,216 @@ vkCmdClearDepthStencilImage(VkCommandBuffer commandBuffer, VkImage image, VkImag
     }
 }
 
-VK_LAYER_EXPORT VKAPI_ATTR void VKAPI_CALL
-vkCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage,
-               VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageCopy *pRegions) {
+// Returns true if [x, xoffset] and [y, yoffset] overlap
+static bool ranges_intersect(int32_t start, uint32_t start_offset, int32_t end, uint32_t end_offset) {
+    bool result = false;
+    uint32_t intersection_min = std::max(static_cast<uint32_t>(start), static_cast<uint32_t>(end));
+    uint32_t intersection_max = std::min(static_cast<uint32_t>(start) + start_offset, static_cast<uint32_t>(end) + end_offset);
+
+    if (intersection_max > intersection_min) {
+        result = true;
+    }
+    return result;
+}
+
+// Returns true if two VkImageCopy structures overlap
+static bool region_intersects(const VkImageCopy *src, const VkImageCopy *dst, VkImageType type) {
+    bool result = true;
+    if ((src->srcSubresource.mipLevel == dst->dstSubresource.mipLevel) &&
+        (ranges_intersect(src->srcSubresource.baseArrayLayer, src->srcSubresource.layerCount, dst->dstSubresource.baseArrayLayer,
+                          dst->dstSubresource.layerCount))) {
+
+        switch (type) {
+        case VK_IMAGE_TYPE_3D:
+            result &= ranges_intersect(src->srcOffset.z, src->extent.depth, dst->dstOffset.z, dst->extent.depth);
+        // Intentionally fall through to 2D case
+        case VK_IMAGE_TYPE_2D:
+            result &= ranges_intersect(src->srcOffset.y, src->extent.height, dst->dstOffset.y, dst->extent.height);
+        // Intentionally fall through to 1D case
+        case VK_IMAGE_TYPE_1D:
+            result &= ranges_intersect(src->srcOffset.x, src->extent.width, dst->dstOffset.x, dst->extent.width);
+            break;
+        default:
+            // Unrecognized or new IMAGE_TYPE enums will be caught in parameter_validation
+            assert(false);
+        }
+    }
+    return result;
+}
+
+// Returns true if offset and extent exceed image extents
+static bool exceeds_bounds(const VkOffset3D *offset, const VkExtent3D *extent, IMAGE_STATE *image) {
+    bool result = false;
+    // Extents/depths cannot be negative but checks left in for clarity
+
+    return result;
+}
+
+VkBool32 cmd_copy_image_valid_usage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImage dstImage, uint32_t regionCount,
+                                    const VkImageCopy *pRegions) {
+
     VkBool32 skipCall = VK_FALSE;
     layer_data *device_data = get_my_data_ptr(get_dispatch_key(commandBuffer), layer_data_map);
     auto srcImageEntry = device_data->imageMap.find(srcImage);
     auto dstImageEntry = device_data->imageMap.find(dstImage);
 
-    // For each region, src and dst number of layers should not be zero
-    // For each region, src and dst number of layers must match
-    // For each region, src aspect mask must match dest aspect mask
-    // For each region, color aspects cannot be mixed with depth/stencil aspects
-    for (uint32_t i = 0; i < regionCount; i++) {
-        if (pRegions[i].srcSubresource.layerCount == 0) {
-            char const str[] = "vkCmdCopyImage: number of layers in source subresource is zero";
-            // TODO: Verify against Valid Use section of spec
-            skipCall |=
-                log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
-                        (uint64_t)commandBuffer, __LINE__, IMAGE_MISMATCHED_IMAGE_ASPECT, "IMAGE", str);
-        }
+    // TODO: This does not cover swapchain-created images. This should fall out when this layer is moved
+    // into the core_validation layer
+    if ((srcImageEntry != device_data->imageMap.end()) && (dstImageEntry != device_data->imageMap.end())) {
 
-        if (pRegions[i].dstSubresource.layerCount == 0) {
-            char const str[] = "vkCmdCopyImage: number of layers in destination subresource is zero";
-            // TODO: Verify against Valid Use section of spec
-            skipCall |=
-                log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
-                        (uint64_t)commandBuffer, __LINE__, IMAGE_MISMATCHED_IMAGE_ASPECT, "IMAGE", str);
-        }
+        for (uint32_t i = 0; i < regionCount; i++) {
 
-        if (pRegions[i].srcSubresource.layerCount != pRegions[i].dstSubresource.layerCount) {
-            char const str[] = "vkCmdCopyImage: number of layers in source and destination subresources must match";
-            skipCall |=
-                log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
-                        (uint64_t)commandBuffer, __LINE__, IMAGE_MISMATCHED_IMAGE_ASPECT, "IMAGE", str);
-        }
+            if (pRegions[i].srcSubresource.layerCount == 0) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: number of layers in pRegions[" << i << "] srcSubresource is zero";
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_WARNING_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_MISMATCHED_IMAGE_ASPECT, "IMAGE", "%s", ss.str().c_str());
+            }
 
-        if (pRegions[i].srcSubresource.aspectMask != pRegions[i].dstSubresource.aspectMask) {
-            char const str[] = "vkCmdCopyImage: Src and dest aspectMasks for each region must match";
-            skipCall |=
-                log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
-                        (uint64_t)commandBuffer, __LINE__, IMAGE_MISMATCHED_IMAGE_ASPECT, "IMAGE", str);
-        }
-        if ((pRegions[i].srcSubresource.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) &&
-            (pRegions[i].srcSubresource.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT))) {
-            char const str[] = "vkCmdCopyImage aspectMask cannot specify both COLOR and DEPTH/STENCIL aspects";
-            skipCall |=
-                log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
-                        (uint64_t)commandBuffer, __LINE__, IMAGE_INVALID_IMAGE_ASPECT, "IMAGE", str);
-        }
-    }
+            if (pRegions[i].dstSubresource.layerCount == 0) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: number of layers in pRegions[" << i << "] dstSubresource is zero";
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_WARNING_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_MISMATCHED_IMAGE_ASPECT, "IMAGE", "%s", ss.str().c_str());
+            }
 
-    if ((srcImageEntry != device_data->imageMap.end()) && (dstImageEntry != device_data->imageMap.end())) {
-        if (srcImageEntry->second.imageType != dstImageEntry->second.imageType) {
-            char const str[] = "vkCmdCopyImage called with unmatched source and dest image types.";
-            skipCall |=
-                log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
-                        (uint64_t)commandBuffer, __LINE__, IMAGE_MISMATCHED_IMAGE_TYPE, "IMAGE", str);
+            // For each region the layerCount member of srcSubresource and dstSubresource must match
+            if (pRegions[i].srcSubresource.layerCount != pRegions[i].dstSubresource.layerCount) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: number of layers in source and destination subresources for pRegions[" << i
+                   << "] do not match";
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_EXTENTS, "IMAGE", "%s", ss.str().c_str());
+            }
+
+            // For each region, the aspectMask member of srcSubresource and dstSubresource must match
+            if (pRegions[i].srcSubresource.aspectMask != pRegions[i].dstSubresource.aspectMask) {
+                char const str[] = "vkCmdCopyImage: Src and dest aspectMasks for each region must match";
+                skipCall |=
+                    log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            reinterpret_cast<uint64_t &>(commandBuffer), __LINE__, IMAGE_MISMATCHED_IMAGE_ASPECT, "IMAGE", str);
+            }
+
+            // AspectMask must not contain VK_IMAGE_ASPECT_METADATA_BIT
+            if ((pRegions[i].srcSubresource.aspectMask & VK_IMAGE_ASPECT_METADATA_BIT) ||
+                (pRegions[i].dstSubresource.aspectMask & VK_IMAGE_ASPECT_METADATA_BIT)) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: pRegions[" << i << "] may not specify aspectMask containing VK_IMAGE_ASPECT_METADATA_BIT";
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_IMAGE_ASPECT, "IMAGE", "%s", ss.str().c_str());
+            }
+
+            // For each region, if aspectMask contains VK_IMAGE_ASPECT_COLOR_BIT, it must not contain either of
+            // VK_IMAGE_ASPECT_DEPTH_BIT or VK_IMAGE_ASPECT_STENCIL_BIT
+            if ((pRegions[i].srcSubresource.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) &&
+                (pRegions[i].srcSubresource.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT))) {
+                char const str[] = "vkCmdCopyImage aspectMask cannot specify both COLOR and DEPTH/STENCIL aspects";
+                skipCall |=
+                    log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
+                            reinterpret_cast<uint64_t &>(commandBuffer), __LINE__, IMAGE_INVALID_IMAGE_ASPECT, "IMAGE", str);
+            }
+
+            // If either of the calling command's srcImage or dstImage parameters are of VkImageType VK_IMAGE_TYPE_3D,
+            // the baseArrayLayer and layerCount members of both srcSubresource and dstSubresource must be 0 and 1, respectively
+            if (((srcImageEntry->second.imageType == VK_IMAGE_TYPE_3D) || (dstImageEntry->second.imageType == VK_IMAGE_TYPE_3D)) &&
+                ((pRegions[i].srcSubresource.baseArrayLayer != 0) || (pRegions[i].srcSubresource.layerCount != 1) ||
+                 (pRegions[i].dstSubresource.baseArrayLayer != 0) || (pRegions[i].dstSubresource.layerCount != 1))) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: src or dstImage type was IMAGE_TYPE_3D, but in subRegion[" << i
+                   << "] baseArrayLayer was not zero or layerCount was not 1.";
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_EXTENTS, "IMAGE", "%s", ss.str().c_str());
+            }
+
+            // MipLevel must be less than the mipLevels specified in VkImageCreateInfo when the image was created
+            if (pRegions[i].srcSubresource.mipLevel >= srcImageEntry->second.mipLevels) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: pRegions[" << i
+                   << "] specifies a src mipLevel greater than the number specified when the srcImage was created.";
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_EXTENTS, "IMAGE", "%s", ss.str().c_str());
+            }
+            if (pRegions[i].dstSubresource.mipLevel >= dstImageEntry->second.mipLevels) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: pRegions[" << i
+                   << "] specifies a dst mipLevel greater than the number specified when the dstImage was created.";
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_EXTENTS, "IMAGE", "%s", ss.str().c_str());
+            }
+
+            // (baseArrayLayer + layerCount) must be less than or equal to the arrayLayers specified in VkImageCreateInfo when the
+            // image was created
+            if ((pRegions[i].srcSubresource.baseArrayLayer + pRegions[i].srcSubresource.layerCount) >
+                srcImageEntry->second.arraySize) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: srcImage arrayLayers was " << srcImageEntry->second.arraySize << " but subRegion[" << i
+                   << "] baseArrayLayer + layerCount is "
+                   << (pRegions[i].srcSubresource.baseArrayLayer + pRegions[i].srcSubresource.layerCount);
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_EXTENTS, "IMAGE", "%s", ss.str().c_str());
+            }
+            if ((pRegions[i].dstSubresource.baseArrayLayer + pRegions[i].dstSubresource.layerCount) >
+                dstImageEntry->second.arraySize) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: dstImage arrayLayers was " << dstImageEntry->second.arraySize << " but subRegion[" << i
+                   << "] baseArrayLayer + layerCount is "
+                   << (pRegions[i].dstSubresource.baseArrayLayer + pRegions[i].dstSubresource.layerCount);
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_EXTENTS, "IMAGE", "%s", ss.str().c_str());
+            }
+
+            // The source region specified by a given element of pRegions must be a region that is contained within srcImage
+            if (exceeds_bounds(&pRegions[i].srcOffset, &pRegions[i].extent, &srcImageEntry->second)) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: srcSubResource in pRegions[" << i << "] exceeds extents srcImage was created with";
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_EXTENTS, "IMAGE", "%s", ss.str().c_str());
+            }
+
+            // The destination region specified by a given element of pRegions must be a region that is contained within dstImage
+            if (exceeds_bounds(&pRegions[i].dstOffset, &pRegions[i].extent, &dstImageEntry->second)) {
+                std::stringstream ss;
+                ss << "vkCmdCopyImage: dstSubResource in pRegions[" << i << "] exceeds extents dstImage was created with";
+                skipCall |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_EXTENTS, "IMAGE", "%s", ss.str().c_str());
+            }
+
+            // The union of all source regions, and the union of all destination regions, specified by the elements of pRegions,
+            // must not overlap in memory
+            if (srcImage == dstImage) {
+                for (uint32_t j = 0; j < regionCount; j++) {
+                    if (region_intersects(&pRegions[i], &pRegions[j], srcImageEntry->second.imageType)) {
+                        std::stringstream ss;
+                        ss << "vkCmdCopyImage: pRegions[" << i << "] src overlaps with pRegions[" << j << "].";
+                        skipCall |=
+                            log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
+                                    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, reinterpret_cast<uint64_t &>(commandBuffer),
+                                    __LINE__, IMAGE_INVALID_EXTENTS, "IMAGE", "%s", ss.str().c_str());
+                    }
+                }
+            }
         }
-        // Check that format is same size or exact stencil/depth
-        if (is_depth_format(srcImageEntry->second.format)) {
+
+        // The formats of srcImage and dstImage 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. Depth/stencil formats must match exactly.
+        if (is_depth_format(srcImageEntry->second.format) || is_depth_format(dstImageEntry->second.format)) {
             if (srcImageEntry->second.format != dstImageEntry->second.format) {
                 char const str[] = "vkCmdCopyImage called with unmatched source and dest image depth/stencil formats.";
                 skipCall |=
                     log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
-                            (uint64_t)commandBuffer, __LINE__, IMAGE_MISMATCHED_IMAGE_FORMAT, "IMAGE", str);
+                            reinterpret_cast<uint64_t &>(commandBuffer), __LINE__, IMAGE_MISMATCHED_IMAGE_FORMAT, "IMAGE", str);
             }
         } else {
             size_t srcSize = vk_format_get_size(srcImageEntry->second.format);
@@ -722,10 +871,22 @@ vkCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout sr
                 char const str[] = "vkCmdCopyImage called with unmatched source and dest image format sizes.";
                 skipCall |=
                     log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,
-                            (uint64_t)commandBuffer, __LINE__, IMAGE_MISMATCHED_IMAGE_FORMAT, "IMAGE", str);
+                            reinterpret_cast<uint64_t &>(commandBuffer), __LINE__, IMAGE_MISMATCHED_IMAGE_FORMAT, "IMAGE", str);
             }
         }
     }
+    return skipCall;
+}
+
+VK_LAYER_EXPORT VKAPI_ATTR void VKAPI_CALL vkCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage,
+                                                          VkImageLayout srcImageLayout, VkImage dstImage,
+                                                          VkImageLayout dstImageLayout, uint32_t regionCount,
+                                                          const VkImageCopy *pRegions) {
+
+    VkBool32 skipCall = VK_FALSE;
+    layer_data *device_data = get_my_data_ptr(get_dispatch_key(commandBuffer), layer_data_map);
+
+    skipCall = cmd_copy_image_valid_usage(commandBuffer, srcImage, dstImage, regionCount, pRegions);
 
     if (VK_FALSE == skipCall) {
         device_data->device_dispatch_table->CmdCopyImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout,
index bec5bb9e26bc7c06b4e57a03fae412ad108dba11..db1124c22c228a6404cf73b55199d0ad9397a06f 100644 (file)
@@ -49,7 +49,8 @@ typedef enum _IMAGE_ERROR {
     IMAGE_INVALID_FILTER,                   // Operation specifies an invalid filter setting
     IMAGE_INVALID_IMAGE_RESOURCE,           // Image resource/subresource called with invalid setting
     IMAGE_INVALID_FORMAT_LIMITS_VIOLATION,  // Device limits for this format have been exceeded
-    IMAGE_INVALID_LAYOUT,                   // Operation specifies an invalid layout.
+    IMAGE_INVALID_LAYOUT,                   // Operation specifies an invalid layout
+    IMAGE_INVALID_EXTENTS,                  // Operation specifies invalid image extents
 } IMAGE_ERROR;
 
 typedef struct _IMAGE_STATE {