Implement 3D texture copy on D3D12: T2B and T2T

This change implements 3D texture copy on D3D12 for texture to
buffer and texture to texture copy for full copy upon the entire
3D texture.

It also uses a function named CopyBufferToTexture to wrap
CopyBufferTo3DTexture and CopyBufferTo2DTextureWithCopySplits.
Likewise, it uses another function named CopyTextureToBuffer to
wrap Copy3DTextureToBuffer and Copy2DTextureToBufferWithCopySplits.

BUG: dawn:547

Change-Id: I4293f6ca4d37f604e6f1c10827686d17469a07ca
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/46820
Commit-Queue: Yunchao He <yunchao.he@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index a483b42..8d0056a 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -178,15 +178,15 @@
             bufferCopy.offset = 0;
             bufferCopy.bytesPerRow = bytesPerRow;
             bufferCopy.rowsPerImage = rowsPerImage;
-            Copy2DTextureToBufferWithCopySplit(recordingContext->GetCommandList(), srcCopy,
-                                               bufferCopy, srcTexture, tempBuffer.Get(), copySize);
+            RecordCopyTextureToBuffer(recordingContext->GetCommandList(), srcCopy, bufferCopy,
+                                      srcTexture, tempBuffer.Get(), copySize);
 
             // Copy from tempBuffer into destination texture
             tempBuffer->TrackUsageAndTransitionNow(recordingContext, wgpu::BufferUsage::CopySrc);
             Texture* dstTexture = ToBackend(dstCopy.texture).Get();
-            CopyBufferTo2DTextureWithCopySplit(recordingContext, dstCopy,
-                                               tempBuffer->GetD3D12Resource(), 0, bytesPerRow,
-                                               rowsPerImage, copySize, dstTexture, dstCopy.aspect);
+            RecordCopyBufferToTexture(recordingContext, dstCopy, tempBuffer->GetD3D12Resource(), 0,
+                                      bytesPerRow, rowsPerImage, copySize, dstTexture,
+                                      dstCopy.aspect);
 
             // Save tempBuffer into recordingContext
             recordingContext->AddToTempBuffers(std::move(tempBuffer));
@@ -756,22 +756,10 @@
                     texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst,
                                                         subresources);
 
-                    // Record the CopyTextureRegion commands for 3D textures. Multiple depths of 3D
-                    // textures can be copied in one shot and copySplits are not needed.
-                    if (texture->GetDimension() == wgpu::TextureDimension::e3D) {
-                        CopyBufferTo3DTexture(commandContext, copy->destination,
+                    RecordCopyBufferToTexture(commandContext, copy->destination,
                                               buffer->GetD3D12Resource(), copy->source.offset,
                                               copy->source.bytesPerRow, copy->source.rowsPerImage,
                                               copy->copySize, texture, subresources.aspects);
-                    } else {
-                        // Compute the copySplits and record the CopyTextureRegion commands for 2D
-                        // textures.
-                        CopyBufferTo2DTextureWithCopySplit(
-                            commandContext, copy->destination, buffer->GetD3D12Resource(),
-                            copy->source.offset, copy->source.bytesPerRow,
-                            copy->source.rowsPerImage, copy->copySize, texture,
-                            subresources.aspects);
-                    }
 
                     break;
                 }
@@ -793,14 +781,8 @@
                                                         subresources);
                     buffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopyDst);
 
-                    if (texture->GetDimension() == wgpu::TextureDimension::e3D) {
-                        Copy3DTextureToBuffer(commandList, copy->source, copy->destination, texture,
+                    RecordCopyTextureToBuffer(commandList, copy->source, copy->destination, texture,
                                               buffer, copy->copySize);
-                    } else {
-                        Copy2DTextureToBufferWithCopySplit(commandList, copy->source,
-                                                           copy->destination, texture, buffer,
-                                                           copy->copySize);
-                    }
 
                     break;
                 }
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 2c09181..fc56302 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -405,7 +405,7 @@
         CommandRecordingContext* commandContext;
         DAWN_TRY_ASSIGN(commandContext, GetPendingCommandContext());
         Texture* texture = ToBackend(dst->texture.Get());
-        ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
+        ASSERT(texture->GetDimension() != wgpu::TextureDimension::e1D);
 
         SubresourceRange range = GetSubresourcesAffectedByCopy(*dst, copySizePixels);
 
@@ -417,10 +417,9 @@
 
         texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst, range);
 
-        // compute the copySplits and record the CopyTextureRegion commands
-        CopyBufferTo2DTextureWithCopySplit(commandContext, *dst, ToBackend(source)->GetResource(),
-                                           src.offset, src.bytesPerRow, src.rowsPerImage,
-                                           copySizePixels, texture, range.aspects);
+        RecordCopyBufferToTexture(commandContext, *dst, ToBackend(source)->GetResource(),
+                                  src.offset, src.bytesPerRow, src.rowsPerImage, copySizePixels,
+                                  texture, range.aspects);
 
         return {};
     }
diff --git a/src/dawn_native/d3d12/UtilsD3D12.cpp b/src/dawn_native/d3d12/UtilsD3D12.cpp
index 4aea0ff..b190210 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.cpp
+++ b/src/dawn_native/d3d12/UtilsD3D12.cpp
@@ -237,6 +237,29 @@
             bytesPerRow, texture, textureCopy.mipLevel, textureCopy.origin.z, aspect);
     }
 
+    void RecordCopyBufferToTexture(CommandRecordingContext* commandContext,
+                                   const TextureCopy& textureCopy,
+                                   ID3D12Resource* bufferResource,
+                                   const uint64_t offset,
+                                   const uint32_t bytesPerRow,
+                                   const uint32_t rowsPerImage,
+                                   const Extent3D& copySize,
+                                   Texture* texture,
+                                   Aspect aspect) {
+        // Record the CopyTextureRegion commands for 3D textures. Multiple depths of 3D
+        // textures can be copied in one shot and copySplits are not needed.
+        if (texture->GetDimension() == wgpu::TextureDimension::e3D) {
+            CopyBufferTo3DTexture(commandContext, textureCopy, bufferResource, offset, bytesPerRow,
+                                  rowsPerImage, copySize, texture, aspect);
+        } else {
+            // Compute the copySplits and record the CopyTextureRegion commands for 2D
+            // textures.
+            CopyBufferTo2DTextureWithCopySplit(commandContext, textureCopy, bufferResource, offset,
+                                               bytesPerRow, rowsPerImage, copySize, texture,
+                                               aspect);
+        }
+    }
+
     void RecordCopyTextureToBufferFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
                                                        const Texture2DCopySplit& baseCopySplit,
                                                        Buffer* buffer,
@@ -331,4 +354,18 @@
             textureCopy.mipLevel, textureCopy.origin.z, textureCopy.aspect);
     }
 
+    void RecordCopyTextureToBuffer(ID3D12GraphicsCommandList* commandList,
+                                   const TextureCopy& textureCopy,
+                                   const BufferCopy& bufferCopy,
+                                   Texture* texture,
+                                   Buffer* buffer,
+                                   const Extent3D& copySize) {
+        if (texture->GetDimension() == wgpu::TextureDimension::e3D) {
+            Copy3DTextureToBuffer(commandList, textureCopy, bufferCopy, texture, buffer, copySize);
+        } else {
+            Copy2DTextureToBufferWithCopySplit(commandList, textureCopy, bufferCopy, texture,
+                                               buffer, copySize);
+        }
+    }
+
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/UtilsD3D12.h b/src/dawn_native/d3d12/UtilsD3D12.h
index fd9be66..719a19a 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.h
+++ b/src/dawn_native/d3d12/UtilsD3D12.h
@@ -54,25 +54,15 @@
                                                        uint32_t textureSlice,
                                                        Aspect aspect);
 
-    void CopyBufferTo2DTextureWithCopySplit(CommandRecordingContext* commandContext,
-                                            const TextureCopy& textureCopy,
-                                            ID3D12Resource* bufferResource,
-                                            const uint64_t offset,
-                                            const uint32_t bytesPerRow,
-                                            const uint32_t rowsPerImage,
-                                            const Extent3D& copySize,
-                                            Texture* texture,
-                                            Aspect aspect);
-
-    void CopyBufferTo3DTexture(CommandRecordingContext* commandContext,
-                               const TextureCopy& textureCopy,
-                               ID3D12Resource* bufferResource,
-                               const uint64_t offset,
-                               const uint32_t bytesPerRow,
-                               const uint32_t rowsPerImage,
-                               const Extent3D& copySize,
-                               Texture* texture,
-                               Aspect aspect);
+    void RecordCopyBufferToTexture(CommandRecordingContext* commandContext,
+                                   const TextureCopy& textureCopy,
+                                   ID3D12Resource* bufferResource,
+                                   const uint64_t offset,
+                                   const uint32_t bytesPerRow,
+                                   const uint32_t rowsPerImage,
+                                   const Extent3D& copySize,
+                                   Texture* texture,
+                                   Aspect aspect);
 
     void RecordCopyTextureToBufferFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
                                                        const Texture2DCopySplit& baseCopySplit,
@@ -84,19 +74,12 @@
                                                        uint32_t textureSlice,
                                                        Aspect aspect);
 
-    void Copy2DTextureToBufferWithCopySplit(ID3D12GraphicsCommandList* commandList,
-                                            const TextureCopy& textureCopy,
-                                            const BufferCopy& bufferCopy,
-                                            Texture* texture,
-                                            Buffer* buffer,
-                                            const Extent3D& copySize);
-
-    void Copy3DTextureToBuffer(ID3D12GraphicsCommandList* commandList,
-                               const TextureCopy& textureCopy,
-                               const BufferCopy& bufferCopy,
-                               Texture* texture,
-                               Buffer* buffer,
-                               const Extent3D& copySize);
+    void RecordCopyTextureToBuffer(ID3D12GraphicsCommandList* commandList,
+                                   const TextureCopy& textureCopy,
+                                   const BufferCopy& bufferCopy,
+                                   Texture* texture,
+                                   Buffer* buffer,
+                                   const Extent3D& copySize);
 
 }}  // namespace dawn_native::d3d12
 
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index 92230d6..1903652 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -130,14 +130,15 @@
   protected:
     void DoTest(const TextureSpec& textureSpec,
                 const BufferSpec& bufferSpec,
-                const wgpu::Extent3D& copySize) {
+                const wgpu::Extent3D& copySize,
+                wgpu::TextureDimension dimension = wgpu::TextureDimension::e2D) {
         // TODO(jiawei.shao@intel.com): support testing arbitrary formats
         ASSERT_EQ(kDefaultFormat, textureSpec.format);
 
         const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
         // Create a texture that is `width` x `height` with (`level` + 1) mip levels.
         wgpu::TextureDescriptor descriptor;
-        descriptor.dimension = wgpu::TextureDimension::e2D;
+        descriptor.dimension = dimension;
         descriptor.size = textureSpec.textureSize;
         descriptor.sampleCount = 1;
         descriptor.format = textureSpec.format;
@@ -186,11 +187,18 @@
 
         uint64_t bufferOffset = bufferSpec.offset;
 
-        const wgpu::Extent3D copySizePerSlice = {copySize.width, copySize.height, 1};
+        uint32_t copyLayer = copySize.depthOrArrayLayers;
+        uint32_t copyDepth = 1;
+        if (dimension == wgpu::TextureDimension::e3D) {
+            copyLayer = 1;
+            copyDepth = copySize.depthOrArrayLayers;
+        }
+
+        const wgpu::Extent3D copySizePerLayer = {copySize.width, copySize.height, copyDepth};
         // Texels in single slice.
         const uint32_t texelCountInCopyRegion = utils::GetTexelCountInCopyRegion(
-            bufferSpec.bytesPerRow, bufferSpec.rowsPerImage, copySizePerSlice, textureSpec.format);
-        const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depthOrArrayLayers;
+            bufferSpec.bytesPerRow, bufferSpec.rowsPerImage, copySizePerLayer, textureSpec.format);
+        const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copyLayer;
         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
@@ -203,7 +211,7 @@
 
             PackTextureData(bytesPerTexel,
                             textureArrayData.data() + expectedTexelArrayDataStartIndex,
-                            copySize.width, copySize.height, 1, copyLayout.bytesPerRow,
+                            copySize.width, copySize.height, copyDepth, copyLayout.bytesPerRow,
                             expected.data(), bufferSpec.bytesPerRow);
 
             EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<const uint32_t*>(expected.data()), buffer,
@@ -314,12 +322,13 @@
     void DoTest(const TextureSpec& srcSpec,
                 const TextureSpec& dstSpec,
                 const wgpu::Extent3D& copySize,
-                bool copyWithinSameTexture = false) {
+                bool copyWithinSameTexture = false,
+                wgpu::TextureDimension dimension = wgpu::TextureDimension::e2D) {
         ASSERT_EQ(srcSpec.format, dstSpec.format);
         const wgpu::TextureFormat format = srcSpec.format;
 
         wgpu::TextureDescriptor srcDescriptor;
-        srcDescriptor.dimension = wgpu::TextureDimension::e2D;
+        srcDescriptor.dimension = dimension;
         srcDescriptor.size = srcSpec.textureSize;
         srcDescriptor.sampleCount = 1;
         srcDescriptor.format = format;
@@ -332,7 +341,7 @@
             dstTexture = srcTexture;
         } else {
             wgpu::TextureDescriptor dstDescriptor;
-            dstDescriptor.dimension = wgpu::TextureDimension::e2D;
+            dstDescriptor.dimension = dimension;
             dstDescriptor.size = dstSpec.textureSize;
             dstDescriptor.sampleCount = 1;
             dstDescriptor.format = format;
@@ -393,14 +402,21 @@
         // Validate if the data in outputBuffer is what we expected, including the untouched data
         // outside of the copy.
         {
+            uint32_t copyLayer = copySize.depthOrArrayLayers;
+            uint32_t copyDepth = 1;
+            if (dimension == wgpu::TextureDimension::e3D) {
+                copyLayer = 1;
+                copyDepth = copySize.depthOrArrayLayers;
+            }
+
             const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(format);
             const uint64_t validDataSizePerDstTextureLayer = utils::RequiredBytesInCopy(
                 dstDataCopyLayout.bytesPerRow, dstDataCopyLayout.mipSize.height,
-                dstDataCopyLayout.mipSize.width, dstDataCopyLayout.mipSize.height, 1,
+                dstDataCopyLayout.mipSize.width, dstDataCopyLayout.mipSize.height, copyDepth,
                 bytesPerTexel);
 
             std::vector<uint8_t> expectedDstDataPerSlice(validDataSizePerDstTextureLayer);
-            for (uint32_t slice = 0; slice < copySize.depthOrArrayLayers; ++slice) {
+            for (uint32_t slice = 0; slice < copyLayer; ++slice) {
                 // For each source texture array slice involved in the copy, emulate the T2T copy
                 // on the CPU side by "copying" the copy data from the "source texture"
                 // (srcTextureCopyData) to the "destination texture" (expectedDstDataPerSlice).
@@ -419,10 +435,10 @@
                      dstSpec.copyOrigin.y * dstDataCopyLayout.mipSize.width) *
                     bytesPerTexel;
                 // Do the T2T "copy" on the CPU side to get the expected texel value at the
-                PackTextureData(bytesPerTexel, &srcTextureCopyData[srcTexelDataOffset],
-                                copySize.width, copySize.height, 1, srcDataCopyLayout.bytesPerRow,
-                                &expectedDstDataPerSlice[expectedDstDataOffset],
-                                dstDataCopyLayout.bytesPerRow);
+                PackTextureData(
+                    bytesPerTexel, &srcTextureCopyData[srcTexelDataOffset], copySize.width,
+                    copySize.height, copyDepth, srcDataCopyLayout.bytesPerRow,
+                    &expectedDstDataPerSlice[expectedDstDataOffset], dstDataCopyLayout.bytesPerRow);
 
                 // Compare the content of the destination texture at the (dstSpec.copyOrigin.z +
                 // slice)-th layer to its expected data after the copy (the outputBuffer contains
@@ -820,7 +836,7 @@
 }
 
 // Test that copying whole texture 2D array layers in one texture-to-buffer-copy works.
-TEST_P(CopyTests_T2B, Texture2DArrayRegion) {
+TEST_P(CopyTests_T2B, Texture2DArrayFull) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
@@ -930,7 +946,24 @@
     DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
 }
 
-// TODO(yunchao.he@intel.com): add T2B tests for 3D textures, like entire texture copy, RowPitch,
+// Test that copying whole 3D texture in one texture-to-buffer-copy works.
+TEST_P(CopyTests_T2B, Texture3DFull) {
+    // TODO(yunchao.he@intel.com): implement 3D texture copy on Vulkan, Metal, OpenGL and OpenGLES
+    // backend.
+    DAWN_SKIP_TEST_IF(IsVulkan() || IsMetal() || IsOpenGL() || IsOpenGLES());
+
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    constexpr uint32_t kDepth = 6u;
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, kDepth};
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kDepth), {kWidth, kHeight, kDepth},
+           wgpu::TextureDimension::e3D);
+}
+
+// TODO(yunchao.he@intel.com): add T2B tests for 3D textures, like RowPitch,
 // RowsPerImage, buffer offset, partial depth range, non-zero level, etc.
 
 DAWN_INSTANTIATE_TEST(CopyTests_T2B,
@@ -1279,7 +1312,7 @@
 }
 
 // Test that copying whole texture 2D array layers in one texture-to-buffer-copy works.
-TEST_P(CopyTests_B2T, Texture2DArrayRegion) {
+TEST_P(CopyTests_B2T, Texture2DArrayFull) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
@@ -1370,20 +1403,20 @@
     DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
 }
 
-// Test that copying whole texture 3D in one texture-to-buffer-copy works.
-TEST_P(CopyTests_B2T, Texture3DRegion) {
+// Test that copying whole texture 3D in one buffer-to-texture-copy works.
+TEST_P(CopyTests_B2T, Texture3DFull) {
     // TODO(yunchao.he@intel.com): implement 3D texture copy on Vulkan, Metal, OpenGL and OpenGLES
     // backend.
     DAWN_SKIP_TEST_IF(IsVulkan() || IsMetal() || IsOpenGL() || IsOpenGLES());
 
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
-    constexpr uint32_t kLayers = 6u;
+    constexpr uint32_t kDepth = 6u;
 
     TextureSpec textureSpec;
-    textureSpec.textureSize = {kWidth, kHeight, kLayers};
+    textureSpec.textureSize = {kWidth, kHeight, kDepth};
 
-    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kLayers), {kWidth, kHeight, kLayers},
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kDepth), {kWidth, kHeight, kDepth},
            wgpu::TextureDimension::e3D);
 }
 
@@ -1434,7 +1467,7 @@
 }
 
 // Test copying the whole 2D array texture.
-TEST_P(CopyTests_T2T, Texture2DArray) {
+TEST_P(CopyTests_T2T, Texture2DArrayFull) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
@@ -1700,7 +1733,23 @@
     }
 }
 
-// TODO(yunchao.he@intel.com): add T2T tests for 3D textures, like entire texture copy, RowPitch,
+// Test that copying whole 3D texture in one texture-to-texture-copy works.
+TEST_P(CopyTests_T2T, Texture3DFull) {
+    // TODO(yunchao.he@intel.com): implement 3D texture copy on Vulkan, Metal, OpenGL and OpenGLES
+    // backend.
+    DAWN_SKIP_TEST_IF(IsVulkan() || IsMetal() || IsOpenGL() || IsOpenGLES());
+
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    constexpr uint32_t kDepth = 6u;
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, kDepth};
+
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, kDepth}, false, wgpu::TextureDimension::e3D);
+}
+
+// TODO(yunchao.he@intel.com): add T2T tests for 3D textures, like RowPitch,
 // RowsPerImage, buffer offset, partial depth range, non-zero level, etc.
 
 DAWN_INSTANTIATE_TEST(