Support multiple array layers in one texture-to-texture copy command

This patch adds the supports of copying multiple array layers of a
2D array texture in one texture-to-texture call. Note that in D3D12
and Metal it is implemented by copying each array layer in a for-loop.

Note that we need extra validations when the source and destination
texture are the same one in a texture-to-texture copy. This CL does
not include these validations and we will add them in another one.

BUG=dawn:18
TEST=dawn_unittests, dawn_end2end_tests

Change-Id: I1239543e5692e140474b3c1de0b3579be449e283
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22140
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index d2d4302..79db419 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -37,6 +37,8 @@
 
     namespace {
 
+        // TODO(jiawei.shao@intel.com): add validations on the texture-to-texture copies within the
+        // same texture.
         MaybeError ValidateCopySizeFitsInTexture(const TextureCopyView& textureCopy,
                                                  const Extent3D& copySize) {
             const TextureBase* texture = textureCopy.texture;
@@ -44,7 +46,9 @@
                 return DAWN_VALIDATION_ERROR("Copy mipLevel out of range");
             }
 
-            if (textureCopy.arrayLayer >= texture->GetArrayLayers()) {
+            if (static_cast<uint64_t>(textureCopy.arrayLayer) +
+                    static_cast<uint64_t>(copySize.depth) >
+                static_cast<uint64_t>(texture->GetArrayLayers())) {
                 return DAWN_VALIDATION_ERROR("Copy arrayLayer out of range");
             }
 
@@ -52,17 +56,18 @@
 
             // All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
             // overflows.
-            if (uint64_t(textureCopy.origin.x) + uint64_t(copySize.width) >
+            if (static_cast<uint64_t>(textureCopy.origin.x) +
+                        static_cast<uint64_t>(copySize.width) >
                     static_cast<uint64_t>(extent.width) ||
-                uint64_t(textureCopy.origin.y) + uint64_t(copySize.height) >
+                static_cast<uint64_t>(textureCopy.origin.y) +
+                        static_cast<uint64_t>(copySize.height) >
                     static_cast<uint64_t>(extent.height)) {
                 return DAWN_VALIDATION_ERROR("Copy would touch outside of the texture");
             }
 
-            // TODO(cwallez@chromium.org): Check the depth bound differently for 2D arrays and 3D
-            // textures
-            if (textureCopy.origin.z != 0 || copySize.depth > 1) {
-                return DAWN_VALIDATION_ERROR("No support for z != 0 and depth > 1 for now");
+            // TODO(cwallez@chromium.org): Check the depth bound differently for 3D textures.
+            if (textureCopy.origin.z != 0) {
+                return DAWN_VALIDATION_ERROR("No support for z != 0 for now");
             }
 
             return {};
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index ebbef1c..ae85ef4 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -660,15 +660,17 @@
                     Texture* destination = ToBackend(copy->destination.texture.Get());
 
                     source->EnsureSubresourceContentInitialized(
-                        commandContext, copy->source.mipLevel, 1, copy->source.arrayLayer, 1);
+                        commandContext, copy->source.mipLevel, 1, copy->source.arrayLayer,
+                        copy->copySize.depth);
                     if (IsCompleteSubresourceCopiedTo(destination, copy->copySize,
                                                       copy->destination.mipLevel)) {
                         destination->SetIsSubresourceContentInitialized(
-                            true, copy->destination.mipLevel, 1, copy->destination.arrayLayer, 1);
+                            true, copy->destination.mipLevel, 1, copy->destination.arrayLayer,
+                            copy->copySize.depth);
                     } else {
                         destination->EnsureSubresourceContentInitialized(
                             commandContext, copy->destination.mipLevel, 1,
-                            copy->destination.arrayLayer, 1);
+                            copy->destination.arrayLayer, copy->copySize.depth);
                     }
                     source->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopySrc);
                     destination->TrackUsageAndTransitionNow(commandContext,
@@ -678,21 +680,29 @@
                         commandList->CopyResource(destination->GetD3D12Resource(),
                                                   source->GetD3D12Resource());
                     } else {
-                        D3D12_TEXTURE_COPY_LOCATION srcLocation =
-                            ComputeTextureCopyLocationForTexture(source, copy->source.mipLevel,
-                                                                 copy->source.arrayLayer);
+                        // TODO(jiawei.shao@intel.com): support copying with 1D and 3D textures.
+                        ASSERT(source->GetDimension() == wgpu::TextureDimension::e2D &&
+                               destination->GetDimension() == wgpu::TextureDimension::e2D);
+                        const dawn_native::Extent3D copyExtentOneSlice = {
+                            copy->copySize.width, copy->copySize.height, 1u};
+                        for (uint32_t slice = 0; slice < copy->copySize.depth; ++slice) {
+                            D3D12_TEXTURE_COPY_LOCATION srcLocation =
+                                ComputeTextureCopyLocationForTexture(
+                                    source, copy->source.mipLevel, copy->source.arrayLayer + slice);
 
-                        D3D12_TEXTURE_COPY_LOCATION dstLocation =
-                            ComputeTextureCopyLocationForTexture(destination,
-                                                                 copy->destination.mipLevel,
-                                                                 copy->destination.arrayLayer);
+                            D3D12_TEXTURE_COPY_LOCATION dstLocation =
+                                ComputeTextureCopyLocationForTexture(
+                                    destination, copy->destination.mipLevel,
+                                    copy->destination.arrayLayer + slice);
 
-                        D3D12_BOX sourceRegion =
-                            ComputeD3D12BoxFromOffsetAndSize(copy->source.origin, copy->copySize);
+                            D3D12_BOX sourceRegion = ComputeD3D12BoxFromOffsetAndSize(
+                                copy->source.origin, copyExtentOneSlice);
 
-                        commandList->CopyTextureRegion(
-                            &dstLocation, copy->destination.origin.x, copy->destination.origin.y,
-                            copy->destination.origin.z, &srcLocation, &sourceRegion);
+                            commandList->CopyTextureRegion(&dstLocation, copy->destination.origin.x,
+                                                           copy->destination.origin.y,
+                                                           copy->destination.origin.z, &srcLocation,
+                                                           &sourceRegion);
+                        }
                     }
                     break;
                 }
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index f27e7d4..2ed61e3 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -325,10 +325,6 @@
             return MTLOriginMake(origin.x, origin.y, origin.z);
         }
 
-        MTLSize MakeMTLSize(Extent3D extent) {
-            return MTLSizeMake(extent.width, extent.height, extent.depth);
-        }
-
         TextureBufferCopySplit ComputeTextureBufferCopySplit(Origin3D origin,
                                                              Extent3D copyExtent,
                                                              Format textureFormat,
@@ -445,19 +441,19 @@
         void EnsureSourceTextureInitialized(Texture* texture,
                                             const Extent3D& size,
                                             const TextureCopy& src) {
-            // TODO(crbug.com/dawn/145): Specify multiple layers based on |size|
-            texture->EnsureSubresourceContentInitialized(src.mipLevel, 1, src.arrayLayer, 1);
+            texture->EnsureSubresourceContentInitialized(src.mipLevel, 1, src.arrayLayer,
+                                                         size.depth);
         }
 
         void EnsureDestinationTextureInitialized(Texture* texture,
                                                  const Extent3D& size,
                                                  const TextureCopy& dst) {
-            // TODO(crbug.com/dawn/145): Specify multiple layers based on |size|
             if (IsCompleteSubresourceCopiedTo(texture, size, dst.mipLevel)) {
                 texture->SetIsSubresourceContentInitialized(true, dst.mipLevel, 1, dst.arrayLayer,
-                                                            1);
+                                                            size.depth);
             } else {
-                texture->EnsureSubresourceContentInitialized(dst.mipLevel, 1, dst.arrayLayer, 1);
+                texture->EnsureSubresourceContentInitialized(dst.mipLevel, 1, dst.arrayLayer,
+                                                             size.depth);
             }
         }
 
@@ -809,16 +805,24 @@
                     EnsureDestinationTextureInitialized(dstTexture, copy->copySize,
                                                         copy->destination);
 
-                    [commandContext->EnsureBlit()
-                          copyFromTexture:srcTexture->GetMTLTexture()
-                              sourceSlice:copy->source.arrayLayer
-                              sourceLevel:copy->source.mipLevel
-                             sourceOrigin:MakeMTLOrigin(copy->source.origin)
-                               sourceSize:MakeMTLSize(copy->copySize)
-                                toTexture:dstTexture->GetMTLTexture()
-                         destinationSlice:copy->destination.arrayLayer
-                         destinationLevel:copy->destination.mipLevel
-                        destinationOrigin:MakeMTLOrigin(copy->destination.origin)];
+                    // TODO(jiawei.shao@intel.com): support copies with 1D and 3D textures.
+                    ASSERT(srcTexture->GetDimension() == wgpu::TextureDimension::e2D &&
+                           dstTexture->GetDimension() == wgpu::TextureDimension::e2D);
+                    const MTLSize mtlSizeOneLayer =
+                        MTLSizeMake(copy->copySize.width, copy->copySize.height, 1);
+                    for (uint32_t slice = 0; slice < copy->copySize.depth; ++slice) {
+                        [commandContext->EnsureBlit()
+                              copyFromTexture:srcTexture->GetMTLTexture()
+                                  sourceSlice:copy->source.arrayLayer + slice
+                                  sourceLevel:copy->source.mipLevel
+                                 sourceOrigin:MakeMTLOrigin(copy->source.origin)
+                                   sourceSize:mtlSizeOneLayer
+                                    toTexture:dstTexture->GetMTLTexture()
+                             destinationSlice:copy->destination.arrayLayer + slice
+                             destinationLevel:copy->destination.mipLevel
+                            destinationOrigin:MakeMTLOrigin(copy->destination.origin)];
+                    }
+
                     break;
                 }
 
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index 9ee4e08..b8d6d26 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -623,17 +623,17 @@
                     srcTexture->EnsureSubresourceContentInitialized(src.mipLevel, 1, src.arrayLayer,
                                                                     1);
                     if (IsCompleteSubresourceCopiedTo(dstTexture, copySize, dst.mipLevel)) {
-                        dstTexture->SetIsSubresourceContentInitialized(true, dst.mipLevel, 1,
-                                                                       dst.arrayLayer, 1);
+                        dstTexture->SetIsSubresourceContentInitialized(
+                            true, dst.mipLevel, 1, dst.arrayLayer, copy->copySize.depth);
                     } else {
-                        dstTexture->EnsureSubresourceContentInitialized(dst.mipLevel, 1,
-                                                                        dst.arrayLayer, 1);
+                        dstTexture->EnsureSubresourceContentInitialized(
+                            dst.mipLevel, 1, dst.arrayLayer, copy->copySize.depth);
                     }
                     gl.CopyImageSubData(srcTexture->GetHandle(), srcTexture->GetGLTarget(),
                                         src.mipLevel, src.origin.x, src.origin.y, src.arrayLayer,
                                         dstTexture->GetHandle(), dstTexture->GetGLTarget(),
                                         dst.mipLevel, dst.origin.x, dst.origin.y, dst.arrayLayer,
-                                        copySize.width, copySize.height, 1);
+                                        copySize.width, copySize.height, copy->copySize.depth);
                     break;
                 }
 
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index e5c2106..6fad7c6 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -64,10 +64,13 @@
 
             VkImageCopy region;
 
+            // TODO(jiawei.shao@intel.com): support 1D and 3D textures
+            ASSERT(srcTexture->GetDimension() == wgpu::TextureDimension::e2D &&
+                   dstTexture->GetDimension() == wgpu::TextureDimension::e2D);
             region.srcSubresource.aspectMask = srcTexture->GetVkAspectMask();
             region.srcSubresource.mipLevel = srcCopy.mipLevel;
             region.srcSubresource.baseArrayLayer = srcCopy.arrayLayer;
-            region.srcSubresource.layerCount = 1;
+            region.srcSubresource.layerCount = copySize.depth;
 
             region.srcOffset.x = srcCopy.origin.x;
             region.srcOffset.y = srcCopy.origin.y;
@@ -76,7 +79,7 @@
             region.dstSubresource.aspectMask = dstTexture->GetVkAspectMask();
             region.dstSubresource.mipLevel = dstCopy.mipLevel;
             region.dstSubresource.baseArrayLayer = dstCopy.arrayLayer;
-            region.dstSubresource.layerCount = 1;
+            region.dstSubresource.layerCount = copySize.depth;
 
             region.dstOffset.x = dstCopy.origin.x;
             region.dstOffset.y = dstCopy.origin.y;
@@ -86,7 +89,7 @@
             Extent3D imageExtent = ComputeTextureCopyExtent(dstCopy, copySize);
             region.extent.width = imageExtent.width;
             region.extent.height = imageExtent.height;
-            region.extent.depth = imageExtent.depth;
+            region.extent.depth = 1;
 
             return region;
         }
@@ -495,20 +498,21 @@
                     if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize,
                                                       dst.mipLevel)) {
                         // Since destination texture has been overwritten, it has been "initialized"
-                        dst.texture->SetIsSubresourceContentInitialized(true, dst.mipLevel, 1,
-                                                                        dst.arrayLayer, 1);
+                        dst.texture->SetIsSubresourceContentInitialized(
+                            true, dst.mipLevel, 1, dst.arrayLayer, copy->copySize.depth);
                     } else {
                         ToBackend(dst.texture)
                             ->EnsureSubresourceContentInitialized(recordingContext, dst.mipLevel, 1,
-                                                                  dst.arrayLayer, 1);
+                                                                  dst.arrayLayer,
+                                                                  copy->copySize.depth);
                     }
 
                     ToBackend(src.texture)
                         ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc,
-                                             src.mipLevel, 1, src.arrayLayer, 1);
+                                             src.mipLevel, 1, src.arrayLayer, copy->copySize.depth);
                     ToBackend(dst.texture)
                         ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst,
-                                             dst.mipLevel, 1, dst.arrayLayer, 1);
+                                             dst.mipLevel, 1, dst.arrayLayer, copy->copySize.depth);
 
                     // In some situations we cannot do texture-to-texture copies with vkCmdCopyImage
                     // because as Vulkan SPEC always validates image copies with the virtual size of
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index f00df82..f9d768d 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -281,11 +281,13 @@
         uint32_t y;
         uint32_t level;
         uint32_t arraySize = 1u;
+        uint32_t baseArrayLayer = 0u;
     };
 
     struct CopySize {
         uint32_t width;
         uint32_t height;
+        uint32_t arrayLayerCount = 1u;
     };
 
   protected:
@@ -324,77 +326,56 @@
         uint32_t texelsPerRow = bytesPerRow / kBytesPerTexel;
         uint32_t texelCountPerLayer = texelsPerRow * (height - 1) + width;
 
-        std::vector<std::vector<RGBA8>> textureArrayData(srcSpec.arraySize);
-        for (uint32_t slice = 0; slice < srcSpec.arraySize; ++slice) {
-            textureArrayData[slice].resize(texelCountPerLayer);
+        // TODO(jiawei.shao@intel.com): support copying into multiple contiguous array layers in one
+        // copyBufferToTexture() call.
+        std::vector<std::vector<RGBA8>> textureArrayCopyData(copy.arrayLayerCount);
+        for (uint32_t slice = 0; slice < copy.arrayLayerCount; ++slice) {
+            textureArrayCopyData[slice].resize(texelCountPerLayer);
             FillTextureData(width, height, bytesPerRow / kBytesPerTexel, slice,
-                            textureArrayData[slice].data());
+                            textureArrayCopyData[slice].data());
 
             wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(
-                device, textureArrayData[slice].data(),
-                static_cast<uint32_t>(sizeof(RGBA8) * textureArrayData[slice].size()),
+                device, textureArrayCopyData[slice].data(),
+                static_cast<uint32_t>(sizeof(RGBA8) * textureArrayCopyData[slice].size()),
                 wgpu::BufferUsage::CopySrc);
             wgpu::BufferCopyView bufferCopyView =
                 utils::CreateBufferCopyView(uploadBuffer, 0, bytesPerRow, 0);
-            wgpu::TextureCopyView textureCopyView =
-                utils::CreateTextureCopyView(srcTexture, srcSpec.level, slice, {0, 0, 0});
+            wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(
+                srcTexture, srcSpec.level, srcSpec.baseArrayLayer + slice, {0, 0, 0});
             wgpu::Extent3D bufferCopySize = {width, height, 1};
 
             encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &bufferCopySize);
         }
 
-        // Create an upload buffer filled with empty data and use it to populate the `level` mip of
-        // the texture. Note: Prepopulating the texture 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
-        {
-            uint32_t dstWidth = dstSpec.width >> dstSpec.level;
-            uint32_t dstHeight = dstSpec.height >> dstSpec.level;
-            uint32_t dstRowPitch = Align(kBytesPerTexel * dstWidth, kTextureBytesPerRowAlignment);
-            uint32_t dstTexelsPerRow = dstRowPitch / kBytesPerTexel;
-            uint32_t dstTexelCount = dstTexelsPerRow * (dstHeight - 1) + dstWidth;
-
-            std::vector<RGBA8> emptyData(dstTexelCount);
-            wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(
-                device, emptyData.data(), static_cast<uint32_t>(sizeof(RGBA8) * emptyData.size()),
-                wgpu::BufferUsage::CopySrc);
-            wgpu::BufferCopyView bufferCopyView =
-                utils::CreateBufferCopyView(uploadBuffer, 0, dstRowPitch, 0);
-            wgpu::TextureCopyView textureCopyView =
-                utils::CreateTextureCopyView(dstTexture, dstSpec.level, 0, {0, 0, 0});
-            wgpu::Extent3D dstCopySize = {dstWidth, dstHeight, 1};
-            encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &dstCopySize);
-        }
-
         // Perform the texture to texture copy
-        for (uint32_t slice = 0; slice < srcSpec.arraySize; ++slice) {
-            wgpu::TextureCopyView srcTextureCopyView = utils::CreateTextureCopyView(
-                srcTexture, srcSpec.level, slice, {srcSpec.x, srcSpec.y, 0});
-            wgpu::TextureCopyView dstTextureCopyView = utils::CreateTextureCopyView(
-                dstTexture, dstSpec.level, slice, {dstSpec.x, dstSpec.y, 0});
-            wgpu::Extent3D copySize = {copy.width, copy.height, 1};
-            encoder.CopyTextureToTexture(&srcTextureCopyView, &dstTextureCopyView, &copySize);
-        }
+        wgpu::TextureCopyView srcTextureCopyView = utils::CreateTextureCopyView(
+            srcTexture, srcSpec.level, srcSpec.baseArrayLayer, {srcSpec.x, srcSpec.y, 0});
+        wgpu::TextureCopyView dstTextureCopyView = utils::CreateTextureCopyView(
+            dstTexture, dstSpec.level, dstSpec.baseArrayLayer, {dstSpec.x, dstSpec.y, 0});
+        wgpu::Extent3D copySize = {copy.width, copy.height, copy.arrayLayerCount};
+        encoder.CopyTextureToTexture(&srcTextureCopyView, &dstTextureCopyView, &copySize);
 
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
         std::vector<RGBA8> expected(bytesPerRow / kBytesPerTexel * (copy.height - 1) + copy.width);
-        for (uint32_t slice = 0; slice < srcSpec.arraySize; ++slice) {
+        for (uint32_t slice = 0; slice < copy.arrayLayerCount; ++slice) {
             std::fill(expected.begin(), expected.end(), RGBA8());
-            PackTextureData(
-                &textureArrayData[slice][srcSpec.x + srcSpec.y * (bytesPerRow / kBytesPerTexel)],
-                copy.width, copy.height, texelsPerRow, expected.data(), copy.width);
+            PackTextureData(&textureArrayCopyData[slice][srcSpec.x + srcSpec.y * (bytesPerRow /
+                                                                                  kBytesPerTexel)],
+                            copy.width, copy.height, texelsPerRow, expected.data(), copy.width);
 
             EXPECT_TEXTURE_RGBA8_EQ(expected.data(), dstTexture, dstSpec.x, dstSpec.y, copy.width,
-                                    copy.height, dstSpec.level, slice)
+                                    copy.height, dstSpec.level, dstSpec.baseArrayLayer + slice)
                 << "Texture to Texture copy failed copying region [(" << srcSpec.x << ", "
                 << srcSpec.y << "), (" << srcSpec.x + copy.width << ", " << srcSpec.y + copy.height
                 << ")) from " << srcSpec.width << " x " << srcSpec.height
-                << " texture at mip level " << srcSpec.level << " layer " << slice << " to [("
-                << dstSpec.x << ", " << dstSpec.y << "), (" << dstSpec.x + copy.width << ", "
-                << dstSpec.y + copy.height << ")) region of " << dstSpec.width << " x "
-                << dstSpec.height << " texture at mip level " << dstSpec.level << std::endl;
+                << " texture at mip level " << srcSpec.level << " layer "
+                << srcSpec.baseArrayLayer + slice << " to [(" << dstSpec.x << ", " << dstSpec.y
+                << "), (" << dstSpec.x + copy.width << ", " << dstSpec.y + copy.height
+                << ")) region of " << dstSpec.width << " x " << dstSpec.height
+                << " texture at mip level " << dstSpec.level << " layer "
+                << dstSpec.baseArrayLayer + slice << std::endl;
         }
     }
 };
@@ -723,26 +704,61 @@
     }
 }
 
+// Test copying the whole 2D array texture.
 TEST_P(CopyTests_T2T, Texture2DArray) {
+    // TODO(jiawei.shao@intel.com): investigate why this test fails with swiftshader.
+    DAWN_SKIP_TEST_IF(IsSwiftshader());
+
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
     DoTest({kWidth, kHeight, 0, 0, 0, kLayers}, {kWidth, kHeight, 0, 0, 0, kLayers},
-           {kWidth, kHeight});
+           {kWidth, kHeight, kLayers});
 }
 
+// Test copying a subresource region of the 2D array texture.
 TEST_P(CopyTests_T2T, Texture2DArrayRegion) {
+    // TODO(jiawei.shao@intel.com): investigate why this test fails with swiftshader.
+    DAWN_SKIP_TEST_IF(IsSwiftshader());
+
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
     for (unsigned int w : {64, 128, 256}) {
         for (unsigned int h : {16, 32, 48}) {
             DoTest({kWidth, kHeight, 0, 0, 0, kLayers}, {kWidth, kHeight, 0, 0, 0, kLayers},
-                   {w, h});
+                   {w, h, kLayers});
         }
     }
 }
 
+// Test copying one slice of a 2D array texture.
+TEST_P(CopyTests_T2T, Texture2DArrayCopyOneSlice) {
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    constexpr uint32_t kLayers = 6u;
+    constexpr uint32_t kSrcBaseLayer = 1u;
+    constexpr uint32_t kDstBaseLayer = 3u;
+    DoTest({kWidth, kHeight, 0, 0, 0, kLayers, kSrcBaseLayer},
+           {kWidth, kHeight, 0, 0, 0, kLayers, kDstBaseLayer}, {kWidth, kHeight, 1u});
+}
+
+// Test copying multiple contiguous slices of a 2D array texture.
+TEST_P(CopyTests_T2T, Texture2DArrayCopyMultipleSlices) {
+    // TODO(jiawei.shao@intel.com): investigate why this test fails 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 kSrcBaseLayer = 0u;
+    constexpr uint32_t kDstBaseLayer = 3u;
+    constexpr uint32_t kCopyArrayLayerCount = 3u;
+    DoTest({kWidth, kHeight, 0, 0, 0, kLayers, kSrcBaseLayer},
+           {kWidth, kHeight, 0, 0, 0, kLayers, kDstBaseLayer},
+           {kWidth, kHeight, kCopyArrayLayerCount});
+}
+
 TEST_P(CopyTests_T2T, TextureMip) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
index 4523aee..de1da26 100644
--- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
+++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
@@ -990,9 +990,9 @@
 
 TEST_F(CopyCommandTest_T2T, Success) {
     wgpu::Texture source =
-        Create2DTexture(16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopySrc);
+        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopySrc);
     wgpu::Texture destination =
-        Create2DTexture(16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
+        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
 
     // Different copies, including some that touch the OOB condition
     {
@@ -1019,6 +1019,16 @@
         // Copy between slices
         TestT2TCopy(utils::Expectation::Success, source, 0, 1, {0, 0, 0}, destination, 0, 1,
                     {0, 0, 0}, {16, 16, 1});
+
+        // Copy multiple slices (srcTextureCopyView.arrayLayer + copySize.depth ==
+        // srcTextureCopyView.texture.arrayLayerCount)
+        TestT2TCopy(utils::Expectation::Success, source, 0, 2, {0, 0, 0}, destination, 0, 0,
+                    {0, 0, 0}, {16, 16, 2});
+
+        // Copy multiple slices (dstTextureCopyView.arrayLayer + copySize.depth ==
+        // dstTextureCopyView.texture.arrayLayerCount)
+        TestT2TCopy(utils::Expectation::Success, source, 0, 0, {0, 0, 0}, destination, 0, 2,
+                    {0, 0, 0}, {16, 16, 2});
     }
 
     // Empty copies are valid
@@ -1058,9 +1068,9 @@
 
 TEST_F(CopyCommandTest_T2T, OutOfBounds) {
     wgpu::Texture source =
-        Create2DTexture(16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopySrc);
+        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopySrc);
     wgpu::Texture destination =
-        Create2DTexture(16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
+        Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
 
     // OOB on source
     {
@@ -1076,12 +1086,16 @@
         TestT2TCopy(utils::Expectation::Failure, source, 1, 0, {0, 0, 0}, destination, 0, 0,
                     {0, 0, 0}, {9, 9, 1});
 
+        // arrayLayer + depth OOB
+        TestT2TCopy(utils::Expectation::Failure, source, 0, 3, {0, 0, 0}, destination, 0, 0,
+                    {0, 0, 0}, {16, 16, 2});
+
         // empty copy on non-existent mip fails
         TestT2TCopy(utils::Expectation::Failure, source, 6, 0, {0, 0, 0}, destination, 0, 0,
                     {0, 0, 0}, {0, 0, 1});
 
         // empty copy from non-existent slice fails
-        TestT2TCopy(utils::Expectation::Failure, source, 0, 2, {0, 0, 0}, destination, 0, 0,
+        TestT2TCopy(utils::Expectation::Failure, source, 0, 4, {0, 0, 0}, destination, 0, 0,
                     {0, 0, 0}, {0, 0, 1});
     }
 
@@ -1099,12 +1113,16 @@
         TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 1, 0,
                     {0, 0, 0}, {9, 9, 1});
 
+        // arrayLayer + depth OOB
+        TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 3,
+                    {0, 0, 0}, {16, 16, 2});
+
         // empty copy on non-existent mip fails
         TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 6, 0,
                     {0, 0, 0}, {0, 0, 1});
 
         // empty copy on non-existent slice fails
-        TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 2,
+        TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 4,
                     {0, 0, 0}, {0, 0, 1});
     }
 }
@@ -1122,10 +1140,6 @@
     // Empty copy on destination with z > 0 fails
     TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 0, {0, 0, 1},
                 {0, 0, 1});
-
-    // Empty copy with depth > 1 fails
-    TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 0, {0, 0, 0},
-                {0, 0, 2});
 }
 
 TEST_F(CopyCommandTest_T2T, 2DTextureDepthStencil) {