Added validation tests for copying to 2d-array textures

Added validation tests for CommandEncoder::CopyB2T, CommandEncoder::CopyT2B and
Queue::WriteTexture which involve copying or writing to multiple layers
of a 2d-array texture.

Bug: dawn:483
Change-Id: I23f580dff86cd2512a94b41c9c0ce795122a045e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/24443
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Tomek Ponitka <tommek@google.com>
diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
index 831b934..1cd9e28 100644
--- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
+++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
@@ -16,6 +16,7 @@
 #include "common/Constants.h"
 #include "common/Math.h"
 #include "tests/unittests/validation/ValidationTest.h"
+#include "utils/TextureFormatUtils.h"
 #include "utils/WGPUHelpers.h"
 
 class CopyCommandTest : public ValidationTest {
@@ -53,7 +54,7 @@
         uint32_t height,
         uint32_t depth,
         wgpu::TextureFormat format = wgpu::TextureFormat::RGBA8Unorm) {
-        uint32_t bytesPerPixel = utils::TextureFormatPixelSize(format);
+        uint32_t bytesPerPixel = utils::GetTexelBlockSizeInBytes(format);
         uint32_t bytesPerRow = Align(width * bytesPerPixel, kTextureBytesPerRowAlignment);
         return (bytesPerRow * (height - 1) + width * bytesPerPixel) * depth;
     }
@@ -124,6 +125,55 @@
 
         ValidateExpectation(encoder, expectation);
     }
+
+    void TestBothTBCopies(utils::Expectation expectation,
+                          wgpu::Buffer buffer,
+                          uint64_t bufferOffset,
+                          uint32_t bufferBytesPerRow,
+                          uint32_t rowsPerImage,
+                          wgpu::Texture texture,
+                          uint32_t level,
+                          wgpu::Origin3D origin,
+                          wgpu::Extent3D extent3D) {
+        TestB2TCopy(expectation, buffer, bufferOffset, bufferBytesPerRow, rowsPerImage, texture,
+                    level, origin, extent3D);
+        TestT2BCopy(expectation, texture, level, origin, buffer, bufferOffset, bufferBytesPerRow,
+                    rowsPerImage, extent3D);
+    }
+
+    void TestBothT2TCopies(utils::Expectation expectation,
+                           wgpu::Texture texture1,
+                           uint32_t level1,
+                           wgpu::Origin3D origin1,
+                           wgpu::Texture texture2,
+                           uint32_t level2,
+                           wgpu::Origin3D origin2,
+                           wgpu::Extent3D extent3D) {
+        TestT2TCopy(expectation, texture1, level1, origin1, texture2, level2, origin2, extent3D);
+        TestT2TCopy(expectation, texture2, level2, origin2, texture1, level1, origin1, extent3D);
+    }
+
+    void TestBothTBCopiesExactBufferSize(uint32_t bufferBytesPerRow,
+                                         uint32_t rowsPerImage,
+                                         wgpu::Texture texture,
+                                         wgpu::TextureFormat textureFormat,
+                                         wgpu::Origin3D origin,
+                                         wgpu::Extent3D extent3D) {
+        // Check the minimal valid bufferSize.
+        uint64_t bufferSize =
+            utils::RequiredBytesInCopy(bufferBytesPerRow, rowsPerImage, extent3D, textureFormat);
+        wgpu::Buffer source =
+            CreateBuffer(bufferSize, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
+        TestBothTBCopies(utils::Expectation::Success, source, 0, bufferBytesPerRow, rowsPerImage,
+                         texture, 0, origin, extent3D);
+
+        // Check bufferSize was indeed minimal.
+        uint64_t invalidSize = bufferSize - 1;
+        wgpu::Buffer invalidSource =
+            CreateBuffer(invalidSize, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
+        TestBothTBCopies(utils::Expectation::Failure, invalidSource, 0, bufferBytesPerRow,
+                         rowsPerImage, texture, 0, origin, extent3D);
+    }
 };
 
 class CopyCommandTest_B2B : public CopyCommandTest {};
@@ -1344,33 +1394,6 @@
                                                 kUsage, 1);
     }
 
-    void TestBothTBCopies(utils::Expectation expectation,
-                          wgpu::Buffer buffer,
-                          uint64_t bufferOffset,
-                          uint32_t bufferBytesPerRow,
-                          uint32_t rowsPerImage,
-                          wgpu::Texture texture,
-                          uint32_t level,
-                          wgpu::Origin3D origin,
-                          wgpu::Extent3D extent3D) {
-        TestB2TCopy(expectation, buffer, bufferOffset, bufferBytesPerRow, rowsPerImage, texture,
-                    level, origin, extent3D);
-        TestT2BCopy(expectation, texture, level, origin, buffer, bufferOffset, bufferBytesPerRow,
-                    rowsPerImage, extent3D);
-    }
-
-    void TestBothT2TCopies(utils::Expectation expectation,
-                           wgpu::Texture texture1,
-                           uint32_t level1,
-                           wgpu::Origin3D origin1,
-                           wgpu::Texture texture2,
-                           uint32_t level2,
-                           wgpu::Origin3D origin2,
-                           wgpu::Extent3D extent3D) {
-        TestT2TCopy(expectation, texture1, level1, origin1, texture2, level2, origin2, extent3D);
-        TestT2TCopy(expectation, texture2, level2, origin2, texture1, level1, origin1, extent3D);
-    }
-
     static constexpr uint32_t kWidth = 16;
     static constexpr uint32_t kHeight = 16;
 };
@@ -1386,14 +1409,14 @@
 
         // Valid usages of BufferOffset in B2T and T2B copies with compressed texture formats.
         {
-            uint32_t validBufferOffset = utils::CompressedFormatBlockSizeInBytes(bcFormat);
+            uint32_t validBufferOffset = utils::GetTexelBlockSizeInBytes(bcFormat);
             TestBothTBCopies(utils::Expectation::Success, buffer, validBufferOffset, 256, 4,
                              texture, 0, {0, 0, 0}, {4, 4, 1});
         }
 
         // Failures on invalid bufferOffset.
         {
-            uint32_t kInvalidBufferOffset = utils::CompressedFormatBlockSizeInBytes(bcFormat) / 2;
+            uint32_t kInvalidBufferOffset = utils::GetTexelBlockSizeInBytes(bcFormat) / 2;
             TestBothTBCopies(utils::Expectation::Failure, buffer, kInvalidBufferOffset, 256, 4,
                              texture, 0, {0, 0, 0}, {4, 4, 1});
         }
@@ -1426,7 +1449,7 @@
             for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
                 wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
                 uint32_t inValidBytesPerRow =
-                    kTestWidth / 4 * utils::CompressedFormatBlockSizeInBytes(bcFormat);
+                    kTestWidth / 4 * utils::GetTexelBlockSizeInBytes(bcFormat);
                 ASSERT_NE(0u, inValidBytesPerRow % 256);
                 TestBothTBCopies(utils::Expectation::Failure, buffer, 0, inValidBytesPerRow, 4,
                                  texture, 0, {0, 0, 0}, {kTestWidth, 4, 1});
@@ -1438,7 +1461,7 @@
             for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
                 wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
                 uint32_t smallestValidBytesPerRow =
-                    Align(kTestWidth / 4 * utils::CompressedFormatBlockSizeInBytes(bcFormat), 256);
+                    Align(kTestWidth / 4 * utils::GetTexelBlockSizeInBytes(bcFormat), 256);
                 TestBothTBCopies(utils::Expectation::Success, buffer, 0, smallestValidBytesPerRow,
                                  4, texture, 0, {0, 0, 0}, {kTestWidth, 4, 1});
             }
@@ -1574,3 +1597,66 @@
         }
     }
 }
+
+// Test copies between buffer and multiple array layers of an uncompressed texture
+TEST_F(CopyCommandTest, CopyToMultipleArrayLayers) {
+    wgpu::Texture destination =
+        CopyCommandTest::Create2DTexture(4, 2, 1, 5, wgpu::TextureFormat::RGBA8Unorm,
+                                         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);
+
+    // Copy to all array layers
+    TestBothTBCopiesExactBufferSize(256, 2, destination, wgpu::TextureFormat::RGBA8Unorm, {0, 0, 0},
+                                    {4, 2, 5});
+
+    // Copy to the highest array layer
+    TestBothTBCopiesExactBufferSize(256, 2, destination, wgpu::TextureFormat::RGBA8Unorm, {0, 0, 4},
+                                    {4, 2, 1});
+
+    // Copy to array layers in the middle
+    TestBothTBCopiesExactBufferSize(256, 2, destination, wgpu::TextureFormat::RGBA8Unorm, {0, 0, 1},
+                                    {4, 2, 3});
+
+    // Copy with a non-packed rowsPerImage
+    TestBothTBCopiesExactBufferSize(256, 3, destination, wgpu::TextureFormat::RGBA8Unorm, {0, 0, 0},
+                                    {4, 2, 5});
+
+    // Copy with bytesPerRow = 512
+    TestBothTBCopiesExactBufferSize(512, 2, destination, wgpu::TextureFormat::RGBA8Unorm, {0, 0, 1},
+                                    {4, 2, 3});
+}
+
+// Test copies between buffer and multiple array layers of a compressed texture
+TEST_F(CopyCommandTest_CompressedTextureFormats, CopyToMultipleArrayLayers) {
+    for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
+        wgpu::Texture texture = CopyCommandTest::Create2DTexture(
+            12, 16, 1, 20, bcFormat, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);
+
+        // Copy to all array layers
+        TestBothTBCopiesExactBufferSize(256, 16, texture, bcFormat, {0, 0, 0}, {12, 16, 20});
+
+        // Copy to the highest array layer
+        TestBothTBCopiesExactBufferSize(256, 16, texture, bcFormat, {0, 0, 19}, {12, 16, 1});
+
+        // Copy to array layers in the middle
+        TestBothTBCopiesExactBufferSize(256, 16, texture, bcFormat, {0, 0, 1}, {12, 16, 18});
+
+        // Copy touching the texture corners with a non-packed rowsPerImage
+        TestBothTBCopiesExactBufferSize(256, 24, texture, bcFormat, {4, 4, 4}, {8, 12, 16});
+
+        // rowsPerImage needs to be a multiple of blockHeight
+        {
+            wgpu::Buffer source =
+                CreateBuffer(8192, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
+            TestBothTBCopies(utils::Expectation::Failure, source, 0, 256, 6, texture, 0, {0, 0, 0},
+                             {4, 4, 1});
+        }
+
+        // rowsPerImage must be a multiple of blockHeight even with an empty copy
+        {
+            wgpu::Buffer source =
+                CreateBuffer(0, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
+            TestBothTBCopies(utils::Expectation::Failure, source, 0, 256, 2, texture, 0, {0, 0, 0},
+                             {0, 0, 0});
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
index 74e4eda..039da11 100644
--- a/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
+++ b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
@@ -15,6 +15,7 @@
 #include "tests/unittests/validation/ValidationTest.h"
 
 #include "common/Math.h"
+#include "utils/TextureFormatUtils.h"
 #include "utils/WGPUHelpers.h"
 
 namespace {
@@ -45,21 +46,6 @@
             return tex;
         }
 
-        uint64_t RequiredBytesInCopy(uint32_t bytesPerRow,
-                                     uint32_t rowsPerImage,
-                                     wgpu::Extent3D copyExtent,
-                                     wgpu::TextureFormat format = wgpu::TextureFormat::RGBA8Unorm) {
-            if (copyExtent.width == 0 || copyExtent.height == 0 || copyExtent.depth == 0) {
-                return 0;
-            } else {
-                uint64_t bytesPerImage = bytesPerRow * rowsPerImage;
-                uint64_t bytesInLastSlice =
-                    bytesPerRow * (copyExtent.height - 1) +
-                    (copyExtent.width * utils::TextureFormatPixelSize(format));
-                return bytesPerImage * (copyExtent.depth - 1) + bytesInLastSlice;
-            }
-        }
-
         void TestWriteTexture(size_t dataSize,
                               uint32_t dataOffset,
                               uint32_t dataBytesPerRow,
@@ -81,12 +67,30 @@
             queue.WriteTexture(&textureCopyView, data.data(), dataSize, &textureDataLayout, &size);
         }
 
+        void TestWriteTextureExactDataSize(uint32_t bytesPerRow,
+                                           uint32_t rowsPerImage,
+                                           wgpu::Texture texture,
+                                           wgpu::TextureFormat textureFormat,
+                                           wgpu::Origin3D origin,
+                                           wgpu::Extent3D extent3D) {
+            // Check the minimal valid dataSize.
+            uint64_t dataSize =
+                utils::RequiredBytesInCopy(bytesPerRow, rowsPerImage, extent3D, textureFormat);
+            TestWriteTexture(dataSize, 0, bytesPerRow, rowsPerImage, texture, 0, origin, extent3D);
+
+            // Check dataSize was indeed minimal.
+            uint64_t invalidSize = dataSize - 1;
+            ASSERT_DEVICE_ERROR(TestWriteTexture(invalidSize, 0, bytesPerRow, rowsPerImage, texture,
+                                                 0, origin, extent3D));
+        }
+
         wgpu::Queue queue;
     };
 
     // Test the success case for WriteTexture
     TEST_F(QueueWriteTextureValidationTest, Success) {
-        const uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        const uint64_t dataSize =
+            utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
         wgpu::Texture destination = Create2DTexture({16, 16, 4}, 5, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst);
 
@@ -131,7 +135,8 @@
 
     // Test OOB conditions on the data
     TEST_F(QueueWriteTextureValidationTest, OutOfBoundsOnData) {
-        const uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        const uint64_t dataSize =
+            utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
         wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst);
 
@@ -143,14 +148,15 @@
         ASSERT_DEVICE_ERROR(
             TestWriteTexture(dataSize, 4, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1}));
 
-        // OOB on the data because RequiredBytesInCopy overflows
+        // OOB on the data because utils::RequiredBytesInCopy overflows
         ASSERT_DEVICE_ERROR(
             TestWriteTexture(dataSize, 0, 512, 0, destination, 0, {0, 0, 0}, {4, 3, 1}));
 
         // Not OOB on the data although bytes per row * height overflows
-        // but RequiredBytesInCopy * depth does not overflow
+        // but utils::RequiredBytesInCopy * depth does not overflow
         {
-            uint32_t sourceDataSize = RequiredBytesInCopy(256, 0, {7, 3, 1});
+            uint32_t sourceDataSize =
+                utils::RequiredBytesInCopy(256, 0, {7, 3, 1}, wgpu::TextureFormat::RGBA8Unorm);
             ASSERT_TRUE(256 * 3 > sourceDataSize) << "bytes per row * height should overflow data";
 
             TestWriteTexture(sourceDataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {7, 3, 1});
@@ -159,7 +165,8 @@
 
     // Test OOB conditions on the texture
     TEST_F(QueueWriteTextureValidationTest, OutOfBoundsOnTexture) {
-        const uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        const uint64_t dataSize =
+            utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
         wgpu::Texture destination = Create2DTexture({16, 16, 2}, 5, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst);
 
@@ -186,7 +193,8 @@
 
     // Test that we force Depth=1 on writes to 2D textures
     TEST_F(QueueWriteTextureValidationTest, DepthConstraintFor2DTextures) {
-        const uint64_t dataSize = RequiredBytesInCopy(0, 0, {0, 0, 2});
+        const uint64_t dataSize =
+            utils::RequiredBytesInCopy(0, 0, {0, 0, 2}, wgpu::TextureFormat::RGBA8Unorm);
         wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst);
 
@@ -197,7 +205,8 @@
 
     // Test WriteTexture with incorrect texture usage
     TEST_F(QueueWriteTextureValidationTest, IncorrectUsage) {
-        const uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        const uint64_t dataSize =
+            utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
         wgpu::Texture sampled = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
                                                 wgpu::TextureUsage::Sampled);
 
@@ -226,7 +235,8 @@
 
     // Test that if rowsPerImage is greater than 0, it must be at least copy height.
     TEST_F(QueueWriteTextureValidationTest, ImageHeightConstraint) {
-        uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        uint64_t dataSize =
+            utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
         wgpu::Texture destination = Create2DTexture({16, 16, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst);
 
@@ -246,7 +256,8 @@
 
     // Test WriteTexture with incorrect data offset usage
     TEST_F(QueueWriteTextureValidationTest, IncorrectDataOffset) {
-        uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        uint64_t dataSize =
+            utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
         wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst);
 
@@ -259,7 +270,8 @@
 
     // Test multisampled textures can be used in WriteTexture.
     TEST_F(QueueWriteTextureValidationTest, WriteToMultisampledTexture) {
-        uint64_t dataSize = RequiredBytesInCopy(256, 0, {2, 2, 1});
+        uint64_t dataSize =
+            utils::RequiredBytesInCopy(256, 0, {2, 2, 1}, wgpu::TextureFormat::RGBA8Unorm);
         wgpu::Texture destination = Create2DTexture({2, 2, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst, 4);
 
@@ -316,11 +328,11 @@
         {
             for (wgpu::TextureFormat format : kFormats) {
                 uint32_t validDataSize =
-                    RequiredBytesInCopy(kBytesPerRow, 0, {kWidth, kHeight, 1}, format);
+                    utils::RequiredBytesInCopy(kBytesPerRow, 0, {kWidth, kHeight, 1}, format);
                 wgpu::Texture destination =
                     Create2DTexture({kWidth, kHeight, 1}, 1, format, wgpu::TextureUsage::CopyDst);
 
-                // Verify the return value of RequiredBytesInCopu() is exactly the minimum valid
+                // Verify the return value of RequiredBytesInCopy() is exactly the minimum valid
                 // data size in this test.
                 {
                     uint32_t invalidDataSize = validDataSize - 1;
@@ -339,7 +351,8 @@
 
     // Test write from data to mip map of non square texture
     TEST_F(QueueWriteTextureValidationTest, WriteToMipmapOfNonSquareTexture) {
-        uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 2, 1});
+        uint64_t dataSize =
+            utils::RequiredBytesInCopy(256, 0, {4, 2, 1}, wgpu::TextureFormat::RGBA8Unorm);
         uint32_t maxMipmapLevel = 3;
         wgpu::Texture destination =
             Create2DTexture({4, 2, 1}, maxMipmapLevel, wgpu::TextureFormat::RGBA8Unorm,
@@ -362,6 +375,33 @@
                                              {0, 0, 0}, {2, 2, 1}));
     }
 
+    // Test writes to multiple array layers of an uncompressed texture
+    TEST_F(QueueWriteTextureValidationTest, WriteToMultipleArrayLayers) {
+        wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
+            {4, 2, 5}, 1, wgpu::TextureFormat::RGBA8Unorm,
+            wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);
+
+        // Write to all array layers
+        TestWriteTextureExactDataSize(256, 2, destination, wgpu::TextureFormat::RGBA8Unorm,
+                                      {0, 0, 0}, {4, 2, 5});
+
+        // Write to the highest array layer
+        TestWriteTextureExactDataSize(256, 2, destination, wgpu::TextureFormat::RGBA8Unorm,
+                                      {0, 0, 4}, {4, 2, 1});
+
+        // Write to array layers in the middle
+        TestWriteTextureExactDataSize(256, 2, destination, wgpu::TextureFormat::RGBA8Unorm,
+                                      {0, 0, 1}, {4, 2, 3});
+
+        // Copy with a non-packed rowsPerImage
+        TestWriteTextureExactDataSize(256, 3, destination, wgpu::TextureFormat::RGBA8Unorm,
+                                      {0, 0, 0}, {4, 2, 5});
+
+        // Copy with bytesPerRow = 500
+        TestWriteTextureExactDataSize(500, 2, destination, wgpu::TextureFormat::RGBA8Unorm,
+                                      {0, 0, 1}, {4, 2, 3});
+    }
+
     class WriteTextureTest_CompressedTextureFormats : public QueueWriteTextureValidationTest {
       public:
         WriteTextureTest_CompressedTextureFormats() : QueueWriteTextureValidationTest() {
@@ -403,14 +443,14 @@
 
             // Valid usages of data offset.
             {
-                uint32_t validDataOffset = utils::CompressedFormatBlockSizeInBytes(bcFormat);
+                uint32_t validDataOffset = utils::GetTexelBlockSizeInBytes(bcFormat);
                 QueueWriteTextureValidationTest::TestWriteTexture(512, validDataOffset, 256, 4,
                                                                   texture, 0, {0, 0, 0}, {4, 4, 1});
             }
 
             // Failures on invalid data offset.
             {
-                uint32_t kInvalidDataOffset = utils::CompressedFormatBlockSizeInBytes(bcFormat) / 2;
+                uint32_t kInvalidDataOffset = utils::GetTexelBlockSizeInBytes(bcFormat) / 2;
                 ASSERT_DEVICE_ERROR(TestWriteTexture(512, kInvalidDataOffset, 256, 4, texture, 0,
                                                      {0, 0, 0}, {4, 4, 1}));
             }
@@ -439,7 +479,7 @@
             for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
                 wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
                 uint32_t ValidBytesPerRow =
-                    kTestWidth / 4 * utils::CompressedFormatBlockSizeInBytes(bcFormat);
+                    kTestWidth / 4 * utils::GetTexelBlockSizeInBytes(bcFormat);
                 ASSERT_NE(0u, ValidBytesPerRow % 256);
                 TestWriteTexture(1024, 0, ValidBytesPerRow, 4, texture, 0, {0, 0, 0},
                                  {kTestWidth, 4, 1});
@@ -568,4 +608,32 @@
         }
     }
 
+    // Test writes to multiple array layers of a compressed texture
+    TEST_F(WriteTextureTest_CompressedTextureFormats, WriteToMultipleArrayLayers) {
+        for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
+            wgpu::Texture texture = QueueWriteTextureValidationTest::Create2DTexture(
+                {12, 16, 20}, 1, bcFormat,
+                wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);
+
+            // Write to all array layers
+            TestWriteTextureExactDataSize(256, 16, texture, bcFormat, {0, 0, 0}, {12, 16, 20});
+
+            // Write to the highest array layer
+            TestWriteTextureExactDataSize(256, 16, texture, bcFormat, {0, 0, 19}, {12, 16, 1});
+
+            // Write to array layers in the middle
+            TestWriteTextureExactDataSize(256, 16, texture, bcFormat, {0, 0, 1}, {12, 16, 18});
+
+            // Write touching the texture corners with a non-packed rowsPerImage
+            TestWriteTextureExactDataSize(256, 24, texture, bcFormat, {4, 4, 4}, {8, 12, 16});
+
+            // rowsPerImage needs to be a multiple of blockHeight
+            ASSERT_DEVICE_ERROR(
+                TestWriteTexture(8192, 0, 256, 6, texture, 0, {0, 0, 0}, {4, 4, 1}));
+
+            // rowsPerImage must be a multiple of blockHeight even with an empty write
+            ASSERT_DEVICE_ERROR(TestWriteTexture(0, 0, 256, 2, texture, 0, {0, 0, 0}, {0, 0, 0}));
+        }
+    }
+
 }  // anonymous namespace
\ No newline at end of file
diff --git a/src/utils/TextureFormatUtils.cpp b/src/utils/TextureFormatUtils.cpp
index 795d1f9..9a57779 100644
--- a/src/utils/TextureFormatUtils.cpp
+++ b/src/utils/TextureFormatUtils.cpp
@@ -119,6 +119,7 @@
             case wgpu::TextureFormat::BGRA8UnormSrgb:
             case wgpu::TextureFormat::RGB10A2Unorm:
             case wgpu::TextureFormat::RG11B10Float:
+            case wgpu::TextureFormat::Depth32Float:
                 return 4u;
 
             case wgpu::TextureFormat::RG32Float:
@@ -152,9 +153,138 @@
             case wgpu::TextureFormat::BC7RGBAUnormSrgb:
                 return 16u;
 
+            case wgpu::TextureFormat::Depth24Plus:
+            case wgpu::TextureFormat::Depth24PlusStencil8:
+            case wgpu::TextureFormat::Undefined:
+            default:
+                UNREACHABLE();
+                return 0u;
+        }
+    }
+
+    uint32_t GetTextureFormatBlockWidth(wgpu::TextureFormat textureFormat) {
+        switch (textureFormat) {
+            case wgpu::TextureFormat::R8Unorm:
+            case wgpu::TextureFormat::R8Snorm:
+            case wgpu::TextureFormat::R8Uint:
+            case wgpu::TextureFormat::R8Sint:
+            case wgpu::TextureFormat::R16Uint:
+            case wgpu::TextureFormat::R16Sint:
+            case wgpu::TextureFormat::R16Float:
+            case wgpu::TextureFormat::RG8Unorm:
+            case wgpu::TextureFormat::RG8Snorm:
+            case wgpu::TextureFormat::RG8Uint:
+            case wgpu::TextureFormat::RG8Sint:
+            case wgpu::TextureFormat::R32Float:
+            case wgpu::TextureFormat::R32Uint:
+            case wgpu::TextureFormat::R32Sint:
+            case wgpu::TextureFormat::RG16Uint:
+            case wgpu::TextureFormat::RG16Sint:
+            case wgpu::TextureFormat::RG16Float:
+            case wgpu::TextureFormat::RGBA8Unorm:
+            case wgpu::TextureFormat::RGBA8UnormSrgb:
+            case wgpu::TextureFormat::RGBA8Snorm:
+            case wgpu::TextureFormat::RGBA8Uint:
+            case wgpu::TextureFormat::RGBA8Sint:
+            case wgpu::TextureFormat::BGRA8Unorm:
+            case wgpu::TextureFormat::BGRA8UnormSrgb:
+            case wgpu::TextureFormat::RGB10A2Unorm:
+            case wgpu::TextureFormat::RG11B10Float:
+            case wgpu::TextureFormat::RG32Float:
+            case wgpu::TextureFormat::RG32Uint:
+            case wgpu::TextureFormat::RG32Sint:
+            case wgpu::TextureFormat::RGBA16Uint:
+            case wgpu::TextureFormat::RGBA16Sint:
+            case wgpu::TextureFormat::RGBA16Float:
+            case wgpu::TextureFormat::RGBA32Float:
+            case wgpu::TextureFormat::RGBA32Uint:
+            case wgpu::TextureFormat::RGBA32Sint:
             case wgpu::TextureFormat::Depth32Float:
             case wgpu::TextureFormat::Depth24Plus:
             case wgpu::TextureFormat::Depth24PlusStencil8:
+                return 1u;
+
+            case wgpu::TextureFormat::BC1RGBAUnorm:
+            case wgpu::TextureFormat::BC1RGBAUnormSrgb:
+            case wgpu::TextureFormat::BC4RUnorm:
+            case wgpu::TextureFormat::BC4RSnorm:
+            case wgpu::TextureFormat::BC2RGBAUnorm:
+            case wgpu::TextureFormat::BC2RGBAUnormSrgb:
+            case wgpu::TextureFormat::BC3RGBAUnorm:
+            case wgpu::TextureFormat::BC3RGBAUnormSrgb:
+            case wgpu::TextureFormat::BC5RGUnorm:
+            case wgpu::TextureFormat::BC5RGSnorm:
+            case wgpu::TextureFormat::BC6HRGBUfloat:
+            case wgpu::TextureFormat::BC6HRGBSfloat:
+            case wgpu::TextureFormat::BC7RGBAUnorm:
+            case wgpu::TextureFormat::BC7RGBAUnormSrgb:
+                return 4u;
+
+            case wgpu::TextureFormat::Undefined:
+            default:
+                UNREACHABLE();
+                return 0u;
+        }
+    }
+
+    uint32_t GetTextureFormatBlockHeight(wgpu::TextureFormat textureFormat) {
+        switch (textureFormat) {
+            case wgpu::TextureFormat::R8Unorm:
+            case wgpu::TextureFormat::R8Snorm:
+            case wgpu::TextureFormat::R8Uint:
+            case wgpu::TextureFormat::R8Sint:
+            case wgpu::TextureFormat::R16Uint:
+            case wgpu::TextureFormat::R16Sint:
+            case wgpu::TextureFormat::R16Float:
+            case wgpu::TextureFormat::RG8Unorm:
+            case wgpu::TextureFormat::RG8Snorm:
+            case wgpu::TextureFormat::RG8Uint:
+            case wgpu::TextureFormat::RG8Sint:
+            case wgpu::TextureFormat::R32Float:
+            case wgpu::TextureFormat::R32Uint:
+            case wgpu::TextureFormat::R32Sint:
+            case wgpu::TextureFormat::RG16Uint:
+            case wgpu::TextureFormat::RG16Sint:
+            case wgpu::TextureFormat::RG16Float:
+            case wgpu::TextureFormat::RGBA8Unorm:
+            case wgpu::TextureFormat::RGBA8UnormSrgb:
+            case wgpu::TextureFormat::RGBA8Snorm:
+            case wgpu::TextureFormat::RGBA8Uint:
+            case wgpu::TextureFormat::RGBA8Sint:
+            case wgpu::TextureFormat::BGRA8Unorm:
+            case wgpu::TextureFormat::BGRA8UnormSrgb:
+            case wgpu::TextureFormat::RGB10A2Unorm:
+            case wgpu::TextureFormat::RG11B10Float:
+            case wgpu::TextureFormat::RG32Float:
+            case wgpu::TextureFormat::RG32Uint:
+            case wgpu::TextureFormat::RG32Sint:
+            case wgpu::TextureFormat::RGBA16Uint:
+            case wgpu::TextureFormat::RGBA16Sint:
+            case wgpu::TextureFormat::RGBA16Float:
+            case wgpu::TextureFormat::RGBA32Float:
+            case wgpu::TextureFormat::RGBA32Uint:
+            case wgpu::TextureFormat::RGBA32Sint:
+            case wgpu::TextureFormat::Depth32Float:
+            case wgpu::TextureFormat::Depth24Plus:
+            case wgpu::TextureFormat::Depth24PlusStencil8:
+                return 1u;
+
+            case wgpu::TextureFormat::BC1RGBAUnorm:
+            case wgpu::TextureFormat::BC1RGBAUnormSrgb:
+            case wgpu::TextureFormat::BC4RUnorm:
+            case wgpu::TextureFormat::BC4RSnorm:
+            case wgpu::TextureFormat::BC2RGBAUnorm:
+            case wgpu::TextureFormat::BC2RGBAUnormSrgb:
+            case wgpu::TextureFormat::BC3RGBAUnorm:
+            case wgpu::TextureFormat::BC3RGBAUnormSrgb:
+            case wgpu::TextureFormat::BC5RGUnorm:
+            case wgpu::TextureFormat::BC5RGSnorm:
+            case wgpu::TextureFormat::BC6HRGBUfloat:
+            case wgpu::TextureFormat::BC6HRGBSfloat:
+            case wgpu::TextureFormat::BC7RGBAUnorm:
+            case wgpu::TextureFormat::BC7RGBAUnormSrgb:
+                return 4u;
+
             case wgpu::TextureFormat::Undefined:
             default:
                 UNREACHABLE();
diff --git a/src/utils/TextureFormatUtils.h b/src/utils/TextureFormatUtils.h
index 2976e94..cdd8942 100644
--- a/src/utils/TextureFormatUtils.h
+++ b/src/utils/TextureFormatUtils.h
@@ -55,6 +55,8 @@
     bool TextureFormatSupportsStorageTexture(wgpu::TextureFormat format);
 
     uint32_t GetTexelBlockSizeInBytes(wgpu::TextureFormat textureFormat);
+    uint32_t GetTextureFormatBlockWidth(wgpu::TextureFormat textureFormat);
+    uint32_t GetTextureFormatBlockHeight(wgpu::TextureFormat textureFormat);
     const char* GetGLSLImageFormatQualifier(wgpu::TextureFormat textureFormat);
 }  // namespace utils
 
diff --git a/src/utils/WGPUHelpers.cpp b/src/utils/WGPUHelpers.cpp
index 3280586..3a6b45f 100644
--- a/src/utils/WGPUHelpers.cpp
+++ b/src/utils/WGPUHelpers.cpp
@@ -418,19 +418,6 @@
         return layout;
     }
 
-    // TODO(jiawei.shao@intel.com): support more pixel formats
-    uint32_t TextureFormatPixelSize(wgpu::TextureFormat format) {
-        switch (format) {
-            case wgpu::TextureFormat::RG8Unorm:
-                return 2;
-            case wgpu::TextureFormat::RGBA8Unorm:
-                return 4;
-            default:
-                UNREACHABLE();
-                return 0;
-        }
-    }
-
     const std::array<wgpu::TextureFormat, 14> kBCFormats = {
         wgpu::TextureFormat::BC1RGBAUnorm,  wgpu::TextureFormat::BC1RGBAUnormSrgb,
         wgpu::TextureFormat::BC2RGBAUnorm,  wgpu::TextureFormat::BC2RGBAUnormSrgb,
@@ -440,27 +427,22 @@
         wgpu::TextureFormat::BC6HRGBUfloat, wgpu::TextureFormat::BC6HRGBSfloat,
         wgpu::TextureFormat::BC7RGBAUnorm,  wgpu::TextureFormat::BC7RGBAUnormSrgb};
 
-    uint32_t CompressedFormatBlockSizeInBytes(wgpu::TextureFormat format) {
-        switch (format) {
-            case wgpu::TextureFormat::BC1RGBAUnorm:
-            case wgpu::TextureFormat::BC1RGBAUnormSrgb:
-            case wgpu::TextureFormat::BC4RSnorm:
-            case wgpu::TextureFormat::BC4RUnorm:
-                return 8;
-            case wgpu::TextureFormat::BC2RGBAUnorm:
-            case wgpu::TextureFormat::BC2RGBAUnormSrgb:
-            case wgpu::TextureFormat::BC3RGBAUnorm:
-            case wgpu::TextureFormat::BC3RGBAUnormSrgb:
-            case wgpu::TextureFormat::BC5RGSnorm:
-            case wgpu::TextureFormat::BC5RGUnorm:
-            case wgpu::TextureFormat::BC6HRGBSfloat:
-            case wgpu::TextureFormat::BC6HRGBUfloat:
-            case wgpu::TextureFormat::BC7RGBAUnorm:
-            case wgpu::TextureFormat::BC7RGBAUnormSrgb:
-                return 16;
-            default:
-                UNREACHABLE();
-                return 0;
+    uint64_t RequiredBytesInCopy(uint64_t bytesPerRow,
+                                 uint64_t rowsPerImage,
+                                 wgpu::Extent3D copyExtent,
+                                 wgpu::TextureFormat textureFormat) {
+        if (copyExtent.width == 0 || copyExtent.height == 0 || copyExtent.depth == 0) {
+            return 0;
+        } else {
+            uint32_t blockSize = utils::GetTexelBlockSizeInBytes(textureFormat);
+            uint32_t blockWidth = utils::GetTextureFormatBlockWidth(textureFormat);
+            uint32_t blockHeight = utils::GetTextureFormatBlockHeight(textureFormat);
+
+            uint64_t texelBlockRowsPerImage = rowsPerImage / blockHeight;
+            uint64_t bytesPerImage = bytesPerRow * texelBlockRowsPerImage;
+            uint64_t bytesInLastSlice = bytesPerRow * (copyExtent.height / blockHeight - 1) +
+                                        (copyExtent.width / blockWidth * blockSize);
+            return bytesPerImage * (copyExtent.depth - 1) + bytesInLastSlice;
         }
     }
 
diff --git a/src/utils/WGPUHelpers.h b/src/utils/WGPUHelpers.h
index 6c7825f..a01cee6 100644
--- a/src/utils/WGPUHelpers.h
+++ b/src/utils/WGPUHelpers.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "common/Constants.h"
+#include "utils/TextureFormatUtils.h"
 
 namespace utils {
 
@@ -151,9 +152,12 @@
         uint32_t mipmapLevel,
         uint32_t rowsPerImage);
 
-    uint32_t TextureFormatPixelSize(wgpu::TextureFormat format);
     extern const std::array<wgpu::TextureFormat, 14> kBCFormats;
-    uint32_t CompressedFormatBlockSizeInBytes(wgpu::TextureFormat format);
+
+    uint64_t RequiredBytesInCopy(uint64_t bytesPerRow,
+                                 uint64_t rowsPerImage,
+                                 wgpu::Extent3D copyExtent,
+                                 wgpu::TextureFormat textureFormat);
 
 }  // namespace utils