Support Noop Copy for B2T, T2B and T2T Copies

In B2T, T2B and T2T copies, copySize has 0 in width, height or depth
will trigger errors in D3D12 backend.

This patch bypass the command record step for noop copy. But all
validation rules still applies to the copy.

BUG=dawn:255

Change-Id: I4d01cef2e3c1f78440014c2c6ac63a48310d99af
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/28521
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index bc5b533..09eb91f 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -654,19 +654,22 @@
                     Align(copySize->width * blockInfo.blockByteSize, kTextureBytesPerRowAlignment);
             }
 
-            // Record the copy command.
-            CopyBufferToTextureCmd* copy =
-                allocator->Allocate<CopyBufferToTextureCmd>(Command::CopyBufferToTexture);
-            copy->source.buffer = source->buffer;
-            copy->source.offset = source->layout.offset;
-            copy->source.bytesPerRow = bytesPerRow;
-            copy->source.rowsPerImage = defaultedRowsPerImage;
-            copy->destination.texture = destination->texture;
-            copy->destination.origin = destination->origin;
-            copy->destination.mipLevel = destination->mipLevel;
-            copy->destination.aspect =
-                ConvertAspect(destination->texture->GetFormat(), destination->aspect);
-            copy->copySize = *copySize;
+            // Skip noop copies.
+            if (copySize->width != 0 && copySize->height != 0 && copySize->depth != 0) {
+                // Record the copy command.
+                CopyBufferToTextureCmd* copy =
+                    allocator->Allocate<CopyBufferToTextureCmd>(Command::CopyBufferToTexture);
+                copy->source.buffer = source->buffer;
+                copy->source.offset = source->layout.offset;
+                copy->source.bytesPerRow = bytesPerRow;
+                copy->source.rowsPerImage = defaultedRowsPerImage;
+                copy->destination.texture = destination->texture;
+                copy->destination.origin = destination->origin;
+                copy->destination.mipLevel = destination->mipLevel;
+                copy->destination.aspect =
+                    ConvertAspect(destination->texture->GetFormat(), destination->aspect);
+                copy->copySize = *copySize;
+            }
 
             return {};
         });
@@ -713,18 +716,21 @@
                     Align(copySize->width * blockInfo.blockByteSize, kTextureBytesPerRowAlignment);
             }
 
-            // Record the copy command.
-            CopyTextureToBufferCmd* copy =
-                allocator->Allocate<CopyTextureToBufferCmd>(Command::CopyTextureToBuffer);
-            copy->source.texture = source->texture;
-            copy->source.origin = source->origin;
-            copy->source.mipLevel = source->mipLevel;
-            copy->source.aspect = ConvertAspect(source->texture->GetFormat(), source->aspect);
-            copy->destination.buffer = destination->buffer;
-            copy->destination.offset = destination->layout.offset;
-            copy->destination.bytesPerRow = bytesPerRow;
-            copy->destination.rowsPerImage = defaultedRowsPerImage;
-            copy->copySize = *copySize;
+            // Skip noop copies.
+            if (copySize->width != 0 && copySize->height != 0 && copySize->depth != 0) {
+                // Record the copy command.
+                CopyTextureToBufferCmd* copy =
+                    allocator->Allocate<CopyTextureToBufferCmd>(Command::CopyTextureToBuffer);
+                copy->source.texture = source->texture;
+                copy->source.origin = source->origin;
+                copy->source.mipLevel = source->mipLevel;
+                copy->source.aspect = ConvertAspect(source->texture->GetFormat(), source->aspect);
+                copy->destination.buffer = destination->buffer;
+                copy->destination.offset = destination->layout.offset;
+                copy->destination.bytesPerRow = bytesPerRow;
+                copy->destination.rowsPerImage = defaultedRowsPerImage;
+                copy->copySize = *copySize;
+            }
 
             return {};
         });
@@ -754,18 +760,21 @@
                 mTopLevelTextures.insert(destination->texture);
             }
 
-            CopyTextureToTextureCmd* copy =
-                allocator->Allocate<CopyTextureToTextureCmd>(Command::CopyTextureToTexture);
-            copy->source.texture = source->texture;
-            copy->source.origin = source->origin;
-            copy->source.mipLevel = source->mipLevel;
-            copy->source.aspect = ConvertAspect(source->texture->GetFormat(), source->aspect);
-            copy->destination.texture = destination->texture;
-            copy->destination.origin = destination->origin;
-            copy->destination.mipLevel = destination->mipLevel;
-            copy->destination.aspect =
-                ConvertAspect(destination->texture->GetFormat(), destination->aspect);
-            copy->copySize = *copySize;
+            // Skip noop copies.
+            if (copySize->width != 0 && copySize->height != 0 && copySize->depth != 0) {
+                CopyTextureToTextureCmd* copy =
+                    allocator->Allocate<CopyTextureToTextureCmd>(Command::CopyTextureToTexture);
+                copy->source.texture = source->texture;
+                copy->source.origin = source->origin;
+                copy->source.mipLevel = source->mipLevel;
+                copy->source.aspect = ConvertAspect(source->texture->GetFormat(), source->aspect);
+                copy->destination.texture = destination->texture;
+                copy->destination.origin = destination->origin;
+                copy->destination.mipLevel = destination->mipLevel;
+                copy->destination.aspect =
+                    ConvertAspect(destination->texture->GetFormat(), destination->aspect);
+                copy->copySize = *copySize;
+            }
 
             return {};
         });
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index 311d98a..9dbbbbd 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -88,6 +88,7 @@
     void DoTest(const TextureSpec& textureSpec,
                 const BufferSpec& bufferSpec,
                 const wgpu::Extent3D& copySize) {
+        const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(kTextureFormat);
         // Create a texture that is `width` x `height` with (`level` + 1) mip levels.
         wgpu::TextureDescriptor descriptor;
         descriptor.dimension = wgpu::TextureDimension::e2D;
@@ -121,13 +122,10 @@
         // Prepopulating the buffer with empty data ensures that there is not random data in the
         // expectation and helps ensure that the padding due to the bytes per row is not modified
         // by the copy.
-        // TODO(jiawei.shao@intel.com): remove the initialization of the buffer after we support
-        // buffer lazy-initialization.
-        const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(kTextureFormat);
-        const std::vector<RGBA8> emptyData(bufferSpec.size / bytesPerTexel, RGBA8());
-        wgpu::Buffer buffer =
-            utils::CreateBufferFromData(device, emptyData.data(), bufferSpec.size,
-                                        wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
+        wgpu::BufferDescriptor bufferDesc;
+        bufferDesc.size = bufferSpec.size;
+        bufferDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
+        wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);
 
         {
             wgpu::TextureCopyView textureCopyView =
@@ -141,8 +139,11 @@
         queue.Submit(1, &commands);
 
         uint64_t bufferOffset = bufferSpec.offset;
-        const uint32_t texelCountInCopyRegion =
-            bufferSpec.bytesPerRow / bytesPerTexel * (copySize.height - 1) + copySize.width;
+
+        const wgpu::Extent3D copySizePerSlice = {copySize.width, copySize.height, 1};
+        // Texels in single slice.
+        const uint32_t texelCountInCopyRegion = utils::GetTexelCountInCopyRegion(
+            bufferSpec.bytesPerRow, bufferSpec.rowsPerImage, copySizePerSlice, kTextureFormat);
         const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depth;
         std::vector<RGBA8> expected(texelCountInCopyRegion);
         for (uint32_t slice = textureSpec.copyOrigin.z; slice < maxArrayLayer; ++slice) {
@@ -298,6 +299,7 @@
             utils::CreateTextureCopyView(srcTexture, srcSpec.level, {0, 0, srcSpec.copyOrigin.z});
         encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyLayout.mipSize);
 
+        const wgpu::Extent3D copySizePerSlice = {copySize.width, copySize.height, 1};
         // Perform the texture to texture copy
         wgpu::TextureCopyView srcTextureCopyView =
             utils::CreateTextureCopyView(srcTexture, srcSpec.level, srcSpec.copyOrigin);
@@ -308,8 +310,10 @@
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
-        const uint32_t texelCountInCopyRegion =
-            copyLayout.texelBlocksPerRow * (copySize.height - 1) + copySize.width;
+        // Texels in single slice.
+        const uint32_t texelCountInCopyRegion = utils::GetTexelCountInCopyRegion(
+            copyLayout.bytesPerRow, copyLayout.bytesPerImage / copyLayout.bytesPerRow,
+            copySizePerSlice, kTextureFormat);
         std::vector<RGBA8> expected(texelCountInCopyRegion);
         for (uint32_t slice = 0; slice < copySize.depth; ++slice) {
             std::fill(expected.begin(), expected.end(), RGBA8());
@@ -395,6 +399,21 @@
     DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
 }
 
+// Test noop copies
+TEST_P(CopyTests_T2B, ZeroSizedCopy) {
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {0, kHeight, 1});
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, 0, 1});
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 0});
+}
+
 // Test that copying an entire texture without 256-byte aligned dimensions works
 TEST_P(CopyTests_T2B, FullTextureUnaligned) {
     constexpr uint32_t kWidth = 259;
@@ -830,6 +849,21 @@
     DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
 }
 
+// Test noop copies.
+TEST_P(CopyTests_B2T, ZeroSizedCopy) {
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {0, kHeight, 1});
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, 0, 1});
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 0});
+}
+
 // Test that copying an entire texture without 256-byte aligned dimensions works
 TEST_P(CopyTests_B2T, FullTextureUnaligned) {
     constexpr uint32_t kWidth = 259;
@@ -1244,6 +1278,20 @@
     DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1});
 }
 
+// Test noop copies.
+TEST_P(CopyTests_T2T, ZeroSizedCopy) {
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    DoTest(textureSpec, textureSpec, {0, kHeight, 1});
+    DoTest(textureSpec, textureSpec, {kWidth, 0, 1});
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 0});
+}
+
 TEST_P(CopyTests_T2T, TextureRegion) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
diff --git a/src/utils/TestUtils.cpp b/src/utils/TestUtils.cpp
index cdbe3b3..c56cb8d 100644
--- a/src/utils/TestUtils.cpp
+++ b/src/utils/TestUtils.cpp
@@ -87,6 +87,14 @@
         }
     }
 
+    uint64_t GetTexelCountInCopyRegion(uint64_t bytesPerRow,
+                                       uint64_t rowsPerImage,
+                                       wgpu::Extent3D copyExtent,
+                                       wgpu::TextureFormat textureFormat) {
+        return RequiredBytesInCopy(bytesPerRow, rowsPerImage, copyExtent, textureFormat) /
+               utils::GetTexelBlockSizeInBytes(textureFormat);
+    }
+
     void UnalignDynamicUploader(wgpu::Device device) {
         std::vector<uint8_t> data = {1};
 
diff --git a/src/utils/TestUtils.h b/src/utils/TestUtils.h
index d1ba25f..c75ca64 100644
--- a/src/utils/TestUtils.h
+++ b/src/utils/TestUtils.h
@@ -46,6 +46,11 @@
                                  wgpu::Extent3D copyExtent,
                                  wgpu::TextureFormat textureFormat);
 
+    uint64_t GetTexelCountInCopyRegion(uint64_t bytesPerRow,
+                                       uint64_t rowsPerImage,
+                                       wgpu::Extent3D copyExtent,
+                                       wgpu::TextureFormat textureFormat);
+
     // A helper function used for testing DynamicUploader offset alignment.
     // A call of this function will do a Queue::WriteTexture with 1 byte of data,
     // so that assuming that WriteTexture uses DynamicUploader, the first RingBuffer