Implement 3D texture copy on D3D12 backend: copy to entire 3DTexture

This is the first patch to implement 3D texture copy. It starts with
implementation for 3D texture copy on D3D12 backend with the simplest
case: copy to the entire 3D texture. And texture's width is aligned
with 256 bytes.

The implementation for 3d texture copy might be inaccurate/incorrect
in some functions for complicated cases. But don't panic. The previous
implementation is also incorrect because many functions assumes that
we are copying to/from 2D textures only. And I will incrementally fix
the incorrect functions via upcoming tests for 3d texture copy.

BUG: dawn:547

Change-Id: I588b09fc8d0f0398e0798573415ba3a6a3f576fc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/45980
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Yunchao He <yunchao.he@intel.com>
diff --git a/src/dawn_native/CommandBuffer.cpp b/src/dawn_native/CommandBuffer.cpp
index 9fd1a99..f02a20f 100644
--- a/src/dawn_native/CommandBuffer.cpp
+++ b/src/dawn_native/CommandBuffer.cpp
@@ -66,11 +66,16 @@
                                        const uint32_t mipLevel) {
         Extent3D extent = texture->GetMipLevelPhysicalSize(mipLevel);
 
-        ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
-        if (extent.width == copySize.width && extent.height == copySize.height) {
-            return true;
+        ASSERT(texture->GetDimension() != wgpu::TextureDimension::e1D);
+        switch (texture->GetDimension()) {
+            case wgpu::TextureDimension::e2D:
+                return extent.width == copySize.width && extent.height == copySize.height;
+            case wgpu::TextureDimension::e3D:
+                return extent.width == copySize.width && extent.height == copySize.height &&
+                       extent.depthOrArrayLayers == copySize.depthOrArrayLayers;
+            default:
+                UNREACHABLE();
         }
-        return false;
     }
 
     SubresourceRange GetSubresourcesAffectedByCopy(const TextureCopy& copy,
@@ -79,6 +84,8 @@
             case wgpu::TextureDimension::e2D:
                 return {
                     copy.aspect, {copy.origin.z, copySize.depthOrArrayLayers}, {copy.mipLevel, 1}};
+            case wgpu::TextureDimension::e3D:
+                return {copy.aspect, {0, 1}, {copy.mipLevel, 1}};
             default:
                 UNREACHABLE();
         }
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 5fb9a4d..8d06963 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -648,7 +648,7 @@
                 // because in the latter we divide copyExtent.width by blockWidth and
                 // copyExtent.height by blockHeight while the divisibility conditions are
                 // checked in validating texture copy range.
-                DAWN_TRY(ValidateTextureCopyRange(*destination, fixedCopySize));
+                DAWN_TRY(ValidateTextureCopyRange(GetDevice(), *destination, fixedCopySize));
             }
             const TexelBlockInfo& blockInfo =
                 destination->texture->GetFormat().GetAspectInfo(destination->aspect).block;
@@ -707,7 +707,7 @@
                 // because in the latter we divide copyExtent.width by blockWidth and
                 // copyExtent.height by blockHeight while the divisibility conditions are
                 // checked in validating texture copy range.
-                DAWN_TRY(ValidateTextureCopyRange(*source, fixedCopySize));
+                DAWN_TRY(ValidateTextureCopyRange(GetDevice(), *source, fixedCopySize));
             }
             const TexelBlockInfo& blockInfo =
                 source->texture->GetFormat().GetAspectInfo(source->aspect).block;
@@ -761,8 +761,8 @@
                 DAWN_TRY(
                     ValidateTextureToTextureCopyRestrictions(*source, *destination, fixedCopySize));
 
-                DAWN_TRY(ValidateTextureCopyRange(*source, fixedCopySize));
-                DAWN_TRY(ValidateTextureCopyRange(*destination, fixedCopySize));
+                DAWN_TRY(ValidateTextureCopyRange(GetDevice(), *source, fixedCopySize));
+                DAWN_TRY(ValidateTextureCopyRange(GetDevice(), *destination, fixedCopySize));
 
                 DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc));
                 DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst));
diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp
index 7ddd044..1e2fc38 100644
--- a/src/dawn_native/CommandValidation.cpp
+++ b/src/dawn_native/CommandValidation.cpp
@@ -308,19 +308,29 @@
         return {};
     }
 
-    MaybeError ValidateTextureCopyRange(const ImageCopyTexture& textureCopy,
+    MaybeError ValidateTextureCopyRange(DeviceBase const* device,
+                                        const ImageCopyTexture& textureCopy,
                                         const Extent3D& copySize) {
         // TODO(jiawei.shao@intel.com): add validations on the texture-to-texture copies within the
         // same texture.
         const TextureBase* texture = textureCopy.texture;
 
+        ASSERT(texture->GetDimension() != wgpu::TextureDimension::e1D);
+
+        // Disallow copy to/from a 3D texture as unsafe until it is fully implemented.
+        if (texture->GetDimension() == wgpu::TextureDimension::e3D &&
+            device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs)) {
+            return DAWN_VALIDATION_ERROR(
+                "Copy to/from a 3D texture is disallowed because it is not fully implemented");
+        }
+
         // Validation for the copy being in-bounds:
         Extent3D mipSize = texture->GetMipLevelPhysicalSize(textureCopy.mipLevel);
-        // For 2D textures, include the array layer as depth so it can be checked with other
+        // For 1D/2D textures, include the array layer as depth so it can be checked with other
         // dimensions.
-        ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
-        mipSize.depthOrArrayLayers = texture->GetArrayLayers();
-
+        if (texture->GetDimension() != wgpu::TextureDimension::e3D) {
+            mipSize.depthOrArrayLayers = texture->GetArrayLayers();
+        }
         // All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
         // overflows.
         if (static_cast<uint64_t>(textureCopy.origin.x) + static_cast<uint64_t>(copySize.width) >
diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h
index 5b6290c..9c8a42f 100644
--- a/src/dawn_native/CommandValidation.h
+++ b/src/dawn_native/CommandValidation.h
@@ -48,7 +48,8 @@
                                          uint64_t byteSize,
                                          const TexelBlockInfo& blockInfo,
                                          const Extent3D& copyExtent);
-    MaybeError ValidateTextureCopyRange(const ImageCopyTexture& imageCopyTexture,
+    MaybeError ValidateTextureCopyRange(DeviceBase const* device,
+                                        const ImageCopyTexture& imageCopyTexture,
                                         const Extent3D& copySize);
     ResultOrError<Aspect> SingleAspectUsedByImageCopyTexture(const ImageCopyTexture& view);
     MaybeError ValidateLinearToDepthStencilCopyRestrictions(const ImageCopyTexture& dst);
diff --git a/src/dawn_native/CopyTextureForBrowserHelper.cpp b/src/dawn_native/CopyTextureForBrowserHelper.cpp
index 5d20bd5..b6374be 100644
--- a/src/dawn_native/CopyTextureForBrowserHelper.cpp
+++ b/src/dawn_native/CopyTextureForBrowserHelper.cpp
@@ -208,8 +208,8 @@
 
         DAWN_TRY(ValidateCopyTextureForBrowserRestrictions(*source, *destination, *copySize));
 
-        DAWN_TRY(ValidateTextureCopyRange(*source, *copySize));
-        DAWN_TRY(ValidateTextureCopyRange(*destination, *copySize));
+        DAWN_TRY(ValidateTextureCopyRange(device, *source, *copySize));
+        DAWN_TRY(ValidateTextureCopyRange(device, *destination, *copySize));
 
         DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc));
         DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst));
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 124832c..c113c1e 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -528,7 +528,7 @@
         // because in the latter we divide copyExtent.width by blockWidth and
         // copyExtent.height by blockHeight while the divisibility conditions are
         // checked in validating texture copy range.
-        DAWN_TRY(ValidateTextureCopyRange(*destination, *writeSize));
+        DAWN_TRY(ValidateTextureCopyRange(GetDevice(), *destination, *writeSize));
 
         const TexelBlockInfo& blockInfo =
             destination->texture->GetFormat().GetAspectInfo(destination->aspect).block;
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index a3764c2..a8d153d 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -293,7 +293,7 @@
         if (descriptor->dimension != wgpu::TextureDimension::e2D &&
             device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs)) {
             return DAWN_VALIDATION_ERROR(
-                "1D and 3D textures are disallowed because they are not fully implemented ");
+                "1D and 3D textures are disallowed because they are not fully implemented");
         }
 
         if (descriptor->dimension != wgpu::TextureDimension::e2D && format->isCompressed) {
@@ -421,7 +421,7 @@
           mUsage(descriptor->usage),
           mState(state) {
         uint32_t subresourceCount =
-            mMipLevelCount * mSize.depthOrArrayLayers * GetAspectCount(mFormat.aspects);
+            mMipLevelCount * GetArrayLayers() * GetAspectCount(mFormat.aspects);
         mIsSubresourceContentInitializedAtIndex = std::vector<bool>(subresourceCount, false);
 
         // Add readonly storage usage if the texture has a storage usage. The validation rules in
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index 33811d8..a483b42 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;
-            CopyTextureToBufferWithCopySplit(recordingContext->GetCommandList(), srcCopy,
-                                             bufferCopy, srcTexture, tempBuffer.Get(), copySize);
+            Copy2DTextureToBufferWithCopySplit(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();
-            CopyBufferToTextureWithCopySplit(recordingContext, dstCopy,
-                                             tempBuffer->GetD3D12Resource(), 0, bytesPerRow,
-                                             rowsPerImage, copySize, dstTexture, dstCopy.aspect);
+            CopyBufferTo2DTextureWithCopySplit(recordingContext, dstCopy,
+                                               tempBuffer->GetD3D12Resource(), 0, bytesPerRow,
+                                               rowsPerImage, copySize, dstTexture, dstCopy.aspect);
 
             // Save tempBuffer into recordingContext
             recordingContext->AddToTempBuffers(std::move(tempBuffer));
@@ -741,7 +741,7 @@
 
                     DAWN_TRY(buffer->EnsureDataInitialized(commandContext));
 
-                    ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
+                    ASSERT(texture->GetDimension() != wgpu::TextureDimension::e1D);
                     SubresourceRange subresources =
                         GetSubresourcesAffectedByCopy(copy->destination, copy->copySize);
 
@@ -756,11 +756,22 @@
                     texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst,
                                                         subresources);
 
-                    // compute the copySplits and record the CopyTextureRegion commands
-                    CopyBufferToTextureWithCopySplit(
-                        commandContext, copy->destination, buffer->GetD3D12Resource(),
-                        copy->source.offset, copy->source.bytesPerRow, copy->source.rowsPerImage,
-                        copy->copySize, texture, subresources.aspects);
+                    // 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,
+                                              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;
                 }
@@ -772,7 +783,7 @@
 
                     DAWN_TRY(buffer->EnsureDataInitializedAsDestination(commandContext, copy));
 
-                    ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
+                    ASSERT(texture->GetDimension() != wgpu::TextureDimension::e1D);
                     SubresourceRange subresources =
                         GetSubresourcesAffectedByCopy(copy->source, copy->copySize);
 
@@ -782,8 +793,14 @@
                                                         subresources);
                     buffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopyDst);
 
-                    CopyTextureToBufferWithCopySplit(commandList, copy->source, copy->destination,
-                                                     texture, buffer, copy->copySize);
+                    if (texture->GetDimension() == wgpu::TextureDimension::e3D) {
+                        Copy3DTextureToBuffer(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 ef0b2d7..e03c034 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -418,9 +418,9 @@
         texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst, range);
 
         // compute the copySplits and record the CopyTextureRegion commands
-        CopyBufferToTextureWithCopySplit(commandContext, *dst, ToBackend(source)->GetResource(),
-                                         src.offset, src.bytesPerRow, src.rowsPerImage,
-                                         copySizePixels, texture, range.aspects);
+        CopyBufferTo2DTextureWithCopySplit(commandContext, *dst, ToBackend(source)->GetResource(),
+                                           src.offset, src.bytesPerRow, src.rowsPerImage,
+                                           copySizePixels, texture, range.aspects);
 
         return {};
     }
diff --git a/src/dawn_native/d3d12/TextureCopySplitter.cpp b/src/dawn_native/d3d12/TextureCopySplitter.cpp
index d75257e..e5307cd 100644
--- a/src/dawn_native/d3d12/TextureCopySplitter.cpp
+++ b/src/dawn_native/d3d12/TextureCopySplitter.cpp
@@ -209,7 +209,8 @@
                                                const TexelBlockInfo& blockInfo,
                                                uint64_t offset,
                                                uint32_t bytesPerRow,
-                                               uint32_t rowsPerImage) {
+                                               uint32_t rowsPerImage,
+                                               bool is3DTexture) {
         TextureCopySplits copies;
 
         const uint64_t bytesPerSlice = bytesPerRow * rowsPerImage;
@@ -225,15 +226,19 @@
         // slice. Moreover, if "rowsPerImage" is even, both the first and second copy layers can
         // share the same copy split, so in this situation we just need to compute copy split once
         // and reuse it for all the slices.
-        const dawn_native::Extent3D copyOneLayerSize = {copySize.width, copySize.height, 1};
-        const dawn_native::Origin3D copyFirstLayerOrigin = {origin.x, origin.y, 0};
+        Extent3D copyOneLayerSize = copySize;
+        Origin3D copyFirstLayerOrigin = origin;
+        if (!is3DTexture) {
+            copyOneLayerSize.depthOrArrayLayers = 1;
+            copyFirstLayerOrigin.z = 0;
+        }
 
         copies.copies2D[0] = ComputeTextureCopySplit(copyFirstLayerOrigin, copyOneLayerSize,
                                                      blockInfo, offset, bytesPerRow, rowsPerImage);
 
-        // When the copy only refers one texture 2D array layer copies.copies2D[1] will never be
-        // used so we can safely early return here.
-        if (copySize.depthOrArrayLayers == 1) {
+        // When the copy only refers one texture 2D array layer or a 3D texture, copies.copies2D[1]
+        // will never be used so we can safely early return here.
+        if (copySize.depthOrArrayLayers == 1 || is3DTexture) {
             return copies;
         }
 
diff --git a/src/dawn_native/d3d12/TextureCopySplitter.h b/src/dawn_native/d3d12/TextureCopySplitter.h
index 962c332..f4bdb7b 100644
--- a/src/dawn_native/d3d12/TextureCopySplitter.h
+++ b/src/dawn_native/d3d12/TextureCopySplitter.h
@@ -61,7 +61,8 @@
                                                const TexelBlockInfo& blockInfo,
                                                uint64_t offset,
                                                uint32_t bytesPerRow,
-                                               uint32_t rowsPerImage);
+                                               uint32_t rowsPerImage,
+                                               bool is3DTexture = false);
 }}  // namespace dawn_native::d3d12
 
 #endif  // DAWNNATIVE_D3D12_TEXTURECOPYSPLITTER_H_
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 145fff6..fdcd43e 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -767,8 +767,7 @@
         const ExecutionSerial pendingCommandSerial =
             ToBackend(GetDevice())->GetPendingCommandSerial();
 
-        // This transitions assume it is a 2D texture
-        ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
+        ASSERT(GetDimension() != wgpu::TextureDimension::e1D);
 
         mSubresourceStateAndDecay.Update(
             range, [&](const SubresourceRange& updateRange, StateAndDecay* state) {
diff --git a/src/dawn_native/d3d12/UtilsD3D12.cpp b/src/dawn_native/d3d12/UtilsD3D12.cpp
index a2cc893..4aea0ff 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.cpp
+++ b/src/dawn_native/d3d12/UtilsD3D12.cpp
@@ -174,15 +174,15 @@
         }
     }
 
-    void CopyBufferToTextureWithCopySplit(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 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) {
         ASSERT(HasOneBit(aspect));
         // See comments in ComputeTextureCopySplits() for more details.
         const TexelBlockInfo& blockInfo = texture->GetFormat().GetAspectInfo(aspect).block;
@@ -217,6 +217,26 @@
         }
     }
 
+    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) {
+        ASSERT(HasOneBit(aspect));
+        // See comments in ComputeTextureCopySplits() for more details.
+        const TexelBlockInfo& blockInfo = texture->GetFormat().GetAspectInfo(aspect).block;
+        const TextureCopySplits copySplits = ComputeTextureCopySplits(
+            textureCopy.origin, copySize, blockInfo, offset, bytesPerRow, rowsPerImage, true);
+
+        RecordCopyBufferToTextureFromTextureCopySplit(
+            commandContext->GetCommandList(), copySplits.copies2D[0], bufferResource, 0,
+            bytesPerRow, texture, textureCopy.mipLevel, textureCopy.origin.z, aspect);
+    }
+
     void RecordCopyTextureToBufferFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
                                                        const Texture2DCopySplit& baseCopySplit,
                                                        Buffer* buffer,
@@ -249,12 +269,13 @@
         }
     }
 
-    void CopyTextureToBufferWithCopySplit(ID3D12GraphicsCommandList* commandList,
-                                          const TextureCopy& textureCopy,
-                                          const BufferCopy& bufferCopy,
-                                          Texture* texture,
-                                          Buffer* buffer,
-                                          const Extent3D& copySize) {
+    void Copy2DTextureToBufferWithCopySplit(ID3D12GraphicsCommandList* commandList,
+                                            const TextureCopy& textureCopy,
+                                            const BufferCopy& bufferCopy,
+                                            Texture* texture,
+                                            Buffer* buffer,
+                                            const Extent3D& copySize) {
+        ASSERT(HasOneBit(textureCopy.aspect));
         const TexelBlockInfo& blockInfo =
             texture->GetFormat().GetAspectInfo(textureCopy.aspect).block;
 
@@ -290,4 +311,24 @@
         }
     }
 
+    void Copy3DTextureToBuffer(ID3D12GraphicsCommandList* commandList,
+                               const TextureCopy& textureCopy,
+                               const BufferCopy& bufferCopy,
+                               Texture* texture,
+                               Buffer* buffer,
+                               const Extent3D& copySize) {
+        ASSERT(HasOneBit(textureCopy.aspect));
+        const TexelBlockInfo& blockInfo =
+            texture->GetFormat().GetAspectInfo(textureCopy.aspect).block;
+
+        // See comments around ComputeTextureCopySplits() for more details.
+        const TextureCopySplits copySplits =
+            ComputeTextureCopySplits(textureCopy.origin, copySize, blockInfo, bufferCopy.offset,
+                                     bufferCopy.bytesPerRow, bufferCopy.rowsPerImage, true);
+
+        RecordCopyTextureToBufferFromTextureCopySplit(
+            commandList, copySplits.copies2D[0], buffer, 0, bufferCopy.bytesPerRow, texture,
+            textureCopy.mipLevel, textureCopy.origin.z, textureCopy.aspect);
+    }
+
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/UtilsD3D12.h b/src/dawn_native/d3d12/UtilsD3D12.h
index 5733543..fd9be66 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.h
+++ b/src/dawn_native/d3d12/UtilsD3D12.h
@@ -54,15 +54,25 @@
                                                        uint32_t textureSlice,
                                                        Aspect aspect);
 
-    void CopyBufferToTextureWithCopySplit(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 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 RecordCopyTextureToBufferFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
                                                        const Texture2DCopySplit& baseCopySplit,
@@ -74,12 +84,19 @@
                                                        uint32_t textureSlice,
                                                        Aspect aspect);
 
-    void CopyTextureToBufferWithCopySplit(ID3D12GraphicsCommandList* commandList,
-                                          const TextureCopy& textureCopy,
-                                          const BufferCopy& bufferCopy,
-                                          Texture* texture,
-                                          Buffer* buffer,
-                                          const Extent3D& copySize);
+    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);
 
 }}  // namespace dawn_native::d3d12
 
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index 46f1924..940aa1b 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -1018,7 +1018,7 @@
 
     uint32_t rowsPerImage = extent.height;
     uint32_t size = utils::RequiredBytesInCopy(bytesPerRow, rowsPerImage, extent.width,
-                                               extent.height, extent.depth, dataSize);
+                                               extent.height, extent.depthOrArrayLayers, dataSize);
 
     // TODO(enga): We should have the map async alignment in Contants.h. Also, it should change to 8
     // for Float64Array.
@@ -1026,8 +1026,8 @@
 
     // We need to enqueue the copy immediately because by the time we resolve the expectation,
     // the texture might have been modified.
-    wgpu::ImageCopyTexture imageCopyTexture =
-        utils::CreateImageCopyTexture(texture, level, {origin.x, origin.y, layer}, aspect);
+    wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture(
+        texture, level, {origin.x, origin.y, origin.z + layer}, aspect);
     wgpu::ImageCopyBuffer imageCopyBuffer =
         utils::CreateImageCopyBuffer(readback.buffer, readback.offset, bytesPerRow, rowsPerImage);
 
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index 845fb2f..631dfd9 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -110,13 +110,18 @@
                                 const void* srcData,
                                 uint32_t widthInBlocks,
                                 uint32_t heightInBlocks,
+                                uint32_t depthInBlocks,
                                 uint32_t srcBytesPerRow,
                                 void* dstData,
                                 uint32_t dstBytesPerRow) {
-        for (unsigned int y = 0; y < heightInBlocks; ++y) {
-            memcpy(static_cast<uint8_t*>(dstData) + y * dstBytesPerRow,
-                   static_cast<const uint8_t*>(srcData) + y * srcBytesPerRow,
-                   widthInBlocks * bytesPerTexelBlock);
+        for (unsigned int z = 0; z < depthInBlocks; ++z) {
+            uint32_t srcDepthOffset = z * srcBytesPerRow * heightInBlocks;
+            uint32_t dstDepthOffset = z * dstBytesPerRow * heightInBlocks;
+            for (unsigned int y = 0; y < heightInBlocks; ++y) {
+                memcpy(static_cast<uint8_t*>(dstData) + srcDepthOffset + y * dstBytesPerRow,
+                       static_cast<const uint8_t*>(srcData) + dstDepthOffset + y * srcBytesPerRow,
+                       widthInBlocks * bytesPerTexelBlock);
+            }
         }
     }
 };
@@ -198,7 +203,7 @@
 
             PackTextureData(bytesPerTexel,
                             textureArrayData.data() + expectedTexelArrayDataStartIndex,
-                            copySize.width, copySize.height, copyLayout.bytesPerRow,
+                            copySize.width, copySize.height, 1, copyLayout.bytesPerRow,
                             expected.data(), bufferSpec.bytesPerRow);
 
             EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<const uint32_t*>(expected.data()), buffer,
@@ -229,10 +234,10 @@
 
     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);
-
         // Create a buffer of size `size` and populate it with data
         const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
         std::vector<RGBA8> bufferData(bufferSpec.size / bytesPerTexel);
@@ -243,7 +248,7 @@
 
         // 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;
@@ -253,13 +258,6 @@
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
 
-        const utils::TextureDataCopyLayout copyLayout =
-            utils::GetTextureDataCopyLayoutForTexture2DAtLevel(
-                textureSpec.format, textureSpec.textureSize, textureSpec.copyLevel,
-                bufferSpec.rowsPerImage);
-
-        const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depthOrArrayLayers;
-
         wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer(
             buffer, bufferSpec.offset, bufferSpec.bytesPerRow, bufferSpec.rowsPerImage);
         wgpu::ImageCopyTexture imageCopyTexture =
@@ -269,28 +267,41 @@
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
+        const utils::TextureDataCopyLayout copyLayout =
+            utils::GetTextureDataCopyLayoutForTexture2DAtLevel(
+                textureSpec.format, textureSpec.textureSize, textureSpec.copyLevel,
+                bufferSpec.rowsPerImage);
+
+        uint32_t copyLayer = copySize.depthOrArrayLayers;
+        uint32_t copyDepth = 1;
+        if (dimension == wgpu::TextureDimension::e3D) {
+            copyLayer = 1;
+            copyDepth = copySize.depthOrArrayLayers;
+        }
+
         uint64_t bufferOffset = bufferSpec.offset;
         const uint32_t texelCountLastLayer =
-            copyLayout.texelBlocksPerRow * (copyLayout.mipSize.height - 1) +
-            copyLayout.mipSize.width;
-        for (uint32_t slice = textureSpec.copyOrigin.z; slice < maxArrayLayer; ++slice) {
+            copyDepth * (copyLayout.texelBlocksPerRow * (copyLayout.mipSize.height - 1) +
+                         copyLayout.mipSize.width);
+        for (uint32_t layer = 0; layer < copyLayer; ++layer) {
             // Pack the data used to create the buffer in the specified copy region to have the same
             // format as the expected texture data.
             std::vector<RGBA8> expected(texelCountLastLayer);
             PackTextureData(bytesPerTexel, bufferData.data() + bufferOffset / bytesPerTexel,
-                            copySize.width, copySize.height, bufferSpec.bytesPerRow,
+                            copySize.width, copySize.height, copyDepth, bufferSpec.bytesPerRow,
                             expected.data(), copySize.width * bytesPerTexel);
 
-            EXPECT_TEXTURE_RGBA8_EQ(expected.data(), texture,
-                                    (textureSpec.copyOrigin.x, textureSpec.copyOrigin.y),
-                                    (copySize.width, copySize.height), textureSpec.copyLevel, slice)
+            EXPECT_TEXTURE_RGBA8_EQ(
+                expected.data(), texture,
+                (textureSpec.copyOrigin.x, textureSpec.copyOrigin.y, textureSpec.copyOrigin.z),
+                (copySize.width, copySize.height, copyDepth), textureSpec.copyLevel, layer)
                 << "Buffer to Texture copy failed copying " << bufferSpec.size
                 << "-byte buffer with offset " << bufferSpec.offset << " and bytes per row "
                 << bufferSpec.bytesPerRow << " to [(" << textureSpec.copyOrigin.x << ", "
                 << textureSpec.copyOrigin.y << "), (" << textureSpec.copyOrigin.x + copySize.width
                 << ", " << textureSpec.copyOrigin.y + copySize.height << ")) region of "
                 << textureSpec.textureSize.width << " x " << textureSpec.textureSize.height
-                << " texture at mip level " << textureSpec.copyLevel << " layer " << slice
+                << " texture at mip level " << textureSpec.copyLevel << " layer " << layer
                 << std::endl;
             bufferOffset += copyLayout.bytesPerImage;
         }
@@ -408,7 +419,7 @@
                     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, srcDataCopyLayout.bytesPerRow,
+                                copySize.width, copySize.height, 1, srcDataCopyLayout.bytesPerRow,
                                 &expectedDstDataPerSlice[expectedDstDataOffset],
                                 dstDataCopyLayout.bytesPerRow);
 
@@ -918,6 +929,9 @@
     DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
 }
 
+// TODO(yunchao.he@intel.com): add T2B tests for 3D textures, like entire texture copy, RowPitch,
+// RowsPerImage, buffer offset, partial depth range, non-zero level, etc.
+
 DAWN_INSTANTIATE_TEST(CopyTests_T2B,
                       D3D12Backend(),
                       MetalBackend(),
@@ -1355,6 +1369,26 @@
     DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
 }
 
+// Test that copying whole texture 3D in one texture-to-buffer-copy works.
+TEST_P(CopyTests_B2T, Texture3DRegion) {
+    // 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;
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, kLayers};
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kLayers), {kWidth, kHeight, kLayers},
+           wgpu::TextureDimension::e3D);
+}
+
+// TODO(yunchao.he@intel.com): add more tests like RowPitch, RowsPerImage, buffer offset, partial
+// depth range, non-zero level, etc.
+
 DAWN_INSTANTIATE_TEST(CopyTests_B2T,
                       D3D12Backend(),
                       MetalBackend(),
@@ -1665,6 +1699,9 @@
     }
 }
 
+// TODO(yunchao.he@intel.com): add T2T tests for 3D textures, like entire texture copy, RowPitch,
+// RowsPerImage, buffer offset, partial depth range, non-zero level, etc.
+
 DAWN_INSTANTIATE_TEST(
     CopyTests_T2T,
     D3D12Backend(),