Clean up CopyTests

This patch does several clean-ups in CopyTests as a preparation of
supporting buffer-to-texture and texture-to-buffer copies with multiple
texture array layers.
1. Remove the zero-initialization of textures in CopyTests_B2T
2. Use a big buffer that contains the pixel data in all the texture
   array layers instead of several small ones that only contains one
   texture array layer.
3. Make CopyTests_T2T::DoTest() also use CopyTests::TextureSpec as its
   input parameter.
4. Add tests for the copies with multiple texture array layers. In this
   patch we just copy the data once per texture array layer, while in
   the next patch we will be able to copy the data for all the related
   texture array layers in one copy command.
5. Move some helper functions for copy tests to WGPUHelpers.cpp

BUG=dawn:453
TEST=dawn_end2end_tests

Change-Id: Ia0150a6c55fffce3e24dda2cd45fd920b0873dc9
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/23120
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index 857faa0..4b98414 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -17,22 +17,18 @@
 #include <array>
 #include "common/Constants.h"
 #include "common/Math.h"
+#include "utils/TextureFormatUtils.h"
 #include "utils/WGPUHelpers.h"
 
 class CopyTests : public DawnTest {
     protected:
-        static constexpr unsigned int kBytesPerTexel = 4;
+      static constexpr wgpu::TextureFormat kTextureFormat = wgpu::TextureFormat::RGBA8Unorm;
 
-        struct TextureSpec {
-            uint32_t width;
-            uint32_t height;
-            uint32_t x;
-            uint32_t y;
-            uint32_t copyWidth;
-            uint32_t copyHeight;
-            uint32_t level;
-            uint32_t arraySize = 1u;
-        };
+      struct TextureSpec {
+          wgpu::Origin3D copyOrigin;
+          wgpu::Extent3D textureSize;
+          uint32_t level;
+      };
 
         struct BufferSpec {
             uint64_t size;
@@ -40,24 +36,32 @@
             uint32_t bytesPerRow;
         };
 
-        static void FillTextureData(uint32_t width,
-                                    uint32_t height,
-                                    uint32_t texelsPerRow,
-                                    uint32_t layer,
-                                    RGBA8* data) {
-            for (uint32_t y = 0; y < height; ++y) {
-                for (uint32_t x = 0; x < width; ++x) {
-                    uint32_t i = x + y * texelsPerRow;
-                    data[i] = RGBA8(static_cast<uint8_t>((x + layer * x) % 256),
-                                    static_cast<uint8_t>((y + layer * y) % 256),
-                                    static_cast<uint8_t>(x / 256), static_cast<uint8_t>(y / 256));
+        static std::vector<RGBA8> GetExpectedTextureData(
+            const utils::BufferTextureCopyLayout& layout) {
+            std::vector<RGBA8> textureData(layout.texelBlockCount);
+            for (uint32_t layer = 0; layer < layout.mipSize.depth; ++layer) {
+                const uint32_t texelIndexOffsetPerSlice = layout.texelBlocksPerImage * layer;
+                for (uint32_t y = 0; y < layout.mipSize.height; ++y) {
+                    for (uint32_t x = 0; x < layout.mipSize.width; ++x) {
+                        uint32_t i = x + y * layout.texelBlocksPerRow;
+                        textureData[texelIndexOffsetPerSlice + i] =
+                            RGBA8(static_cast<uint8_t>((x + layer * x) % 256),
+                                  static_cast<uint8_t>((y + layer * y) % 256),
+                                  static_cast<uint8_t>(x / 256), static_cast<uint8_t>(y / 256));
+                    }
                 }
             }
+
+            return textureData;
         }
 
-        BufferSpec MinimumBufferSpec(uint32_t width, uint32_t height) {
-            uint32_t bytesPerRow = Align(width * kBytesPerTexel, kTextureBytesPerRowAlignment);
-            return {bytesPerRow * (height - 1) + width * kBytesPerTexel, 0, bytesPerRow};
+        static BufferSpec MinimumBufferSpec(uint32_t width,
+                                            uint32_t height,
+                                            uint32_t arrayLayer = 1) {
+            const uint32_t bytesPerRow = utils::GetMinimumBytesPerRow(kTextureFormat, width);
+            const uint32_t totalBufferSize = utils::GetBytesInBufferTextureCopy(
+                kTextureFormat, width, bytesPerRow, height, arrayLayer);
+            return {totalBufferSize, 0, bytesPerRow};
         }
 
         static void PackTextureData(const RGBA8* srcData, uint32_t width, uint32_t height, uint32_t srcTexelsPerRow, RGBA8* dstData, uint32_t dstTexelsPerRow) {
@@ -73,104 +77,110 @@
 
 class CopyTests_T2B : public CopyTests {
     protected:
+      void DoTest(const TextureSpec& textureSpec,
+                  const BufferSpec& bufferSpec,
+                  const wgpu::Extent3D& copySize) {
+          // Create a texture that is `width` x `height` with (`level` + 1) mip levels.
+          wgpu::TextureDescriptor descriptor;
+          descriptor.dimension = wgpu::TextureDimension::e2D;
+          descriptor.size = textureSpec.textureSize;
+          descriptor.sampleCount = 1;
+          descriptor.format = kTextureFormat;
+          descriptor.mipLevelCount = textureSpec.level + 1;
+          descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
+          wgpu::Texture texture = device.CreateTexture(&descriptor);
 
-        void DoTest(const TextureSpec& textureSpec, const BufferSpec& bufferSpec) {
-            // Create a texture that is `width` x `height` with (`level` + 1) mip levels.
-            wgpu::TextureDescriptor descriptor;
-            descriptor.dimension = wgpu::TextureDimension::e2D;
-            descriptor.size.width = textureSpec.width;
-            descriptor.size.height = textureSpec.height;
-            descriptor.size.depth = textureSpec.arraySize;
-            descriptor.sampleCount = 1;
-            descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
-            descriptor.mipLevelCount = textureSpec.level + 1;
-            descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
-            wgpu::Texture texture = device.CreateTexture(&descriptor);
+          const uint32_t rowsPerImage = textureSpec.textureSize.height >> textureSpec.level;
+          const utils::BufferTextureCopyLayout copyLayout =
+              utils::GetBufferTextureCopyLayoutForTexture2DAtLevel(
+                  kTextureFormat, textureSpec.textureSize, textureSpec.level, rowsPerImage);
 
-            uint32_t width = textureSpec.width >> textureSpec.level;
-            uint32_t height = textureSpec.height >> textureSpec.level;
-            uint32_t bytesPerRow = Align(kBytesPerTexel * width, kTextureBytesPerRowAlignment);
-            uint32_t texelsPerRow = bytesPerRow / kBytesPerTexel;
-            uint32_t texelCountPerLayer = texelsPerRow * (height - 1) + width;
+          wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
 
-            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+          // Initialize the source texture
+          std::vector<RGBA8> textureArrayData = GetExpectedTextureData(copyLayout);
 
-            std::vector<std::vector<RGBA8>> textureArrayData(textureSpec.arraySize);
-            for (uint32_t slice = 0; slice < textureSpec.arraySize; ++slice) {
-                textureArrayData[slice].resize(texelCountPerLayer);
-                FillTextureData(width, height, bytesPerRow / kBytesPerTexel, slice,
-                                textureArrayData[slice].data());
+          // TODO(jiawei.shao@intel.com): copy into multiple texture array layers in one
+          // buffer-to-texture copy command.
+          wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(
+              device, textureArrayData.data(), copyLayout.byteLength, wgpu::BufferUsage::CopySrc);
+          uint64_t uploadBufferOffset = 0;
+          for (uint32_t slice = 0; slice < textureSpec.textureSize.depth; ++slice) {
+              wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(
+                  uploadBuffer, uploadBufferOffset, copyLayout.bytesPerRow, 0);
+              wgpu::TextureCopyView textureCopyView =
+                  utils::CreateTextureCopyView(texture, textureSpec.level, {0, 0, slice});
+              wgpu::Extent3D copyOneLayerSize = {copyLayout.mipSize.width,
+                                                 copyLayout.mipSize.height, 1};
+              encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyOneLayerSize);
+              uploadBufferOffset += copyLayout.bytesPerImage;
+          }
 
-                // Create an upload buffer and use it to populate the current slice of the texture in `level` mip level
-                wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(
-                    device, textureArrayData[slice].data(),
-                    static_cast<uint32_t>(sizeof(RGBA8) * textureArrayData[slice].size()),
-                    wgpu::BufferUsage::CopySrc);
-                wgpu::BufferCopyView bufferCopyView =
-                    utils::CreateBufferCopyView(uploadBuffer, 0, bytesPerRow, 0);
-                wgpu::TextureCopyView textureCopyView =
-                    utils::CreateTextureCopyView(texture, textureSpec.level, {0, 0, slice});
-                wgpu::Extent3D copySize = {width, height, 1};
-                encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copySize);
-            }
+          // Create a buffer of `size` and populate it with empty data (0,0,0,0) Note:
+          // Prepopulating the buffer 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.
+          // TODO(jiawei.shao@intel.com): remove the initialization of the buffer after we support
+          // buffer lazy-initialization.
+          const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(kTextureFormat);
+          const std::vector<RGBA8> emptyData(bufferSpec.size / bytesPerTexel, RGBA8());
+          wgpu::Buffer buffer =
+              utils::CreateBufferFromData(device, emptyData.data(), bufferSpec.size,
+                                          wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
 
-            // Create a buffer of size `size * textureSpec.arrayLayer` and populate it with empty
-            // data (0,0,0,0) Note: Prepopulating the buffer 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
-            wgpu::BufferDescriptor bufDescriptor;
-            bufDescriptor.size = bufferSpec.size * textureSpec.arraySize;
-            bufDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
-            wgpu::Buffer buffer = device.CreateBuffer(&bufDescriptor);
-            std::vector<RGBA8> emptyData(bufferSpec.size / kBytesPerTexel * textureSpec.arraySize);
-            queue.WriteBuffer(buffer, 0, emptyData.data(),
-                              static_cast<uint32_t>(emptyData.size() * sizeof(RGBA8)));
+          const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depth;
 
-            uint64_t bufferOffset = bufferSpec.offset;
-            for (uint32_t slice = 0; slice < textureSpec.arraySize; ++slice) {
-                // Copy the region [(`x`, `y`), (`x + copyWidth, `y + copyWidth`)] from the `level`
-                // mip into the buffer at `offset + bufferSpec.size * slice` and `bytesPerRow`
-                wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(
-                    texture, textureSpec.level, {textureSpec.x, textureSpec.y, slice});
-                wgpu::BufferCopyView bufferCopyView =
-                    utils::CreateBufferCopyView(buffer, bufferOffset, bufferSpec.bytesPerRow, 0);
-                wgpu::Extent3D copySize = {textureSpec.copyWidth, textureSpec.copyHeight, 1};
-                encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, &copySize);
-                bufferOffset += bufferSpec.size;
-            }
+          // TODO(jiawei.shao@intel.com): copy from multiple texture array layers in one
+          // texture-to-buffer copy command.
+          uint64_t bufferOffset = bufferSpec.offset;
+          for (uint32_t slice = textureSpec.copyOrigin.z; slice < maxArrayLayer; ++slice) {
+              // Copy the region [(`x`, `y`, slice), (`x + testCopySize.width, `y +
+              // testCopySize.height`, 1)] from the `level` mip into the buffer at `offset +
+              // bufferSpec.size * slice` and `bytesPerRow`
+              wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(
+                  texture, textureSpec.level,
+                  {textureSpec.copyOrigin.x, textureSpec.copyOrigin.y, slice});
+              wgpu::BufferCopyView bufferCopyView =
+                  utils::CreateBufferCopyView(buffer, bufferOffset, bufferSpec.bytesPerRow, 0);
+              wgpu::Extent3D copyOneLayerSize = {copySize.width, copySize.height, 1};
+              encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, &copyOneLayerSize);
+              bufferOffset += copyLayout.bytesPerImage;
+          }
 
-            wgpu::CommandBuffer commands = encoder.Finish();
-            queue.Submit(1, &commands);
+          wgpu::CommandBuffer commands = encoder.Finish();
+          queue.Submit(1, &commands);
 
-            bufferOffset = bufferSpec.offset;
-            std::vector<RGBA8> expected(bufferSpec.bytesPerRow / kBytesPerTexel *
-                                            (textureSpec.copyHeight - 1) +
-                                        textureSpec.copyWidth);
-            for (uint32_t slice = 0; slice < textureSpec.arraySize; ++slice) {
-                // Pack the data used to create the upload buffer in the specified copy region to have the same format as the expected buffer data.
-                std::fill(expected.begin(), expected.end(), RGBA8());
-                PackTextureData(
-                    &textureArrayData[slice][textureSpec.x +
-                                             textureSpec.y * (bytesPerRow / kBytesPerTexel)],
-                    textureSpec.copyWidth, textureSpec.copyHeight, bytesPerRow / kBytesPerTexel,
-                    expected.data(), bufferSpec.bytesPerRow / kBytesPerTexel);
+          bufferOffset = bufferSpec.offset;
+          const uint32_t texelCountInCopyRegion =
+              bufferSpec.bytesPerRow / bytesPerTexel * (copySize.height - 1) + copySize.width;
+          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
+              // the same format as the expected buffer data.
+              std::fill(expected.begin(), expected.end(), RGBA8());
+              const uint32_t texelIndexOffset = copyLayout.texelBlocksPerImage * slice;
+              const uint32_t expectedTexelArrayDataStartIndex =
+                  texelIndexOffset + (textureSpec.copyOrigin.x +
+                                      textureSpec.copyOrigin.y * copyLayout.texelBlocksPerRow);
 
-                EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<const uint32_t*>(expected.data()),
-                                           buffer, bufferOffset,
-                                           static_cast<uint32_t>(expected.size()))
-                    << "Texture to Buffer copy failed copying region [(" << textureSpec.x << ", "
-                    << textureSpec.y << "), (" << textureSpec.x + textureSpec.copyWidth << ", "
-                    << textureSpec.y + textureSpec.copyHeight << ")) from " << textureSpec.width
-                    << " x " << textureSpec.height << " texture at mip level " << textureSpec.level
-                    << " layer " << slice << " to " << bufDescriptor.size
-                    << "-byte buffer with offset " << bufferOffset << " and bytes per row "
-                    << bufferSpec.bytesPerRow << std::endl;
+              PackTextureData(&textureArrayData[expectedTexelArrayDataStartIndex], copySize.width,
+                              copySize.height, copyLayout.texelBlocksPerRow, expected.data(),
+                              bufferSpec.bytesPerRow / bytesPerTexel);
 
-                bufferOffset += bufferSpec.size;
-            }
-        }
+              EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<const uint32_t*>(expected.data()), buffer,
+                                         bufferOffset, static_cast<uint32_t>(expected.size()))
+                  << "Texture to Buffer copy failed copying region [(" << textureSpec.copyOrigin.x
+                  << ", " << textureSpec.copyOrigin.y << "), ("
+                  << textureSpec.copyOrigin.x + copySize.width << ", "
+                  << textureSpec.copyOrigin.y + copySize.height << ")) from "
+                  << textureSpec.textureSize.width << " x " << textureSpec.textureSize.height
+                  << " texture at mip level " << textureSpec.level << " layer " << slice << " to "
+                  << bufferSpec.size << "-byte buffer with offset " << bufferOffset
+                  << " and bytes per row " << bufferSpec.bytesPerRow << std::endl;
 
-
+              bufferOffset += copyLayout.bytesPerImage;
+          }
+      }
 };
 
 class CopyTests_B2T : public CopyTests {
@@ -185,121 +195,94 @@
         }
     }
 
-    void DoTest(const TextureSpec& textureSpec, const BufferSpec& bufferSpec) {
+    void DoTest(const TextureSpec& textureSpec,
+                const BufferSpec& bufferSpec,
+                const wgpu::Extent3D& copySize) {
         // Create a buffer of size `size` and populate it with data
-        wgpu::BufferDescriptor bufDescriptor;
-        bufDescriptor.size = bufferSpec.size;
-        bufDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
-        wgpu::Buffer buffer = device.CreateBuffer(&bufDescriptor);
-
-        std::vector<RGBA8> bufferData(bufferSpec.size / kBytesPerTexel);
+        const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(kTextureFormat);
+        std::vector<RGBA8> bufferData(bufferSpec.size / bytesPerTexel);
         FillBufferData(bufferData.data(), bufferData.size());
-        queue.WriteBuffer(buffer, 0, bufferData.data(),
-                          static_cast<uint32_t>(bufferData.size() * sizeof(RGBA8)));
+        wgpu::Buffer buffer =
+            utils::CreateBufferFromData(device, bufferData.data(), bufferSpec.size,
+                                        wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
 
         // Create a texture that is `width` x `height` with (`level` + 1) mip levels.
         wgpu::TextureDescriptor descriptor;
         descriptor.dimension = wgpu::TextureDimension::e2D;
-        descriptor.size.width = textureSpec.width;
-        descriptor.size.height = textureSpec.height;
-        descriptor.size.depth = 1;
+        descriptor.size = textureSpec.textureSize;
         descriptor.sampleCount = 1;
-        descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
+        descriptor.format = kTextureFormat;
         descriptor.mipLevelCount = textureSpec.level + 1;
         descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
         wgpu::Texture texture = device.CreateTexture(&descriptor);
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
 
-        // 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 width = textureSpec.width >> textureSpec.level;
-            uint32_t height = textureSpec.height >> textureSpec.level;
-            uint32_t bytesPerRow = Align(kBytesPerTexel * width, kTextureBytesPerRowAlignment);
-            uint32_t texelsPerRow = bytesPerRow / kBytesPerTexel;
-            uint32_t texelCount = texelsPerRow * (height - 1) + width;
+        const uint32_t rowsPerImage = textureSpec.textureSize.height >> textureSpec.level;
+        const utils::BufferTextureCopyLayout copyLayout =
+            utils::GetBufferTextureCopyLayoutForTexture2DAtLevel(
+                kTextureFormat, textureSpec.textureSize, textureSpec.level, rowsPerImage);
 
-            std::vector<RGBA8> emptyData(texelCount);
-            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, bytesPerRow, 0);
-            wgpu::TextureCopyView textureCopyView =
-                utils::CreateTextureCopyView(texture, textureSpec.level, {0, 0, 0});
-            wgpu::Extent3D copySize = {width, height, 1};
-            encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copySize);
-        }
+        const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depth;
 
-        // Copy to the region [(`x`, `y`), (`x + copyWidth, `y + copyWidth`)] at the `level` mip
-        // from the buffer at the specified `offset` and `bytesPerRow`
-        {
+        // TODO(jiawei.shao@intel.com): support copying into multiple texture array layers in one
+        // copy command.
+        uint64_t bufferOffset = bufferSpec.offset;
+        for (uint32_t slice = textureSpec.copyOrigin.z; slice < maxArrayLayer; ++slice) {
+            // Copy to the region [(`x`, `y`, `slice`), (`x + testCopySize.width, `y +
+            // testCopySize.height`, 1] at the `level` mip
+            // from the buffer at the specified `offset` and `bytesPerRow`
             wgpu::BufferCopyView bufferCopyView =
-                utils::CreateBufferCopyView(buffer, bufferSpec.offset, bufferSpec.bytesPerRow, 0);
+                utils::CreateBufferCopyView(buffer, bufferOffset, bufferSpec.bytesPerRow, 0);
             wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(
-                texture, textureSpec.level, {textureSpec.x, textureSpec.y, 0});
-            wgpu::Extent3D copySize = {textureSpec.copyWidth, textureSpec.copyHeight, 1};
-            encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copySize);
+                texture, textureSpec.level,
+                {textureSpec.copyOrigin.x, textureSpec.copyOrigin.y, slice});
+            wgpu::Extent3D copyOneLayerSize = {copySize.width, copySize.height, 1};
+            encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyOneLayerSize);
+            bufferOffset += copyLayout.bytesPerImage;
         }
 
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
-        // Pack the data used to create the buffer in the specified copy region to have the same format as the expected texture data.
-        uint32_t bytesPerRow =
-            Align(kBytesPerTexel * textureSpec.copyWidth, kTextureBytesPerRowAlignment);
-        std::vector<RGBA8> expected(bytesPerRow / kBytesPerTexel * (textureSpec.copyHeight - 1) +
-                                    textureSpec.copyWidth);
-        PackTextureData(&bufferData[bufferSpec.offset / kBytesPerTexel], textureSpec.copyWidth,
-                        textureSpec.copyHeight, bufferSpec.bytesPerRow / kBytesPerTexel,
-                        expected.data(), textureSpec.copyWidth);
+        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) {
+            // 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(&bufferData[bufferOffset / bytesPerTexel], copySize.width,
+                            copySize.height, bufferSpec.bytesPerRow / bytesPerTexel,
+                            expected.data(), copySize.width);
 
-        EXPECT_TEXTURE_RGBA8_EQ(expected.data(), texture, textureSpec.x, textureSpec.y,
-                                textureSpec.copyWidth, textureSpec.copyHeight, textureSpec.level, 0)
-            << "Buffer to Texture copy failed copying " << bufferSpec.size
-            << "-byte buffer with offset " << bufferSpec.offset << " and bytes per row "
-            << bufferSpec.bytesPerRow << " to [(" << textureSpec.x << ", " << textureSpec.y
-            << "), (" << textureSpec.x + textureSpec.copyWidth << ", "
-            << textureSpec.y + textureSpec.copyHeight << ")) region of " << textureSpec.width
-            << " x " << textureSpec.height << " texture at mip level " << textureSpec.level
-            << std::endl;
+            EXPECT_TEXTURE_RGBA8_EQ(expected.data(), texture, textureSpec.copyOrigin.x,
+                                    textureSpec.copyOrigin.y, copySize.width, copySize.height,
+                                    textureSpec.level, slice)
+                << "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.level << " layer " << slice << std::endl;
+            bufferOffset += copyLayout.bytesPerImage;
+        }
     }
-
-
 };
 
 class CopyTests_T2T : public CopyTests {
-    struct TextureSpec {
-        uint32_t width;
-        uint32_t height;
-        uint32_t x;
-        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:
     void DoTest(const TextureSpec& srcSpec,
                 const TextureSpec& dstSpec,
-                const CopySize& copy,
+                const wgpu::Extent3D& copySize,
                 bool copyWithinSameTexture = false) {
         wgpu::TextureDescriptor srcDescriptor;
         srcDescriptor.dimension = wgpu::TextureDimension::e2D;
-        srcDescriptor.size.width = srcSpec.width;
-        srcDescriptor.size.height = srcSpec.height;
-        srcDescriptor.size.depth = srcSpec.arraySize;
+        srcDescriptor.size = srcSpec.textureSize;
         srcDescriptor.sampleCount = 1;
-        srcDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
+        srcDescriptor.format = kTextureFormat;
         srcDescriptor.mipLevelCount = srcSpec.level + 1;
         srcDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
         wgpu::Texture srcTexture = device.CreateTexture(&srcDescriptor);
@@ -310,11 +293,9 @@
         } else {
             wgpu::TextureDescriptor dstDescriptor;
             dstDescriptor.dimension = wgpu::TextureDimension::e2D;
-            dstDescriptor.size.width = dstSpec.width;
-            dstDescriptor.size.height = dstSpec.height;
-            dstDescriptor.size.depth = dstSpec.arraySize;
+            dstDescriptor.size = dstSpec.textureSize;
             dstDescriptor.sampleCount = 1;
-            dstDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
+            dstDescriptor.format = kTextureFormat;
             dstDescriptor.mipLevelCount = dstSpec.level + 1;
             dstDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
             dstTexture = device.CreateTexture(&dstDescriptor);
@@ -324,62 +305,68 @@
 
         // Create an upload buffer and use it to populate the current slice of the texture in
         // `level` mip level
-        uint32_t width = srcSpec.width >> srcSpec.level;
-        uint32_t height = srcSpec.height >> srcSpec.level;
-        uint32_t bytesPerRow = Align(kBytesPerTexel * width, kTextureBytesPerRowAlignment);
-        uint32_t texelsPerRow = bytesPerRow / kBytesPerTexel;
-        uint32_t texelCountPerLayer = texelsPerRow * (height - 1) + width;
+        const uint32_t rowsPerImage = srcSpec.textureSize.height >> srcSpec.level;
+        const utils::BufferTextureCopyLayout copyLayout =
+            utils::GetBufferTextureCopyLayoutForTexture2DAtLevel(
+                kTextureFormat,
+                {srcSpec.textureSize.width, srcSpec.textureSize.height, copySize.depth},
+                srcSpec.level, rowsPerImage);
+
+        const std::vector<RGBA8> textureArrayCopyData = GetExpectedTextureData(copyLayout);
 
         // 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,
-                            textureArrayCopyData[slice].data());
-
-            wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(
-                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::Buffer uploadBuffer = utils::CreateBufferFromData(
+            device, textureArrayCopyData.data(), copyLayout.byteLength, wgpu::BufferUsage::CopySrc);
+        uint64_t uploadBufferOffset = 0;
+        for (uint32_t slice = 0; slice < copySize.depth; ++slice) {
+            wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(
+                uploadBuffer, uploadBufferOffset, copyLayout.bytesPerRow, 0);
             wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(
-                srcTexture, srcSpec.level, {0, 0, srcSpec.baseArrayLayer + slice});
-            wgpu::Extent3D bufferCopySize = {width, height, 1};
-
-            encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &bufferCopySize);
+                srcTexture, srcSpec.level, {0, 0, srcSpec.copyOrigin.z + slice});
+            wgpu::Extent3D copyOneLayerSize = {copyLayout.mipSize.width, copyLayout.mipSize.height,
+                                               1};
+            encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyOneLayerSize);
+            uploadBufferOffset += copyLayout.bytesPerImage;
         }
 
         // Perform the texture to texture copy
-        wgpu::TextureCopyView srcTextureCopyView = utils::CreateTextureCopyView(
-            srcTexture, srcSpec.level, {srcSpec.x, srcSpec.y, srcSpec.baseArrayLayer});
-        wgpu::TextureCopyView dstTextureCopyView = utils::CreateTextureCopyView(
-            dstTexture, dstSpec.level, {dstSpec.x, dstSpec.y, dstSpec.baseArrayLayer});
-        wgpu::Extent3D copySize = {copy.width, copy.height, copy.arrayLayerCount};
+        wgpu::TextureCopyView srcTextureCopyView =
+            utils::CreateTextureCopyView(srcTexture, srcSpec.level, srcSpec.copyOrigin);
+        wgpu::TextureCopyView dstTextureCopyView =
+            utils::CreateTextureCopyView(dstTexture, dstSpec.level, dstSpec.copyOrigin);
         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 < copy.arrayLayerCount; ++slice) {
+        const uint32_t texelCountInCopyRegion =
+            copyLayout.texelBlocksPerRow * (copySize.height - 1) + copySize.width;
+        std::vector<RGBA8> expected(texelCountInCopyRegion);
+        for (uint32_t slice = 0; slice < copySize.depth; ++slice) {
             std::fill(expected.begin(), expected.end(), RGBA8());
-            PackTextureData(&textureArrayCopyData[slice][srcSpec.x + srcSpec.y * (bytesPerRow /
-                                                                                  kBytesPerTexel)],
-                            copy.width, copy.height, texelsPerRow, expected.data(), copy.width);
+            const uint32_t texelIndexOffset = copyLayout.texelBlocksPerImage * slice;
+            const uint32_t expectedTexelArrayDataStartIndex =
+                texelIndexOffset +
+                (srcSpec.copyOrigin.x + srcSpec.copyOrigin.y * copyLayout.texelBlocksPerRow);
+            PackTextureData(&textureArrayCopyData[expectedTexelArrayDataStartIndex], copySize.width,
+                            copySize.height, copyLayout.texelBlocksPerRow, expected.data(),
+                            copySize.width);
 
-            EXPECT_TEXTURE_RGBA8_EQ(expected.data(), dstTexture, dstSpec.x, dstSpec.y, copy.width,
-                                    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
+            EXPECT_TEXTURE_RGBA8_EQ(expected.data(), dstTexture, dstSpec.copyOrigin.x,
+                                    dstSpec.copyOrigin.y, copySize.width, copySize.height,
+                                    dstSpec.level, dstSpec.copyOrigin.z + slice)
+                << "Texture to Texture copy failed copying region [(" << srcSpec.copyOrigin.x
+                << ", " << srcSpec.copyOrigin.y << "), (" << srcSpec.copyOrigin.x + copySize.width
+                << ", " << srcSpec.copyOrigin.y + copySize.height << ")) from "
+                << srcSpec.textureSize.width << " x " << srcSpec.textureSize.height
                 << " 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
+                << srcSpec.copyOrigin.z + slice << " to [(" << dstSpec.copyOrigin.x << ", "
+                << dstSpec.copyOrigin.y << "), (" << dstSpec.copyOrigin.x + copySize.width << ", "
+                << dstSpec.copyOrigin.y + copySize.height << ")) region of "
+                << dstSpec.textureSize.width << " x " << dstSpec.textureSize.height
                 << " texture at mip level " << dstSpec.level << " layer "
-                << dstSpec.baseArrayLayer + slice << std::endl;
+                << dstSpec.copyOrigin.z + slice << std::endl;
         }
     }
 };
@@ -431,14 +418,26 @@
 TEST_P(CopyTests_T2B, FullTextureAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
-    DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, MinimumBufferSpec(kWidth, kHeight));
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
 }
 
 // Test that copying an entire texture without 256-byte aligned dimensions works
 TEST_P(CopyTests_T2B, FullTextureUnaligned) {
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
-    DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, MinimumBufferSpec(kWidth, kHeight));
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
 }
 
 // Test that reading pixels from a 256-byte aligned texture works
@@ -446,12 +445,48 @@
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     BufferSpec pixelBuffer = MinimumBufferSpec(1, 1);
-    DoTest({ kWidth, kHeight, 0, 0, 1, 1, 0 },                    pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth - 1, 0, 1, 1, 0 },           pixelBuffer);
-    DoTest({ kWidth, kHeight, 0, kHeight - 1, 1, 1, 0 },          pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth - 1, kHeight - 1, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth / 3, kHeight / 7, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth / 7, kHeight / 3, 1, 1, 0 }, pixelBuffer);
+
+    constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
+    constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, 1};
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.textureSize = kTextureSize;
+    defaultTextureSpec.level = 0;
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {0, 0, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth - 1, 0, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {0, kHeight - 1, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth - 1, kHeight - 1, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth / 3, kHeight / 7, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth / 7, kHeight / 3, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
 }
 
 // Test that copying pixels from a texture that is not 256-byte aligned works
@@ -459,12 +494,48 @@
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
     BufferSpec pixelBuffer = MinimumBufferSpec(1, 1);
-    DoTest({ kWidth, kHeight, 0, 0, 1, 1, 0 },                    pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth - 1, 0, 1, 1, 0 },           pixelBuffer);
-    DoTest({ kWidth, kHeight, 0, kHeight - 1, 1, 1, 0 },          pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth - 1, kHeight - 1, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth / 3, kHeight / 7, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth / 7, kHeight / 3, 1, 1, 0 }, pixelBuffer);
+
+    constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
+    constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, 1};
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.textureSize = kTextureSize;
+    defaultTextureSpec.level = 0;
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {0, 0, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth - 1, 0, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {0, kHeight - 1, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth - 1, kHeight - 1, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth / 3, kHeight / 7, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth / 7, kHeight / 3, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
 }
 
 // Test that copying regions with 256-byte aligned sizes works
@@ -473,7 +544,11 @@
     constexpr uint32_t kHeight = 128;
     for (unsigned int w : {64, 128, 256}) {
         for (unsigned int h : { 16, 32, 48 }) {
-            DoTest({ kWidth, kHeight, 0, 0, w, h, 0 }, MinimumBufferSpec(w, h));
+            TextureSpec textureSpec;
+            textureSpec.copyOrigin = {0, 0, 0};
+            textureSpec.level = 0;
+            textureSpec.textureSize = {kWidth, kHeight, 1};
+            DoTest(textureSpec, MinimumBufferSpec(w, h), {w, h, 1});
         }
     }
 }
@@ -482,9 +557,16 @@
 TEST_P(CopyTests_T2B, TextureRegionUnaligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.level = 0;
+    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
+
     for (unsigned int w : {13, 63, 65}) {
         for (unsigned int h : { 17, 19, 63 }) {
-            DoTest({ kWidth, kHeight, 0, 0, w, h, 0 }, MinimumBufferSpec(w, h));
+            TextureSpec textureSpec = defaultTextureSpec;
+            DoTest(textureSpec, MinimumBufferSpec(w, h), {w, h, 1});
         }
     }
 }
@@ -493,8 +575,16 @@
 TEST_P(CopyTests_T2B, TextureMipAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
+
     for (unsigned int i = 1; i < 4; ++i) {
-        DoTest({ kWidth, kHeight, 0, 0, kWidth >> i, kHeight >> i, i }, MinimumBufferSpec(kWidth >> i, kHeight >> i));
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.level = i;
+        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i),
+               {kWidth >> i, kHeight >> i, 1});
     }
 }
 
@@ -502,8 +592,16 @@
 TEST_P(CopyTests_T2B, TextureMipUnaligned) {
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
+
     for (unsigned int i = 1; i < 4; ++i) {
-        DoTest({ kWidth, kHeight, 0, 0, kWidth >> i, kHeight >> i, i }, MinimumBufferSpec(kWidth >> i, kHeight >> i));
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.level = i;
+        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i),
+               {kWidth >> i, kHeight >> i, 1});
     }
 }
 
@@ -511,12 +609,18 @@
 TEST_P(CopyTests_T2B, OffsetBufferAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
     for (unsigned int i = 0; i < 3; ++i) {
         BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
         uint64_t offset = 512 * i;
         bufferSpec.size += offset;
         bufferSpec.offset += offset;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
@@ -524,11 +628,18 @@
 TEST_P(CopyTests_T2B, OffsetBufferUnaligned) {
     constexpr uint32_t kWidth = 128;
     constexpr uint32_t kHeight = 128;
-    for (uint32_t i = kBytesPerTexel; i < 512; i += kBytesPerTexel * 9) {
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
+    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(kTextureFormat);
+    for (uint32_t i = bytesPerTexel; i < 512; i += bytesPerTexel * 9) {
         BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
         bufferSpec.size += i;
         bufferSpec.offset += i;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
@@ -537,11 +648,18 @@
 TEST_P(CopyTests_T2B, OffsetBufferUnalignedSmallRowPitch) {
     constexpr uint32_t kWidth = 32;
     constexpr uint32_t kHeight = 128;
-    for (uint32_t i = 256 + kBytesPerTexel; i < 512; i += kBytesPerTexel * 9) {
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
+    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(kTextureFormat);
+    for (uint32_t i = 256 + bytesPerTexel; i < 512; i += bytesPerTexel * 9) {
         BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
         bufferSpec.size += i;
         bufferSpec.offset += i;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
@@ -549,11 +667,17 @@
 TEST_P(CopyTests_T2B, RowPitchAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
     BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
     for (unsigned int i = 1; i < 4; ++i) {
         bufferSpec.bytesPerRow += 256;
         bufferSpec.size += 256 * kHeight;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
@@ -562,11 +686,17 @@
 TEST_P(CopyTests_T2B, RowPitchUnaligned) {
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
     BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
     for (unsigned int i = 1; i < 4; ++i) {
         bufferSpec.bytesPerRow += 256;
         bufferSpec.size += 256 * kHeight;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
@@ -575,7 +705,30 @@
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
-    DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0, kLayers }, MinimumBufferSpec(kWidth, kHeight));
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, kLayers};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kLayers), {kWidth, kHeight, kLayers});
+}
+
+// Test that copying a sub-region of each texture 2D array layer works
+TEST_P(CopyTests_T2B, Texture2DArraySubRegion) {
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    constexpr uint32_t kLayers = 6u;
+    constexpr uint32_t kBaseLayer = 2u;
+    constexpr uint32_t kCopyLayers = 3u;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, kBaseLayer};
+    textureSpec.textureSize = {kWidth, kHeight, kLayers};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kCopyLayers),
+           {kWidth, kHeight, kCopyLayers});
 }
 
 // Test that copying texture 2D array mips with 256-byte aligned sizes works
@@ -583,8 +736,17 @@
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};
+
     for (unsigned int i = 1; i < 4; ++i) {
-        DoTest({ kWidth, kHeight, 0, 0, kWidth >> i, kHeight >> i, i, kLayers }, MinimumBufferSpec(kWidth >> i, kHeight >> i));
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.level = i;
+
+        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i, kLayers),
+               {kWidth >> i, kHeight >> i, kLayers});
     }
 }
 
@@ -594,14 +756,26 @@
 TEST_P(CopyTests_B2T, FullTextureAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
-    DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, MinimumBufferSpec(kWidth, kHeight));
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
 }
 
 // Test that copying an entire texture without 256-byte aligned dimensions works
 TEST_P(CopyTests_B2T, FullTextureUnaligned) {
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
-    DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, MinimumBufferSpec(kWidth, kHeight));
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
 }
 
 // Test that reading pixels from a 256-byte aligned texture works
@@ -609,12 +783,48 @@
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     BufferSpec pixelBuffer = MinimumBufferSpec(1, 1);
-    DoTest({ kWidth, kHeight, 0, 0, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth - 1, 0, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, 0, kHeight - 1, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth - 1, kHeight - 1, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth / 3, kHeight / 7, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth / 7, kHeight / 3, 1, 1, 0 }, pixelBuffer);
+
+    constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
+    constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, 1};
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.textureSize = kTextureSize;
+    defaultTextureSpec.level = 0;
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {0, 0, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth - 1, 0, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {0, kHeight - 1, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth - 1, kHeight - 1, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth / 3, kHeight / 7, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth / 7, kHeight / 3, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
 }
 
 // Test that copying pixels from a texture that is not 256-byte aligned works
@@ -622,12 +832,48 @@
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
     BufferSpec pixelBuffer = MinimumBufferSpec(1, 1);
-    DoTest({ kWidth, kHeight, 0, 0, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth - 1, 0, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, 0, kHeight - 1, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth - 1, kHeight - 1, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth / 3, kHeight / 7, 1, 1, 0 }, pixelBuffer);
-    DoTest({ kWidth, kHeight, kWidth / 7, kHeight / 3, 1, 1, 0 }, pixelBuffer);
+
+    constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
+    constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, 1};
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.textureSize = kTextureSize;
+    defaultTextureSpec.level = 0;
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {0, 0, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth - 1, 0, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {0, kHeight - 1, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth - 1, kHeight - 1, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth / 3, kHeight / 7, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
+
+    {
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.copyOrigin = {kWidth / 7, kHeight / 3, 0};
+        DoTest(textureSpec, pixelBuffer, kCopySize);
+    }
 }
 
 // Test that copying regions with 256-byte aligned sizes works
@@ -636,7 +882,11 @@
     constexpr uint32_t kHeight = 128;
     for (unsigned int w : {64, 128, 256}) {
         for (unsigned int h : { 16, 32, 48 }) {
-            DoTest({ kWidth, kHeight, 0, 0, w, h, 0 }, MinimumBufferSpec(w, h));
+            TextureSpec textureSpec;
+            textureSpec.copyOrigin = {0, 0, 0};
+            textureSpec.level = 0;
+            textureSpec.textureSize = {kWidth, kHeight, 1};
+            DoTest(textureSpec, MinimumBufferSpec(w, h), {w, h, 1});
         }
     }
 }
@@ -645,9 +895,16 @@
 TEST_P(CopyTests_B2T, TextureRegionUnaligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.level = 0;
+    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
+
     for (unsigned int w : {13, 63, 65}) {
-        for (unsigned int h : { 17, 19, 63 }) {
-            DoTest({ kWidth, kHeight, 0, 0, w, h, 0 }, MinimumBufferSpec(w, h));
+        for (unsigned int h : {17, 19, 63}) {
+            TextureSpec textureSpec = defaultTextureSpec;
+            DoTest(textureSpec, MinimumBufferSpec(w, h), {w, h, 1});
         }
     }
 }
@@ -656,8 +913,16 @@
 TEST_P(CopyTests_B2T, TextureMipAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
+
     for (unsigned int i = 1; i < 4; ++i) {
-        DoTest({ kWidth, kHeight, 0, 0, kWidth >> i, kHeight >> i, i }, MinimumBufferSpec(kWidth >> i, kHeight >> i));
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.level = i;
+        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i),
+               {kWidth >> i, kHeight >> i, 1});
     }
 }
 
@@ -665,8 +930,16 @@
 TEST_P(CopyTests_B2T, TextureMipUnaligned) {
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
+
     for (unsigned int i = 1; i < 4; ++i) {
-        DoTest({ kWidth, kHeight, 0, 0, kWidth >> i, kHeight >> i, i }, MinimumBufferSpec(kWidth >> i, kHeight >> i));
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.level = i;
+        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i),
+               {kWidth >> i, kHeight >> i, 1});
     }
 }
 
@@ -674,12 +947,18 @@
 TEST_P(CopyTests_B2T, OffsetBufferAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
     for (unsigned int i = 0; i < 3; ++i) {
         BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
         uint64_t offset = 512 * i;
         bufferSpec.size += offset;
         bufferSpec.offset += offset;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
@@ -687,11 +966,18 @@
 TEST_P(CopyTests_B2T, OffsetBufferUnaligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
-    for (uint32_t i = kBytesPerTexel; i < 512; i += kBytesPerTexel * 9) {
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
+    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(kTextureFormat);
+    for (uint32_t i = bytesPerTexel; i < 512; i += bytesPerTexel * 9) {
         BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
         bufferSpec.size += i;
         bufferSpec.offset += i;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
@@ -700,11 +986,18 @@
 TEST_P(CopyTests_B2T, OffsetBufferUnalignedSmallRowPitch) {
     constexpr uint32_t kWidth = 32;
     constexpr uint32_t kHeight = 128;
-    for (uint32_t i = 256 + kBytesPerTexel; i < 512; i += kBytesPerTexel * 9) {
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
+    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(kTextureFormat);
+    for (uint32_t i = 256 + bytesPerTexel; i < 512; i += bytesPerTexel * 9) {
         BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
         bufferSpec.size += i;
         bufferSpec.offset += i;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
@@ -712,11 +1005,17 @@
 TEST_P(CopyTests_B2T, RowPitchAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
     BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
     for (unsigned int i = 1; i < 4; ++i) {
         bufferSpec.bytesPerRow += 256;
         bufferSpec.size += 256 * kHeight;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
@@ -725,28 +1024,77 @@
 TEST_P(CopyTests_B2T, RowPitchUnaligned) {
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    textureSpec.level = 0;
+
     BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
     for (unsigned int i = 1; i < 4; ++i) {
         bufferSpec.bytesPerRow += 256;
         bufferSpec.size += 256 * kHeight;
-        DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec);
+        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
     }
 }
 
+// Test that copying into regions of each texture 2D array layer works
+TEST_P(CopyTests_B2T, Texture2DArrayRegion) {
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    constexpr uint32_t kLayers = 6u;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {kWidth, kHeight, kLayers};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kLayers), {kWidth, kHeight, kLayers});
+}
+
+// Test that copying into a sub-region of each texture 2D array layer works
+TEST_P(CopyTests_B2T, Texture2DArraySubRegion) {
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    constexpr uint32_t kLayers = 6u;
+    constexpr uint32_t kBaseLayer = 2u;
+    constexpr uint32_t kCopyLayers = 3u;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, kBaseLayer};
+    textureSpec.textureSize = {kWidth, kHeight, kLayers};
+    textureSpec.level = 0;
+
+    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kCopyLayers),
+           {kWidth, kHeight, kCopyLayers});
+}
+
 DAWN_INSTANTIATE_TEST(CopyTests_B2T, D3D12Backend(), MetalBackend(), OpenGLBackend(), VulkanBackend());
 
 TEST_P(CopyTests_T2T, Texture) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
-    DoTest({kWidth, kHeight, 0, 0, 0}, {kWidth, kHeight, 0, 0, 0}, {kWidth, kHeight});
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1});
 }
 
 TEST_P(CopyTests_T2T, TextureRegion) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.level = 0;
+    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
+
     for (unsigned int w : {64, 128, 256}) {
         for (unsigned int h : {16, 32, 48}) {
-            DoTest({kWidth, kHeight, 0, 0, 0, 1}, {kWidth, kHeight, 0, 0, 0, 1}, {w, h});
+            TextureSpec textureSpec = defaultTextureSpec;
+            DoTest(textureSpec, textureSpec, {w, h, 1});
         }
     }
 }
@@ -759,8 +1107,13 @@
     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, kLayers});
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+    textureSpec.textureSize = {kWidth, kHeight, kLayers};
+
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, kLayers});
 }
 
 // Test copying a subresource region of the 2D array texture.
@@ -771,10 +1124,16 @@
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.level = 0;
+    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};
+
     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, kLayers});
+            TextureSpec textureSpec = defaultTextureSpec;
+            DoTest(textureSpec, textureSpec, {w, h, kLayers});
         }
     }
 }
@@ -786,8 +1145,19 @@
     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});
+    constexpr uint32_t kCopyArrayLayerCount = 1u;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};
+    defaultTextureSpec.level = 0;
+
+    TextureSpec srcTextureSpec = defaultTextureSpec;
+    srcTextureSpec.copyOrigin = {0, 0, kSrcBaseLayer};
+
+    TextureSpec dstTextureSpec = defaultTextureSpec;
+    dstTextureSpec.copyOrigin = {0, 0, kDstBaseLayer};
+
+    DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyArrayLayerCount});
 }
 
 // Test copying multiple contiguous slices of a 2D array texture.
@@ -801,9 +1171,18 @@
     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});
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};
+    defaultTextureSpec.level = 0;
+
+    TextureSpec srcTextureSpec = defaultTextureSpec;
+    srcTextureSpec.copyOrigin = {0, 0, kSrcBaseLayer};
+
+    TextureSpec dstTextureSpec = defaultTextureSpec;
+    dstTextureSpec.copyOrigin = {0, 0, kDstBaseLayer};
+
+    DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyArrayLayerCount});
 }
 
 // Test copying one texture slice within the same texture.
@@ -814,9 +1193,18 @@
     constexpr uint32_t kSrcBaseLayer = 0u;
     constexpr uint32_t kDstBaseLayer = 3u;
     constexpr uint32_t kCopyArrayLayerCount = 1u;
-    DoTest({kWidth, kHeight, 0, 0, 0, kLayers, kSrcBaseLayer},
-           {kWidth, kHeight, 0, 0, 0, kLayers, kDstBaseLayer},
-           {kWidth, kHeight, kCopyArrayLayerCount}, true);
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};
+    defaultTextureSpec.level = 0;
+
+    TextureSpec srcTextureSpec = defaultTextureSpec;
+    srcTextureSpec.copyOrigin = {0, 0, kSrcBaseLayer};
+
+    TextureSpec dstTextureSpec = defaultTextureSpec;
+    dstTextureSpec.copyOrigin = {0, 0, kDstBaseLayer};
+
+    DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyArrayLayerCount}, true);
 }
 
 // Test copying multiple contiguous texture slices within the same texture with non-overlapped
@@ -831,34 +1219,73 @@
     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}, true);
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};
+    defaultTextureSpec.level = 0;
+
+    TextureSpec srcTextureSpec = defaultTextureSpec;
+    srcTextureSpec.copyOrigin = {0, 0, kSrcBaseLayer};
+
+    TextureSpec dstTextureSpec = defaultTextureSpec;
+    dstTextureSpec.copyOrigin = {0, 0, kDstBaseLayer};
+
+    DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyArrayLayerCount}, true);
 }
 
 TEST_P(CopyTests_T2T, TextureMip) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
+
     for (unsigned int i = 1; i < 4; ++i) {
-        DoTest({kWidth, kHeight, 0, 0, i}, {kWidth, kHeight, 0, 0, i}, {kWidth >> i, kHeight >> i});
+        TextureSpec textureSpec = defaultTextureSpec;
+        textureSpec.level = i;
+
+        DoTest(textureSpec, textureSpec, {kWidth >> i, kHeight >> i, 1});
     }
 }
 
 TEST_P(CopyTests_T2T, SingleMipSrcMultipleMipDst) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+
     for (unsigned int i = 1; i < 4; ++i) {
-        DoTest({kWidth >> i, kHeight >> i, 0, 0, 0}, {kWidth, kHeight, 0, 0, i},
-               {kWidth >> i, kHeight >> i});
+        TextureSpec srcTextureSpec = defaultTextureSpec;
+        srcTextureSpec.textureSize = {kWidth >> i, kHeight >> i, 1};
+        srcTextureSpec.level = 0;
+
+        TextureSpec dstTextureSpec = defaultTextureSpec;
+        dstTextureSpec.textureSize = {kWidth, kHeight, 1};
+        dstTextureSpec.level = i;
+
+        DoTest(srcTextureSpec, dstTextureSpec, {kWidth >> i, kHeight >> i, 1});
     }
 }
 
 TEST_P(CopyTests_T2T, MultipleMipSrcSingleMipDst) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
+
+    TextureSpec defaultTextureSpec;
+    defaultTextureSpec.copyOrigin = {0, 0, 0};
+
     for (unsigned int i = 1; i < 4; ++i) {
-        DoTest({kWidth, kHeight, 0, 0, i}, {kWidth >> i, kHeight >> i, 0, 0, 0},
-               {kWidth >> i, kHeight >> i});
+        TextureSpec srcTextureSpec = defaultTextureSpec;
+        srcTextureSpec.textureSize = {kWidth, kHeight, 1};
+        srcTextureSpec.level = i;
+
+        TextureSpec dstTextureSpec = defaultTextureSpec;
+        dstTextureSpec.textureSize = {kWidth >> i, kHeight >> i, 1};
+        dstTextureSpec.level = 0;
+
+        DoTest(srcTextureSpec, dstTextureSpec, {kWidth >> i, kHeight >> i, 1});
     }
 }
 
diff --git a/src/utils/WGPUHelpers.cpp b/src/utils/WGPUHelpers.cpp
index d3ee2c0..b44b866 100644
--- a/src/utils/WGPUHelpers.cpp
+++ b/src/utils/WGPUHelpers.cpp
@@ -17,6 +17,8 @@
 #include "common/Assert.h"
 #include "common/Constants.h"
 #include "common/Log.h"
+#include "common/Math.h"
+#include "utils/TextureFormatUtils.h"
 
 #include <shaderc/shaderc.hpp>
 
@@ -372,4 +374,44 @@
         return device.CreateBindGroup(&descriptor);
     }
 
+    uint32_t GetMinimumBytesPerRow(wgpu::TextureFormat format, uint32_t width) {
+        const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(format);
+        return Align(bytesPerTexel * width, kTextureBytesPerRowAlignment);
+    }
+
+    uint32_t GetBytesInBufferTextureCopy(wgpu::TextureFormat format,
+                                         uint32_t width,
+                                         uint32_t bytesPerRow,
+                                         uint32_t rowsPerImage,
+                                         uint32_t copyArrayLayerCount) {
+        const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(format);
+        const uint32_t bytesAtLastImage = bytesPerRow * (rowsPerImage - 1) + bytesPerTexel * width;
+        return bytesPerRow * rowsPerImage * (copyArrayLayerCount - 1) + bytesAtLastImage;
+    }
+
+    // TODO(jiawei.shao@intel.com): support compressed texture formats
+    BufferTextureCopyLayout GetBufferTextureCopyLayoutForTexture2DAtLevel(
+        wgpu::TextureFormat format,
+        wgpu::Extent3D textureSizeAtLevel0,
+        uint32_t mipmapLevel,
+        uint32_t rowsPerImage) {
+        BufferTextureCopyLayout layout;
+
+        layout.mipSize = {textureSizeAtLevel0.width >> mipmapLevel,
+                          textureSizeAtLevel0.height >> mipmapLevel, textureSizeAtLevel0.depth};
+
+        layout.bytesPerRow = GetMinimumBytesPerRow(format, layout.mipSize.width);
+        layout.bytesPerImage = layout.bytesPerRow * rowsPerImage;
+
+        layout.byteLength =
+            GetBytesInBufferTextureCopy(format, layout.mipSize.width, layout.bytesPerRow,
+                                        layout.mipSize.height, textureSizeAtLevel0.depth);
+
+        const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(format);
+        layout.texelBlocksPerRow = layout.bytesPerRow / bytesPerTexel;
+        layout.texelBlocksPerImage = layout.bytesPerImage / bytesPerTexel;
+        layout.texelBlockCount = layout.byteLength / bytesPerTexel;
+
+        return layout;
+    }
 }  // namespace utils
diff --git a/src/utils/WGPUHelpers.h b/src/utils/WGPUHelpers.h
index 8c756bc..fdbd864 100644
--- a/src/utils/WGPUHelpers.h
+++ b/src/utils/WGPUHelpers.h
@@ -129,6 +129,28 @@
         const wgpu::BindGroupLayout& layout,
         std::initializer_list<BindingInitializationHelper> entriesInitializer);
 
+    struct BufferTextureCopyLayout {
+        uint64_t byteLength;
+        uint64_t texelBlockCount;
+        uint32_t bytesPerRow;
+        uint32_t texelBlocksPerRow;
+        uint32_t bytesPerImage;
+        uint32_t texelBlocksPerImage;
+        wgpu::Extent3D mipSize;
+    };
+
+    uint32_t GetMinimumBytesPerRow(wgpu::TextureFormat format, uint32_t width);
+    uint32_t GetBytesInBufferTextureCopy(wgpu::TextureFormat format,
+                                         uint32_t width,
+                                         uint32_t bytesPerRow,
+                                         uint32_t rowsPerImage,
+                                         uint32_t copyArrayLayerCount);
+    BufferTextureCopyLayout GetBufferTextureCopyLayoutForTexture2DAtLevel(
+        wgpu::TextureFormat format,
+        wgpu::Extent3D textureSizeAtLevel0,
+        uint32_t mipmapLevel,
+        uint32_t rowsPerImage);
+
 }  // namespace utils
 
 #endif  // UTILS_DAWNHELPERS_H_