Add validations to the texture copies with BC formats

This patch adds the validation on the texture copies with BC formats.
1. BufferCopyView.offset in B2T and T2B copies must be a multiple of the
   compressed texel block size in bytes.
2. BufferCopyView.rowPitch in B2T and T2B copies refers to the number of
   bytes from the start of one row of blocks to the start of the next
   row of blocks.
3. BufferCopyView.imageHeight must be a multiple of the compressed texel
   block height (4 for BC formats).
4. All members in TextureCopyView.origin must be a multiple of the
   corresponding dimensions of the compressed texel block (4x4x1 for BC
   formats).
5. All the mumbers in 'copySize' must be a multiple of the corresponding
   dimensions of the compressed texel block (4x4x1 for BC formats)
   because D3D12 requires the width and height of a texture in BC
   formats must be multiples of 4.
6. Compute the texture size in non-zero mipmap levels with paddings for
   textures in BC formats when necessary.

BUG=dawn:42
TEST=dawn_unittests

Change-Id: Iac8d6c93ab8b37bb46becffd4175339722ab6016
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/7860
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 3fa880d..efb5445 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -46,10 +46,22 @@
             // All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
             // overflows.
             uint64_t level = textureCopy.level;
+
+            uint32_t widthAtLevel = texture->GetSize().width >> level;
+            uint32_t heightAtLevel = texture->GetSize().height >> level;
+
+            // Compressed Textures will have paddings if their width or height is not a multiple of
+            // 4 at non-zero mipmap levels.
+            if (Is4x4CompressedFormat(texture->GetFormat())) {
+                // TODO(jiawei.shao@intel.com): check if there are any overflows.
+                widthAtLevel = (widthAtLevel + 3) / 4 * 4;
+                heightAtLevel = (heightAtLevel + 3) / 4 * 4;
+            }
+
             if (uint64_t(textureCopy.origin.x) + uint64_t(copySize.width) >
-                    (static_cast<uint64_t>(texture->GetSize().width) >> level) ||
+                    static_cast<uint64_t>(widthAtLevel) ||
                 uint64_t(textureCopy.origin.y) + uint64_t(copySize.height) >
-                    (static_cast<uint64_t>(texture->GetSize().height) >> level)) {
+                    static_cast<uint64_t>(heightAtLevel)) {
                 return DAWN_VALIDATION_ERROR("Copy would touch outside of the texture");
             }
 
@@ -96,10 +108,10 @@
         }
 
         MaybeError ValidateTexelBufferOffset(TextureBase* texture, const BufferCopy& bufferCopy) {
-            uint32_t texelSize =
-                static_cast<uint32_t>(TextureFormatPixelSize(texture->GetFormat()));
-            if (bufferCopy.offset % texelSize != 0) {
-                return DAWN_VALIDATION_ERROR("Buffer offset must be a multiple of the texel size");
+            uint32_t blockSize = TextureFormatTexelBlockSizeInBytes(texture->GetFormat());
+            if (bufferCopy.offset % blockSize != 0) {
+                return DAWN_VALIDATION_ERROR(
+                    "Buffer offset must be a multiple of the texel or block size");
             }
 
             return {};
@@ -194,18 +206,24 @@
                                                 uint32_t* bufferSize) {
             DAWN_TRY(ValidateImageHeight(imageHeight, copySize.height));
 
+            uint32_t texelOrBlockSizeInBytes = TextureFormatTexelBlockSizeInBytes(textureFormat);
+            uint32_t blockWidthInTexels = TextureFormatBlockWidthInTexels(textureFormat);
+            uint32_t blockHeightInTexels = TextureFormatBlockWidthInTexels(textureFormat);
+
             // TODO(cwallez@chromium.org): check for overflows
-            uint32_t slicePitch = rowPitch * imageHeight;
-            uint32_t sliceSize = rowPitch * (copySize.height - 1) +
-                                 copySize.width * TextureFormatPixelSize(textureFormat);
+            uint32_t slicePitch = rowPitch * imageHeight / blockWidthInTexels;
+            uint32_t sliceSize = rowPitch * (copySize.height / blockHeightInTexels - 1) +
+                                 (copySize.width / blockWidthInTexels) * texelOrBlockSizeInBytes;
             *bufferSize = (slicePitch * (copySize.depth - 1)) + sliceSize;
 
             return {};
         }
 
         uint32_t ComputeDefaultRowPitch(TextureBase* texture, uint32_t width) {
-            uint32_t texelSize = TextureFormatPixelSize(texture->GetFormat());
-            return texelSize * width;
+            const dawn::TextureFormat format = texture->GetFormat();
+            uint32_t texelOrBlockSizeInBytes = TextureFormatTexelBlockSizeInBytes(format);
+            uint32_t blockWidthInTexels = TextureFormatBlockWidthInTexels(format);
+            return width / blockWidthInTexels * texelOrBlockSizeInBytes;
         }
 
         MaybeError ValidateRowPitch(dawn::TextureFormat format,
@@ -215,8 +233,9 @@
                 return DAWN_VALIDATION_ERROR("Row pitch must be a multiple of 256");
             }
 
-            uint32_t texelSize = TextureFormatPixelSize(format);
-            if (rowPitch < copySize.width * texelSize) {
+            uint32_t texelOrBlockSizeInBytes = TextureFormatTexelBlockSizeInBytes(format);
+            uint32_t blockWidthInTexels = TextureFormatBlockWidthInTexels(format);
+            if (rowPitch < copySize.width / blockWidthInTexels * texelOrBlockSizeInBytes) {
                 return DAWN_VALIDATION_ERROR(
                     "Row pitch must not be less than the number of bytes per row");
             }
@@ -224,6 +243,43 @@
             return {};
         }
 
+        MaybeError ValidateImageHeight(dawn::TextureFormat format, uint32_t imageHeight) {
+            if (imageHeight % TextureFormatBlockHeightInTexels(format) != 0) {
+                return DAWN_VALIDATION_ERROR(
+                    "Image height must be a multiple of compressed texture format block width");
+            }
+
+            return {};
+        }
+
+        MaybeError ValidateImageOrigin(dawn::TextureFormat format, const Origin3D& offset) {
+            if (offset.x % TextureFormatBlockWidthInTexels(format) != 0) {
+                return DAWN_VALIDATION_ERROR(
+                    "Offset.x must be a multiple of compressed texture format block width");
+            }
+
+            if (offset.y % TextureFormatBlockHeightInTexels(format) != 0) {
+                return DAWN_VALIDATION_ERROR(
+                    "Offset.y must be a multiple of compressed texture format block height");
+            }
+
+            return {};
+        }
+
+        MaybeError ValidateImageCopySize(dawn::TextureFormat format, const Extent3D& extent) {
+            if (extent.width % TextureFormatBlockWidthInTexels(format) != 0) {
+                return DAWN_VALIDATION_ERROR(
+                    "Extent.width must be a multiple of compressed texture format block width");
+            }
+
+            if (extent.height % TextureFormatBlockHeightInTexels(format) != 0) {
+                return DAWN_VALIDATION_ERROR(
+                    "Extent.height must be a multiple of compressed texture format block height");
+            }
+
+            return {};
+        }
+
         MaybeError ValidateCanUseAs(BufferBase* buffer, dawn::BufferUsageBit usage) {
             ASSERT(HasZeroOrOneBits(usage));
             if (!(buffer->GetUsage() & usage)) {
@@ -904,6 +960,13 @@
                     DAWN_TRY(
                         ValidateTextureSampleCountInCopyCommands(copy->destination.texture.Get()));
 
+                    DAWN_TRY(ValidateImageHeight(copy->destination.texture->GetFormat(),
+                                                 copy->source.imageHeight));
+                    DAWN_TRY(ValidateImageOrigin(copy->destination.texture->GetFormat(),
+                                                 copy->destination.origin));
+                    DAWN_TRY(ValidateImageCopySize(copy->destination.texture->GetFormat(),
+                                                   copy->copySize));
+
                     uint32_t bufferCopySize = 0;
                     DAWN_TRY(ValidateRowPitch(copy->destination.texture->GetFormat(),
                                               copy->copySize, copy->source.rowPitch));
@@ -931,6 +994,13 @@
 
                     DAWN_TRY(ValidateTextureSampleCountInCopyCommands(copy->source.texture.Get()));
 
+                    DAWN_TRY(ValidateImageHeight(copy->source.texture->GetFormat(),
+                                                 copy->destination.imageHeight));
+                    DAWN_TRY(ValidateImageOrigin(copy->source.texture->GetFormat(),
+                                                 copy->source.origin));
+                    DAWN_TRY(
+                        ValidateImageCopySize(copy->source.texture->GetFormat(), copy->copySize));
+
                     uint32_t bufferCopySize = 0;
                     DAWN_TRY(ValidateRowPitch(copy->source.texture->GetFormat(), copy->copySize,
                                               copy->destination.rowPitch));
@@ -960,6 +1030,15 @@
                     DAWN_TRY(ValidateTextureToTextureCopyRestrictions(
                         copy->source, copy->destination, copy->copySize));
 
+                    DAWN_TRY(ValidateImageOrigin(copy->source.texture->GetFormat(),
+                                                 copy->source.origin));
+                    DAWN_TRY(
+                        ValidateImageCopySize(copy->source.texture->GetFormat(), copy->copySize));
+                    DAWN_TRY(ValidateImageOrigin(copy->destination.texture->GetFormat(),
+                                                 copy->destination.origin));
+                    DAWN_TRY(ValidateImageCopySize(copy->destination.texture->GetFormat(),
+                                                   copy->copySize));
+
                     DAWN_TRY(ValidateCopySizeFitsInTexture(copy->source, copy->copySize));
                     DAWN_TRY(ValidateCopySizeFitsInTexture(copy->destination, copy->copySize));
 
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index d94a707..8e02866 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -112,10 +112,6 @@
             return IsBCFormat(format);
         }
 
-        bool Is4x4CompressedFormat(dawn::TextureFormat format) {
-            return IsBCFormat(format);
-        }
-
         bool IsWritableFormat(dawn::TextureFormat format) {
             return !IsBCFormat(format);
         }
@@ -216,6 +212,28 @@
         }
     }  // anonymous namespace
 
+    bool Is4x4CompressedFormat(dawn::TextureFormat format) {
+        return IsBCFormat(format);
+    }
+
+    // We treat non-compressed texture formats as the block texture formats in 1x1 blocks.
+    uint32_t TextureFormatBlockWidthInTexels(dawn::TextureFormat format) {
+        if (Is4x4CompressedFormat(format)) {
+            return 4;
+        }
+
+        return 1;
+    }
+
+    // We treat non-compressed texture formats as the block texture formats in 1x1 blocks.
+    uint32_t TextureFormatBlockHeightInTexels(dawn::TextureFormat format) {
+        if (Is4x4CompressedFormat(format)) {
+            return 4;
+        }
+
+        return 1;
+    }
+
     MaybeError ValidateTextureUsageBit(const TextureDescriptor* descriptor) {
         DAWN_TRY(ValidateTextureUsageBit(descriptor->usage));
         if (!IsWritableFormat(descriptor->format)) {
@@ -289,8 +307,10 @@
         return {};
     }
 
-    uint32_t TextureFormatPixelSize(dawn::TextureFormat format) {
+    // We treat non-compressed texture formats as the block texture formats in 1x1 blocks.
+    uint32_t TextureFormatTexelBlockSizeInBytes(dawn::TextureFormat format) {
         switch (format) {
+            // Non-compressed texture formats
             case dawn::TextureFormat::R8Unorm:
             case dawn::TextureFormat::R8Uint:
                 return 1;
@@ -303,6 +323,25 @@
                 return 4;
             case dawn::TextureFormat::D32FloatS8Uint:
                 return 8;
+
+            // BC formats
+            case dawn::TextureFormat::BC1RGBAUnorm:
+            case dawn::TextureFormat::BC1RGBAUnormSrgb:
+            case dawn::TextureFormat::BC4RSnorm:
+            case dawn::TextureFormat::BC4RUnorm:
+                return 8;
+            case dawn::TextureFormat::BC2RGBAUnorm:
+            case dawn::TextureFormat::BC2RGBAUnormSrgb:
+            case dawn::TextureFormat::BC3RGBAUnorm:
+            case dawn::TextureFormat::BC3RGBAUnormSrgb:
+            case dawn::TextureFormat::BC5RGSnorm:
+            case dawn::TextureFormat::BC5RGUnorm:
+            case dawn::TextureFormat::BC6HRGBSfloat:
+            case dawn::TextureFormat::BC6HRGBUfloat:
+            case dawn::TextureFormat::BC7RGBAUnorm:
+            case dawn::TextureFormat::BC7RGBAUnormSrgb:
+                return 16;
+
             default:
                 UNREACHABLE();
         }
@@ -418,6 +457,8 @@
         ASSERT(!IsError());
         return mDimension;
     }
+
+    // TODO(jiawei.shao@intel.com): return more information about texture format
     dawn::TextureFormat TextureBase::GetFormat() const {
         ASSERT(!IsError());
         return mFormat;
diff --git a/src/dawn_native/Texture.h b/src/dawn_native/Texture.h
index dc3771e..db6a3e4 100644
--- a/src/dawn_native/Texture.h
+++ b/src/dawn_native/Texture.h
@@ -29,7 +29,7 @@
                                              const TextureBase* texture,
                                              const TextureViewDescriptor* descriptor);
 
-    uint32_t TextureFormatPixelSize(dawn::TextureFormat format);
+    uint32_t TextureFormatTexelBlockSizeInBytes(dawn::TextureFormat format);
     bool TextureFormatHasDepth(dawn::TextureFormat format);
     bool TextureFormatHasStencil(dawn::TextureFormat format);
     bool TextureFormatHasDepthOrStencil(dawn::TextureFormat format);
@@ -37,6 +37,10 @@
     bool IsDepthStencilRenderableTextureFormat(dawn::TextureFormat format);
     bool IsValidSampleCount(uint32_t sampleCount);
 
+    bool Is4x4CompressedFormat(dawn::TextureFormat format);
+    uint32_t TextureFormatBlockWidthInTexels(dawn::TextureFormat format);
+    uint32_t TextureFormatBlockHeightInTexels(dawn::TextureFormat format);
+
     static constexpr dawn::TextureUsageBit kReadOnlyTextureUsages =
         dawn::TextureUsageBit::TransferSrc | dawn::TextureUsageBit::Sampled |
         dawn::TextureUsageBit::Present;
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index fd2b4e6..bfdc84a 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -505,7 +505,8 @@
 
                     auto copySplit = ComputeTextureCopySplit(
                         copy->destination.origin, copy->copySize,
-                        static_cast<uint32_t>(TextureFormatPixelSize(texture->GetFormat())),
+                        static_cast<uint32_t>(
+                            TextureFormatTexelBlockSizeInBytes(texture->GetFormat())),
                         copy->source.offset, copy->source.rowPitch, copy->source.imageHeight);
 
                     D3D12_TEXTURE_COPY_LOCATION textureLocation =
@@ -549,7 +550,8 @@
 
                     auto copySplit = ComputeTextureCopySplit(
                         copy->source.origin, copy->copySize,
-                        static_cast<uint32_t>(TextureFormatPixelSize(texture->GetFormat())),
+                        static_cast<uint32_t>(
+                            TextureFormatTexelBlockSizeInBytes(texture->GetFormat())),
                         copy->destination.offset, copy->destination.rowPitch,
                         copy->destination.imageHeight);
 
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index 65cf726..20ff0a9 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -454,7 +454,7 @@
                     // Doing the last row copy with the exact number of bytes in last row.
                     // Like copy to a 1D texture to workaround the issue.
                     uint32_t lastRowDataSize =
-                        copySize.width * TextureFormatPixelSize(texture->GetFormat());
+                        copySize.width * TextureFormatTexelBlockSizeInBytes(texture->GetFormat());
 
                     [encoders.blit
                              copyFromBuffer:buffer->GetMTLBuffer()
@@ -568,7 +568,7 @@
                     // Doing the last row copy with the exact number of bytes in last row.
                     // Like copy from a 1D texture to workaround the issue.
                     uint32_t lastRowDataSize =
-                        copySize.width * TextureFormatPixelSize(texture->GetFormat());
+                        copySize.width * TextureFormatTexelBlockSizeInBytes(texture->GetFormat());
 
                     [encoders.blit
                                  copyFromTexture:texture->GetMTLTexture()
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index 13673d7..602f93e 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -389,8 +389,9 @@
                     gl.ActiveTexture(GL_TEXTURE0);
                     gl.BindTexture(target, texture->GetHandle());
 
-                    gl.PixelStorei(GL_UNPACK_ROW_LENGTH,
-                                   src.rowPitch / TextureFormatPixelSize(texture->GetFormat()));
+                    gl.PixelStorei(
+                        GL_UNPACK_ROW_LENGTH,
+                        src.rowPitch / TextureFormatTexelBlockSizeInBytes(texture->GetFormat()));
                     gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, src.imageHeight);
                     switch (texture->GetDimension()) {
                         case dawn::TextureDimension::e2D:
@@ -451,8 +452,9 @@
                     }
 
                     gl.BindBuffer(GL_PIXEL_PACK_BUFFER, buffer->GetHandle());
-                    gl.PixelStorei(GL_PACK_ROW_LENGTH,
-                                   dst.rowPitch / TextureFormatPixelSize(texture->GetFormat()));
+                    gl.PixelStorei(
+                        GL_PACK_ROW_LENGTH,
+                        dst.rowPitch / TextureFormatTexelBlockSizeInBytes(texture->GetFormat()));
                     gl.PixelStorei(GL_PACK_IMAGE_HEIGHT, dst.imageHeight);
                     ASSERT(copySize.depth == 1 && src.origin.z == 0);
                     void* offset = reinterpret_cast<void*>(static_cast<uintptr_t>(dst.offset));
diff --git a/src/dawn_native/opengl/TextureGL.cpp b/src/dawn_native/opengl/TextureGL.cpp
index 1ffcacb..8726118 100644
--- a/src/dawn_native/opengl/TextureGL.cpp
+++ b/src/dawn_native/opengl/TextureGL.cpp
@@ -171,7 +171,7 @@
 
         if (GetDevice()->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) {
             static constexpr uint32_t MAX_TEXEL_SIZE = 16;
-            ASSERT(TextureFormatPixelSize(GetFormat()) <= MAX_TEXEL_SIZE);
+            ASSERT(TextureFormatTexelBlockSizeInBytes(GetFormat()) <= MAX_TEXEL_SIZE);
             GLubyte clearColor[MAX_TEXEL_SIZE];
             std::fill(clearColor, clearColor + MAX_TEXEL_SIZE, 255);
 
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index ddf4893..e36676f 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -51,7 +51,7 @@
             region.bufferOffset = bufferCopy.offset;
             // In Vulkan the row length is in texels while it is in bytes for Dawn
             region.bufferRowLength =
-                bufferCopy.rowPitch / TextureFormatPixelSize(texture->GetFormat());
+                bufferCopy.rowPitch / TextureFormatTexelBlockSizeInBytes(texture->GetFormat());
             region.bufferImageHeight = bufferCopy.imageHeight;
 
             region.imageSubresource.aspectMask = texture->GetVkAspectMask();
diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
index afad75c..f913029 100644
--- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
+++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
@@ -1080,4 +1080,313 @@
         TestT2TCopy(utils::Expectation::Failure, sourceMultiSampled4x, 0, 0, {0, 0, 0},
                     destinationMultiSampled4x, 0, 0, {0, 0, 0}, {15, 15, 1});
     }
-}
\ No newline at end of file
+}
+
+class CopyCommandTest_CompressedTextureFormats : public CopyCommandTest {
+  protected:
+    dawn::Texture Create2DTexture(dawn::TextureFormat format,
+                                  uint32_t mipmapLevels = 1,
+                                  uint32_t width = kWidth,
+                                  uint32_t height = kHeight) {
+        constexpr dawn::TextureUsageBit kUsage = dawn::TextureUsageBit::TransferDst |
+                                                 dawn::TextureUsageBit::TransferSrc |
+                                                 dawn::TextureUsageBit::Sampled;
+        constexpr uint32_t kArrayLayers = 1;
+        return CopyCommandTest::Create2DTexture(width, height, mipmapLevels, kArrayLayers, format,
+                                                kUsage, 1);
+    }
+
+    static uint32_t CompressedFormatBlockSizeInBytes(dawn::TextureFormat format) {
+        switch (format) {
+            case dawn::TextureFormat::BC1RGBAUnorm:
+            case dawn::TextureFormat::BC1RGBAUnormSrgb:
+            case dawn::TextureFormat::BC4RSnorm:
+            case dawn::TextureFormat::BC4RUnorm:
+                return 8;
+            case dawn::TextureFormat::BC2RGBAUnorm:
+            case dawn::TextureFormat::BC2RGBAUnormSrgb:
+            case dawn::TextureFormat::BC3RGBAUnorm:
+            case dawn::TextureFormat::BC3RGBAUnormSrgb:
+            case dawn::TextureFormat::BC5RGSnorm:
+            case dawn::TextureFormat::BC5RGUnorm:
+            case dawn::TextureFormat::BC6HRGBSfloat:
+            case dawn::TextureFormat::BC6HRGBUfloat:
+            case dawn::TextureFormat::BC7RGBAUnorm:
+            case dawn::TextureFormat::BC7RGBAUnormSrgb:
+                return 16;
+            default:
+                UNREACHABLE();
+                return 0;
+        }
+    }
+
+    void TestBothTBCopies(utils::Expectation expectation,
+                          dawn::Buffer buffer,
+                          uint64_t bufferOffset,
+                          uint32_t bufferRowPitch,
+                          uint32_t imageHeight,
+                          dawn::Texture texture,
+                          uint32_t level,
+                          uint32_t arraySlice,
+                          dawn::Origin3D origin,
+                          dawn::Extent3D extent3D) {
+        TestB2TCopy(expectation, buffer, bufferOffset, bufferRowPitch, imageHeight, texture, level,
+                    arraySlice, origin, extent3D);
+        TestT2BCopy(expectation, texture, level, arraySlice, origin, buffer, bufferOffset,
+                    bufferRowPitch, imageHeight, extent3D);
+    }
+
+    void TestBothT2TCopies(utils::Expectation expectation,
+                           dawn::Texture texture1,
+                           uint32_t level1,
+                           uint32_t slice1,
+                           dawn::Origin3D origin1,
+                           dawn::Texture texture2,
+                           uint32_t level2,
+                           uint32_t slice2,
+                           dawn::Origin3D origin2,
+                           dawn::Extent3D extent3D) {
+        TestT2TCopy(expectation, texture1, level1, slice1, origin1, texture2, level2, slice2,
+                    origin2, extent3D);
+        TestT2TCopy(expectation, texture2, level2, slice2, origin2, texture1, level1, slice1,
+                    origin1, extent3D);
+    }
+
+    static constexpr uint32_t kWidth = 16;
+    static constexpr uint32_t kHeight = 16;
+
+    const std::array<dawn::TextureFormat, 14> kBCFormats = {
+        dawn::TextureFormat::BC1RGBAUnorm,  dawn::TextureFormat::BC1RGBAUnormSrgb,
+        dawn::TextureFormat::BC2RGBAUnorm,  dawn::TextureFormat::BC2RGBAUnormSrgb,
+        dawn::TextureFormat::BC3RGBAUnorm,  dawn::TextureFormat::BC3RGBAUnormSrgb,
+        dawn::TextureFormat::BC4RUnorm,     dawn::TextureFormat::BC4RSnorm,
+        dawn::TextureFormat::BC5RGUnorm,    dawn::TextureFormat::BC5RGSnorm,
+        dawn::TextureFormat::BC6HRGBUfloat, dawn::TextureFormat::BC6HRGBSfloat,
+        dawn::TextureFormat::BC7RGBAUnorm,  dawn::TextureFormat::BC7RGBAUnormSrgb};
+};
+
+// Tests to verify that bufferOffset must be a multiple of the compressed texture blocks in bytes
+// in buffer-to-texture or texture-to-buffer copies with compressed texture formats.
+TEST_F(CopyCommandTest_CompressedTextureFormats, BufferOffset) {
+    dawn::Buffer buffer =
+        CreateBuffer(512, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst);
+
+    for (dawn::TextureFormat bcFormat : kBCFormats) {
+        dawn::Texture texture = Create2DTexture(bcFormat);
+
+        // Valid usages of BufferOffset in B2T and T2B copies with compressed texture formats.
+        {
+            uint32_t validBufferOffset = CompressedFormatBlockSizeInBytes(bcFormat);
+            TestBothTBCopies(utils::Expectation::Success, buffer, validBufferOffset, 256, 4,
+                             texture, 0, 0, {0, 0, 0}, {4, 4, 1});
+        }
+
+        // Failures on invalid bufferOffset.
+        {
+            uint32_t kInvalidBufferOffset = CompressedFormatBlockSizeInBytes(bcFormat) / 2;
+            TestBothTBCopies(utils::Expectation::Failure, buffer, kInvalidBufferOffset, 256, 4,
+                             texture, 0, 0, {0, 0, 0}, {4, 4, 1});
+        }
+    }
+}
+
+// Tests to verify that RowPitch must not be smaller than (width / blockWidth) * blockSizeInBytes
+// and it is valid to use 0 as RowPitch in buffer-to-texture or texture-to-buffer copies with
+// compressed texture formats.
+// Note that in Dawn we require RowPitch be a multiple of 256, which ensures RowPitch will always be
+// the multiple of compressed texture block width in bytes.
+TEST_F(CopyCommandTest_CompressedTextureFormats, RowPitch) {
+    dawn::Buffer buffer =
+        CreateBuffer(1024, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst);
+
+    {
+        constexpr uint32_t kTestWidth = 160;
+        constexpr uint32_t kTestHeight = 160;
+
+        // Failures on the RowPitch that is not large enough.
+        {
+            constexpr uint32_t kSmallRowPitch = 256;
+            for (dawn::TextureFormat bcFormat : kBCFormats) {
+                dawn::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
+                TestBothTBCopies(utils::Expectation::Failure, buffer, 0, kSmallRowPitch, 4, texture,
+                                 0, 0, {0, 0, 0}, {kTestWidth, 4, 1});
+            }
+        }
+
+        // Test it is not valid to use a RowPitch that is not a multiple of 256.
+        {
+            for (dawn::TextureFormat bcFormat : kBCFormats) {
+                dawn::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
+                uint32_t inValidRowPitch =
+                    kTestWidth / 4 * CompressedFormatBlockSizeInBytes(bcFormat);
+                ASSERT_NE(0u, inValidRowPitch % 256);
+                TestBothTBCopies(utils::Expectation::Failure, buffer, 0, inValidRowPitch, 4,
+                                 texture, 0, 0, {0, 0, 0}, {kTestWidth, 4, 1});
+            }
+        }
+
+        // Test the smallest valid RowPitch should work.
+        {
+            for (dawn::TextureFormat bcFormat : kBCFormats) {
+                dawn::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
+                uint32_t smallestValidRowPitch =
+                    Align(kTestWidth / 4 * CompressedFormatBlockSizeInBytes(bcFormat), 256);
+                TestBothTBCopies(utils::Expectation::Success, buffer, 0, smallestValidRowPitch, 4,
+                                 texture, 0, 0, {0, 0, 0}, {kTestWidth, 4, 1});
+            }
+        }
+    }
+
+    // Test RowPitch == 0.
+    {
+        constexpr uint32_t kZeroRowPitch = 0;
+        constexpr uint32_t kTestHeight = 128;
+
+        {
+            constexpr uint32_t kValidWidth = 128;
+            for (dawn::TextureFormat bcFormat : kBCFormats) {
+                dawn::Texture texture = Create2DTexture(bcFormat, 1, kValidWidth, kTestHeight);
+                TestBothTBCopies(utils::Expectation::Success, buffer, 0, kZeroRowPitch, 4, texture,
+                                 0, 0, {0, 0, 0}, {kValidWidth, 4, 1});
+            }
+        }
+
+        {
+            constexpr uint32_t kInValidWidth = 16;
+            for (dawn::TextureFormat bcFormat : kBCFormats) {
+                dawn::Texture texture = Create2DTexture(bcFormat, 1, kInValidWidth, kTestHeight);
+                TestBothTBCopies(utils::Expectation::Failure, buffer, 0, kZeroRowPitch, 4, texture,
+                                 0, 0, {0, 0, 0}, {kInValidWidth, 4, 1});
+            }
+        }
+    }
+}
+
+// Tests to verify that imageHeight must be a multiple of the compressed texture block height in
+// buffer-to-texture or texture-to-buffer copies with compressed texture formats.
+TEST_F(CopyCommandTest_CompressedTextureFormats, ImageHeight) {
+    dawn::Buffer buffer =
+        CreateBuffer(512, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst);
+
+    for (dawn::TextureFormat bcFormat : kBCFormats) {
+        dawn::Texture texture = Create2DTexture(bcFormat);
+
+        // Valid usages of imageHeight in B2T and T2B copies with compressed texture formats.
+        {
+            constexpr uint32_t kValidImageHeight = 8;
+            TestBothTBCopies(utils::Expectation::Success, buffer, 0, 256, kValidImageHeight,
+                             texture, 0, 0, {0, 0, 0}, {4, 4, 1});
+        }
+
+        // Failures on invalid imageHeight.
+        {
+            constexpr uint32_t kInvalidImageHeight = 3;
+            TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, kInvalidImageHeight,
+                             texture, 0, 0, {0, 0, 0}, {4, 4, 1});
+        }
+    }
+}
+
+// Tests to verify that ImageOffset.x must be a multiple of the compressed texture block width and
+// ImageOffset.y must be a multiple of the compressed texture block height in buffer-to-texture,
+// texture-to-buffer or texture-to-texture copies with compressed texture formats.
+TEST_F(CopyCommandTest_CompressedTextureFormats, ImageOffset) {
+    dawn::Buffer buffer =
+        CreateBuffer(512, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst);
+
+    for (dawn::TextureFormat bcFormat : kBCFormats) {
+        dawn::Texture texture = Create2DTexture(bcFormat);
+        dawn::Texture texture2 = Create2DTexture(bcFormat);
+
+        constexpr dawn::Origin3D kSmallestValidOrigin3D = {4, 4, 0};
+
+        // Valid usages of ImageOffset in B2T, T2B and T2T copies with compressed texture formats.
+        {
+            TestBothTBCopies(utils::Expectation::Success, buffer, 0, 256, 4, texture, 0, 0,
+                             kSmallestValidOrigin3D, {4, 4, 1});
+            TestBothT2TCopies(utils::Expectation::Success, texture, 0, 0, {0, 0, 0}, texture2, 0, 0,
+                              kSmallestValidOrigin3D, {4, 4, 1});
+        }
+
+        // Failures on invalid ImageOffset.x.
+        {
+            constexpr dawn::Origin3D kInvalidOrigin3D = {kSmallestValidOrigin3D.x - 1,
+                                                         kSmallestValidOrigin3D.y, 0};
+            TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, 4, texture, 0, 0,
+                             kInvalidOrigin3D, {4, 4, 1});
+            TestBothT2TCopies(utils::Expectation::Failure, texture, 0, 0, kInvalidOrigin3D,
+                              texture2, 0, 0, {0, 0, 0}, {4, 4, 1});
+        }
+
+        // Failures on invalid ImageOffset.y.
+        {
+            constexpr dawn::Origin3D kInvalidOrigin3D = {kSmallestValidOrigin3D.x,
+                                                         kSmallestValidOrigin3D.y - 1, 0};
+            TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, 4, texture, 0, 0,
+                             kInvalidOrigin3D, {4, 4, 1});
+            TestBothT2TCopies(utils::Expectation::Failure, texture, 0, 0, kInvalidOrigin3D,
+                              texture2, 0, 0, {0, 0, 0}, {4, 4, 1});
+        }
+    }
+}
+
+// Tests to verify that ImageExtent.x must be a multiple of the compressed texture block width and
+// ImageExtent.y must be a multiple of the compressed texture block height in buffer-to-texture,
+// texture-to-buffer or texture-to-texture copies with compressed texture formats.
+TEST_F(CopyCommandTest_CompressedTextureFormats, ImageExtent) {
+    dawn::Buffer buffer =
+        CreateBuffer(512, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst);
+
+    constexpr uint32_t kMipmapLevels = 3;
+    constexpr uint32_t kTestWidth = 60;
+    constexpr uint32_t kTestHeight = 60;
+
+    for (dawn::TextureFormat bcFormat : kBCFormats) {
+        dawn::Texture texture = Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight);
+        dawn::Texture texture2 = Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight);
+
+        constexpr dawn::Extent3D kSmallestValidExtent3D = {4, 4, 1};
+
+        // Valid usages of ImageExtent in B2T, T2B and T2T copies with compressed texture formats.
+        {
+            TestBothTBCopies(utils::Expectation::Success, buffer, 0, 256, 8, texture, 0, 0,
+                             {0, 0, 0}, kSmallestValidExtent3D);
+            TestBothT2TCopies(utils::Expectation::Success, texture, 0, 0, {0, 0, 0}, texture2, 0, 0,
+                              {0, 0, 0}, kSmallestValidExtent3D);
+        }
+
+        // Valid usages of ImageExtent in B2T, T2B and T2T copies with compressed texture formats
+        // and non-zero mipmap levels.
+        {
+            constexpr uint32_t kTestMipmapLevel = 2;
+            constexpr dawn::Origin3D kTestOrigin = {
+                (kTestWidth >> kTestMipmapLevel) - kSmallestValidExtent3D.width + 1,
+                (kTestHeight >> kTestMipmapLevel) - kSmallestValidExtent3D.height + 1, 0};
+
+            TestBothTBCopies(utils::Expectation::Success, buffer, 0, 256, 4, texture,
+                             kTestMipmapLevel, 0, kTestOrigin, kSmallestValidExtent3D);
+            TestBothT2TCopies(utils::Expectation::Success, texture, kTestMipmapLevel, 0,
+                              kTestOrigin, texture2, 0, 0, {0, 0, 0}, kSmallestValidExtent3D);
+        }
+
+        // Failures on invalid ImageExtent.x.
+        {
+            constexpr dawn::Extent3D kInValidExtent3D = {kSmallestValidExtent3D.width - 1,
+                                                         kSmallestValidExtent3D.height, 1};
+            TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, 4, texture, 0, 0,
+                             {0, 0, 0}, kInValidExtent3D);
+            TestBothT2TCopies(utils::Expectation::Failure, texture, 0, 0, {0, 0, 0}, texture2, 0, 0,
+                              {0, 0, 0}, kInValidExtent3D);
+        }
+
+        // Failures on invalid ImageExtent.y.
+        {
+            constexpr dawn::Extent3D kInValidExtent3D = {kSmallestValidExtent3D.width,
+                                                         kSmallestValidExtent3D.height - 1, 1};
+            TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, 4, texture, 0, 0,
+                             {0, 0, 0}, kInValidExtent3D);
+            TestBothT2TCopies(utils::Expectation::Failure, texture, 0, 0, {0, 0, 0}, texture2, 0, 0,
+                              {0, 0, 0}, kInValidExtent3D);
+        }
+    }
+}