D3D12: Allow relaxed B2T copy pitch and offset alignment on 3D textures

This patch implements the buffer-texture copy with relaxed copy pitch
and offset alignment (not required to be a multiple of 512 or 256) on
3D textures when `UnrestrictedBufferTextureCopyPitchSupported` is true
on the current D3D12 device so that we just need to implement such copy
with at most two buffer-texture copies.

Bug: chromium:381000081
Test: dawn_end2end_tests
Change-Id: Id438da4ca209d742ae3243b53fc08c118fc82f46
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/216875
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/src/dawn/native/d3d12/TextureCopySplitter.cpp b/src/dawn/native/d3d12/TextureCopySplitter.cpp
index 9c80fc4..016ec8f 100644
--- a/src/dawn/native/d3d12/TextureCopySplitter.cpp
+++ b/src/dawn/native/d3d12/TextureCopySplitter.cpp
@@ -718,4 +718,97 @@
 
     return copy;
 }
+
+TextureCopySubresource Compute3DTextureCopySubresourceWithRelaxedRowPitchAndOffset(
+    BufferTextureCopyDirection direction,
+    Origin3D origin,
+    Extent3D copySize,
+    const TexelBlockInfo& blockInfo,
+    uint64_t offset,
+    uint32_t bytesPerRow,
+    uint32_t rowsPerImage) {
+    TextureCopySubresource copy;
+
+    Origin3D bufferOffset = {0, 0, 0};
+
+    // You can visualize the data in the buffer (bufferLocation) like the inline comments.
+    // * copy data is visualized as '+'.
+    uint32_t depthInCopy1 = copySize.depthOrArrayLayers - 1;
+    if (depthInCopy1 > 0) {
+        // `bufferLocation` in the 1st copy (first `depthInCopy1` images, optional):
+        //
+        //                bufferOffset(0, 0, 0)
+        //                        ^
+        //                        |
+        // |<-------Offset1------>|<-----------RowPitch----------->|----------|------------|
+        // |----------------------|++++++++++++++++++++++~~~~~~~~~~|    |     |     |      |
+        //                        |++++++++++++++++++++++~~~~~~~~~~|CopyHeight|     |      |
+        //                        |++++++++++++++++++++++~~~~~~~~~~|    |     |RowsPerImage|
+        //                        |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|----------|     |      |
+        //                        |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|          |     |      |
+        // |---End of 1st image-->|--------------------------------|----------|------------|
+        //                        |++++++++++++++++++++++~~~~~~~~~~|          |     |      |
+        //                        |++++++++++++++++++++++~~~~~~~~~~|          |     |      |
+        //                        |++++++++++++++++++++++~~~~~~~~~~|          |RowsPerImage|
+        //                        |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|          |     |      |
+        //                        |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|          |     |      |
+        // |---End of 2nd image-->|--------------------------------|----------|------------|
+        //                        |<-----CopyWidth------>|
+        //
+
+        Origin3D textureOffset1 = origin;
+        uint32_t offset1 = offset;
+
+        uint32_t rowsPerImage1 = rowsPerImage;
+
+        auto* copyInfo1 = copy.AddCopy();
+        Extent3D copySize1 = {copySize.width, copySize.height, depthInCopy1};
+        ComputeSourceRegionForCopyInfo(copyInfo1, direction, bufferOffset, textureOffset1,
+                                       copySize1);
+
+        Extent3D bufferSize1 = {copySize.width, rowsPerImage1, depthInCopy1};
+
+        FillFootprintAndOffsetOfBufferLocation(&copyInfo1->bufferLocation, offset1, bufferSize1,
+                                               bytesPerRow);
+    }
+
+    {
+        // We have to use the 2nd copy because there may not be enough memory to hold
+        // (RowPitch * RowsPerImage) data for the last image in the buffer.
+        //
+        // `bufferLocation` in the 2nd copy (the last image):
+        //
+        //                bufferOffset (0, 0, 0)
+        //                Begin of the last image
+        //                        ^
+        //                        |
+        // |<-------Offset2------>|<-----------RowPitch----------->|----------|
+        // |----------------------|++++++++++++++++++++++~~~~~~~~~~|    |     |
+        //                        |++++++++++++++++++++++~~~~~~~~~~|CopyHeight|
+        //                        |++++++++++++++++++++++|         |    |     |
+        //                        |----------------------|---------|----------|
+        //                        |<-----CopyWidth------>|
+        //                                               ^
+        //                                     End of all buffer data
+        //
+        DAWN_ASSERT(copySize.depthOrArrayLayers >= 1);
+        Origin3D textureOffset2 = {origin.x, origin.y, origin.z + depthInCopy1};
+        uint32_t offset2 = offset + bytesPerRow * rowsPerImage * depthInCopy1;
+        uint32_t depthInCopy2 = 1;
+        uint32_t rowsPerImage2 = copySize.height;
+
+        auto* copyInfo2 = copy.AddCopy();
+        Extent3D copySize2 = {copySize.width, copySize.height, depthInCopy2};
+        ComputeSourceRegionForCopyInfo(copyInfo2, direction, bufferOffset, textureOffset2,
+                                       copySize2);
+
+        Extent3D bufferSize2 = {copySize.width, rowsPerImage2, depthInCopy2};
+
+        FillFootprintAndOffsetOfBufferLocation(&copyInfo2->bufferLocation, offset2, bufferSize2,
+                                               bytesPerRow);
+    }
+
+    return copy;
+}
+
 }  // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/TextureCopySplitter.h b/src/dawn/native/d3d12/TextureCopySplitter.h
index 5c6939d..18e9f48 100644
--- a/src/dawn/native/d3d12/TextureCopySplitter.h
+++ b/src/dawn/native/d3d12/TextureCopySplitter.h
@@ -128,6 +128,17 @@
     uint64_t offset,
     uint32_t bytesPerRow);
 
+// Compute the `TextureCopySubresource` for one subresource of a 3D texture with relaxed row pitch
+// and offset.
+TextureCopySubresource Compute3DTextureCopySubresourceWithRelaxedRowPitchAndOffset(
+    BufferTextureCopyDirection direction,
+    Origin3D origin,
+    Extent3D copySize,
+    const TexelBlockInfo& blockInfo,
+    uint64_t offset,
+    uint32_t bytesPerRow,
+    uint32_t rowsPerImage);
+
 }  // namespace dawn::native::d3d12
 
 #endif  // SRC_DAWN_NATIVE_D3D12_TEXTURECOPYSPLITTER_H_
diff --git a/src/dawn/native/d3d12/UtilsD3D12.cpp b/src/dawn/native/d3d12/UtilsD3D12.cpp
index 50602da..f79591a 100644
--- a/src/dawn/native/d3d12/UtilsD3D12.cpp
+++ b/src/dawn/native/d3d12/UtilsD3D12.cpp
@@ -312,11 +312,17 @@
             break;
 
         case wgpu::TextureDimension::e3D: {
-            // See comments in Compute3DTextureCopySplits() for more details.
-            TextureCopySubresource copyRegions =
-                Compute3DTextureCopySplits(direction, textureCopy.origin, copySize, blockInfo,
-                                           offset, bytesPerRow, rowsPerImage);
-
+            TextureCopySubresource copyRegions;
+            if (useRelaxedRowPitchAndOffset) {
+                copyRegions = Compute3DTextureCopySubresourceWithRelaxedRowPitchAndOffset(
+                    direction, textureCopy.origin, copySize, blockInfo, offset, bytesPerRow,
+                    rowsPerImage);
+            } else {
+                // See comments in Compute3DTextureCopySplits() for more details.
+                copyRegions =
+                    Compute3DTextureCopySplits(direction, textureCopy.origin, copySize, blockInfo,
+                                               offset, bytesPerRow, rowsPerImage);
+            }
             RecordBufferTextureCopyFromSplits(direction, commandList, copyRegions, bufferResource,
                                               0, bytesPerRow, texture, textureCopy.mipLevel, 0,
                                               textureCopy.aspect);