Support copying multiple array layers in one B2T and T2B copy command

This patch adds the support of copying with multiple texture array
layers in one buffer-to-texture and texture-to-buffer copy command.

BUG=dawn:453
TEST=dawn_end2end_tests

Change-Id: If009dbb29f2b0ef0667715eed0d66053b1491fd4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/23248
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index 4b98414..d447f3e 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -34,6 +34,7 @@
             uint64_t size;
             uint64_t offset;
             uint32_t bytesPerRow;
+            uint32_t rowsPerImage;
         };
 
         static std::vector<RGBA8> GetExpectedTextureData(
@@ -56,12 +57,14 @@
         }
 
         static BufferSpec MinimumBufferSpec(uint32_t width,
-                                            uint32_t height,
-                                            uint32_t arrayLayer = 1) {
+                                            uint32_t rowsPerImage,
+                                            uint32_t arrayLayer = 1,
+                                            bool testZeroRowsPerImage = true) {
             const uint32_t bytesPerRow = utils::GetMinimumBytesPerRow(kTextureFormat, width);
             const uint32_t totalBufferSize = utils::GetBytesInBufferTextureCopy(
-                kTextureFormat, width, bytesPerRow, height, arrayLayer);
-            return {totalBufferSize, 0, bytesPerRow};
+                kTextureFormat, width, bytesPerRow, rowsPerImage, arrayLayer);
+            uint32_t appliedRowsPerImage = testZeroRowsPerImage ? 0 : rowsPerImage;
+            return {totalBufferSize, 0, bytesPerRow, appliedRowsPerImage};
         }
 
         static void PackTextureData(const RGBA8* srcData, uint32_t width, uint32_t height, uint32_t srcTexelsPerRow, RGBA8* dstData, uint32_t dstTexelsPerRow) {
@@ -90,30 +93,24 @@
           descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
           wgpu::Texture texture = device.CreateTexture(&descriptor);
 
-          const uint32_t rowsPerImage = textureSpec.textureSize.height >> textureSpec.level;
           const utils::BufferTextureCopyLayout copyLayout =
               utils::GetBufferTextureCopyLayoutForTexture2DAtLevel(
-                  kTextureFormat, textureSpec.textureSize, textureSpec.level, rowsPerImage);
+                  kTextureFormat, textureSpec.textureSize, textureSpec.level,
+                  bufferSpec.rowsPerImage);
 
           wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
 
           // Initialize the source texture
           std::vector<RGBA8> textureArrayData = GetExpectedTextureData(copyLayout);
-
-          // TODO(jiawei.shao@intel.com): copy into multiple texture array layers in one
-          // buffer-to-texture copy command.
-          wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(
-              device, textureArrayData.data(), copyLayout.byteLength, wgpu::BufferUsage::CopySrc);
-          uint64_t uploadBufferOffset = 0;
-          for (uint32_t slice = 0; slice < textureSpec.textureSize.depth; ++slice) {
+          {
+              wgpu::Buffer uploadBuffer =
+                  utils::CreateBufferFromData(device, textureArrayData.data(),
+                                              copyLayout.byteLength, wgpu::BufferUsage::CopySrc);
               wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(
-                  uploadBuffer, uploadBufferOffset, copyLayout.bytesPerRow, 0);
+                  uploadBuffer, 0, copyLayout.bytesPerRow, bufferSpec.rowsPerImage);
               wgpu::TextureCopyView textureCopyView =
-                  utils::CreateTextureCopyView(texture, textureSpec.level, {0, 0, slice});
-              wgpu::Extent3D copyOneLayerSize = {copyLayout.mipSize.width,
-                                                 copyLayout.mipSize.height, 1};
-              encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyOneLayerSize);
-              uploadBufferOffset += copyLayout.bytesPerImage;
+                  utils::CreateTextureCopyView(texture, textureSpec.level, {0, 0, 0});
+              encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyLayout.mipSize);
           }
 
           // Create a buffer of `size` and populate it with empty data (0,0,0,0) Note:
@@ -128,31 +125,21 @@
               utils::CreateBufferFromData(device, emptyData.data(), bufferSpec.size,
                                           wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
 
-          const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depth;
-
-          // TODO(jiawei.shao@intel.com): copy from multiple texture array layers in one
-          // texture-to-buffer copy command.
-          uint64_t bufferOffset = bufferSpec.offset;
-          for (uint32_t slice = textureSpec.copyOrigin.z; slice < maxArrayLayer; ++slice) {
-              // Copy the region [(`x`, `y`, slice), (`x + testCopySize.width, `y +
-              // testCopySize.height`, 1)] from the `level` mip into the buffer at `offset +
-              // bufferSpec.size * slice` and `bytesPerRow`
-              wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(
-                  texture, textureSpec.level,
-                  {textureSpec.copyOrigin.x, textureSpec.copyOrigin.y, slice});
-              wgpu::BufferCopyView bufferCopyView =
-                  utils::CreateBufferCopyView(buffer, bufferOffset, bufferSpec.bytesPerRow, 0);
-              wgpu::Extent3D copyOneLayerSize = {copySize.width, copySize.height, 1};
-              encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, &copyOneLayerSize);
-              bufferOffset += copyLayout.bytesPerImage;
+          {
+              wgpu::TextureCopyView textureCopyView =
+                  utils::CreateTextureCopyView(texture, textureSpec.level, textureSpec.copyOrigin);
+              wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(
+                  buffer, bufferSpec.offset, bufferSpec.bytesPerRow, bufferSpec.rowsPerImage);
+              encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, &copySize);
           }
 
           wgpu::CommandBuffer commands = encoder.Finish();
           queue.Submit(1, &commands);
 
-          bufferOffset = bufferSpec.offset;
+          uint64_t bufferOffset = bufferSpec.offset;
           const uint32_t texelCountInCopyRegion =
               bufferSpec.bytesPerRow / bytesPerTexel * (copySize.height - 1) + copySize.width;
+          const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depth;
           std::vector<RGBA8> expected(texelCountInCopyRegion);
           for (uint32_t slice = textureSpec.copyOrigin.z; slice < maxArrayLayer; ++slice) {
               // Pack the data used to create the upload buffer in the specified copy region to have
@@ -218,34 +205,23 @@
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
 
-        const uint32_t rowsPerImage = textureSpec.textureSize.height >> textureSpec.level;
         const utils::BufferTextureCopyLayout copyLayout =
             utils::GetBufferTextureCopyLayoutForTexture2DAtLevel(
-                kTextureFormat, textureSpec.textureSize, textureSpec.level, rowsPerImage);
+                kTextureFormat, textureSpec.textureSize, textureSpec.level,
+                bufferSpec.rowsPerImage);
 
         const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depth;
 
-        // TODO(jiawei.shao@intel.com): support copying into multiple texture array layers in one
-        // copy command.
-        uint64_t bufferOffset = bufferSpec.offset;
-        for (uint32_t slice = textureSpec.copyOrigin.z; slice < maxArrayLayer; ++slice) {
-            // Copy to the region [(`x`, `y`, `slice`), (`x + testCopySize.width, `y +
-            // testCopySize.height`, 1] at the `level` mip
-            // from the buffer at the specified `offset` and `bytesPerRow`
-            wgpu::BufferCopyView bufferCopyView =
-                utils::CreateBufferCopyView(buffer, bufferOffset, bufferSpec.bytesPerRow, 0);
-            wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(
-                texture, textureSpec.level,
-                {textureSpec.copyOrigin.x, textureSpec.copyOrigin.y, slice});
-            wgpu::Extent3D copyOneLayerSize = {copySize.width, copySize.height, 1};
-            encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyOneLayerSize);
-            bufferOffset += copyLayout.bytesPerImage;
-        }
+        wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(
+            buffer, bufferSpec.offset, bufferSpec.bytesPerRow, bufferSpec.rowsPerImage);
+        wgpu::TextureCopyView textureCopyView =
+            utils::CreateTextureCopyView(texture, textureSpec.level, textureSpec.copyOrigin);
+        encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copySize);
 
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
-        bufferOffset = bufferSpec.offset;
+        uint64_t bufferOffset = bufferSpec.offset;
         const uint32_t texelCountLastLayer =
             copyLayout.texelBlocksPerRow * (copyLayout.mipSize.height - 1) +
             copyLayout.mipSize.width;
@@ -305,30 +281,21 @@
 
         // Create an upload buffer and use it to populate the current slice of the texture in
         // `level` mip level
-        const uint32_t rowsPerImage = srcSpec.textureSize.height >> srcSpec.level;
         const utils::BufferTextureCopyLayout copyLayout =
             utils::GetBufferTextureCopyLayoutForTexture2DAtLevel(
                 kTextureFormat,
                 {srcSpec.textureSize.width, srcSpec.textureSize.height, copySize.depth},
-                srcSpec.level, rowsPerImage);
+                srcSpec.level, 0);
 
         const std::vector<RGBA8> textureArrayCopyData = GetExpectedTextureData(copyLayout);
 
-        // TODO(jiawei.shao@intel.com): support copying into multiple contiguous array layers in one
-        // copyBufferToTexture() call.
         wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(
             device, textureArrayCopyData.data(), copyLayout.byteLength, wgpu::BufferUsage::CopySrc);
-        uint64_t uploadBufferOffset = 0;
-        for (uint32_t slice = 0; slice < copySize.depth; ++slice) {
-            wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(
-                uploadBuffer, uploadBufferOffset, copyLayout.bytesPerRow, 0);
-            wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(
-                srcTexture, srcSpec.level, {0, 0, srcSpec.copyOrigin.z + slice});
-            wgpu::Extent3D copyOneLayerSize = {copyLayout.mipSize.width, copyLayout.mipSize.height,
-                                               1};
-            encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyOneLayerSize);
-            uploadBufferOffset += copyLayout.bytesPerImage;
-        }
+        wgpu::BufferCopyView bufferCopyView =
+            utils::CreateBufferCopyView(uploadBuffer, 0, copyLayout.bytesPerRow, 0);
+        wgpu::TextureCopyView textureCopyView =
+            utils::CreateTextureCopyView(srcTexture, srcSpec.level, {0, 0, srcSpec.copyOrigin.z});
+        encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyLayout.mipSize);
 
         // Perform the texture to texture copy
         wgpu::TextureCopyView srcTextureCopyView =
@@ -700,8 +667,12 @@
     }
 }
 
-// Test that copying regions of each texture 2D array layer works
+// Test that copying whole texture 2D array layers in one texture-to-buffer-copy works.
 TEST_P(CopyTests_T2B, Texture2DArrayRegion) {
+    // TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layer fail
+    // with swiftshader.
+    DAWN_SKIP_TEST_IF(IsSwiftshader());
+
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
@@ -714,8 +685,12 @@
     DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kLayers), {kWidth, kHeight, kLayers});
 }
 
-// Test that copying a sub-region of each texture 2D array layer works
+// Test that copying a range of texture 2D array layers in one texture-to-buffer-copy works.
 TEST_P(CopyTests_T2B, Texture2DArraySubRegion) {
+    // TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layer fail
+    // with swiftshader.
+    DAWN_SKIP_TEST_IF(IsSwiftshader());
+
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
@@ -733,6 +708,10 @@
 
 // Test that copying texture 2D array mips with 256-byte aligned sizes works
 TEST_P(CopyTests_T2B, Texture2DArrayMip) {
+    // TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layers fail
+    // with swiftshader.
+    DAWN_SKIP_TEST_IF(IsSwiftshader());
+
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
@@ -750,6 +729,31 @@
     }
 }
 
+// Test that copying from a range of texture 2D array layers in one texture-to-buffer-copy when
+// RowsPerImage is not equal to the height of the texture works.
+TEST_P(CopyTests_T2B, Texture2DArrayRegionNonzeroRowsPerImage) {
+    // TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layers fail
+    // with swiftshader.
+    DAWN_SKIP_TEST_IF(IsSwiftshader());
+
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    constexpr uint32_t kLayers = 6u;
+    constexpr uint32_t kBaseLayer = 2u;
+    constexpr uint32_t kCopyLayers = 3u;
+
+    constexpr uint32_t kRowsPerImage = kHeight * 2;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, kBaseLayer};
+    textureSpec.textureSize = {kWidth, kHeight, kLayers};
+    textureSpec.level = 0;
+
+    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
+    bufferSpec.rowsPerImage = kRowsPerImage;
+    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
+}
+
 DAWN_INSTANTIATE_TEST(CopyTests_T2B, D3D12Backend(), MetalBackend(), OpenGLBackend(), VulkanBackend());
 
 // Test that copying an entire texture with 256-byte aligned dimensions works
@@ -1038,8 +1042,12 @@
     }
 }
 
-// Test that copying into regions of each texture 2D array layer works
+// Test that copying whole texture 2D array layers in one texture-to-buffer-copy works.
 TEST_P(CopyTests_B2T, Texture2DArrayRegion) {
+    // TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layers fail
+    // with swiftshader.
+    DAWN_SKIP_TEST_IF(IsSwiftshader());
+
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
@@ -1052,8 +1060,12 @@
     DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kLayers), {kWidth, kHeight, kLayers});
 }
 
-// Test that copying into a sub-region of each texture 2D array layer works
+// Test that copying a range of texture 2D array layers in one texture-to-buffer-copy works.
 TEST_P(CopyTests_B2T, Texture2DArraySubRegion) {
+    // TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layers fail
+    // with swiftshader.
+    DAWN_SKIP_TEST_IF(IsSwiftshader());
+
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
@@ -1069,6 +1081,31 @@
            {kWidth, kHeight, kCopyLayers});
 }
 
+// Test that copying into a range of texture 2D array layers in one texture-to-buffer-copy when
+// RowsPerImage is not equal to the height of the texture works.
+TEST_P(CopyTests_B2T, Texture2DArrayRegionNonzeroRowsPerImage) {
+    // TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layers fail
+    // with swiftshader.
+    DAWN_SKIP_TEST_IF(IsSwiftshader());
+
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    constexpr uint32_t kLayers = 6u;
+    constexpr uint32_t kBaseLayer = 2u;
+    constexpr uint32_t kCopyLayers = 3u;
+
+    constexpr uint32_t kRowsPerImage = kHeight * 2;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, kBaseLayer};
+    textureSpec.textureSize = {kWidth, kHeight, kLayers};
+    textureSpec.level = 0;
+
+    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
+    bufferSpec.rowsPerImage = kRowsPerImage;
+    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
+}
+
 DAWN_INSTANTIATE_TEST(CopyTests_B2T, D3D12Backend(), MetalBackend(), OpenGLBackend(), VulkanBackend());
 
 TEST_P(CopyTests_T2T, Texture) {