Adding Queue::WriteTexture

Added Queue::WriteTexture with validation but no actual
implementation. Tests were mostly taken from validation tests
for copying buffer to texture. Validation tests for CopyB2T
and WriteTexture do not cover 2d-array textures yet.

Bug: dawn:483
Change-Id: I9027eb615c02fe2265cde912f6ba17a235b94728
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/24440
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Tomek Ponitka <tommek@google.com>
diff --git a/dawn.json b/dawn.json
index d7e1902..25d5070 100644
--- a/dawn.json
+++ b/dawn.json
@@ -985,6 +985,16 @@
                     {"name": "data", "type": "void", "annotation": "const*", "length": "size"},
                     {"name": "size", "type": "size_t"}
                 ]
+            },
+            {
+                "name": "write texture",
+                "args": [
+                    {"name": "destinaton", "type": "texture copy view", "annotation": "const*"},
+                    {"name": "data", "type": "void", "annotation": "const*"},
+                    {"name": "data size", "type": "size_t"},
+                    {"name": "data layout", "type": "texture data layout", "annotation": "const*"},
+                    {"name": "writeSize", "type": "extent 3D", "annotation": "const*"}
+                ]
             }
         ]
     },
diff --git a/dawn_wire.json b/dawn_wire.json
index 87063e0..6279f05 100644
--- a/dawn_wire.json
+++ b/dawn_wire.json
@@ -54,6 +54,14 @@
             {"name": "buffer offset", "type": "uint64_t"},
             {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "size"},
             {"name": "size", "type": "size_t"}
+        ],
+        "queue write texture internal": [
+            {"name": "queue id", "type": "ObjectId" },
+            {"name": "destination", "type": "texture copy view", "annotation": "const*"},
+            {"name": "data", "type": "uint8_t", "annotation": "const*"},
+            {"name": "data size", "type": "size_t"},
+            {"name": "data layout", "type": "texture data layout", "annotation": "const*"},
+            {"name": "writeSize", "type": "extent 3D", "annotation": "const*"}
         ]
     },
     "return commands": {
@@ -104,7 +112,8 @@
             "DeviceSetUncapturedErrorCallback",
             "FenceGetCompletedValue",
             "FenceOnCompletion",
-            "QueueWriteBuffer"
+            "QueueWriteBuffer",
+            "QueueWriteTexture"
         ],
         "client_handwritten_commands": [
             "BufferDestroy",
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 19ba51e..e70a378 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -16,6 +16,7 @@
 
 #include "dawn_native/Buffer.h"
 #include "dawn_native/CommandBuffer.h"
+#include "dawn_native/CommandValidation.h"
 #include "dawn_native/Device.h"
 #include "dawn_native/DynamicUploader.h"
 #include "dawn_native/ErrorScope.h"
@@ -131,6 +132,33 @@
                                                buffer, bufferOffset, size);
     }
 
+    void QueueBase::WriteTexture(const TextureCopyView* destination,
+                                 const void* data,
+                                 size_t dataSize,
+                                 const TextureDataLayout* dataLayout,
+                                 const Extent3D* writeSize) {
+        GetDevice()->ConsumedError(
+            WriteTextureInternal(destination, data, dataSize, dataLayout, writeSize));
+    }
+
+    MaybeError QueueBase::WriteTextureInternal(const TextureCopyView* destination,
+                                               const void* data,
+                                               size_t dataSize,
+                                               const TextureDataLayout* dataLayout,
+                                               const Extent3D* writeSize) {
+        DAWN_TRY(ValidateWriteTexture(destination, dataSize, dataLayout, writeSize));
+        return WriteTextureImpl(destination, data, dataSize, dataLayout, writeSize);
+    }
+
+    MaybeError QueueBase::WriteTextureImpl(const TextureCopyView* destination,
+                                           const void* data,
+                                           size_t dataSize,
+                                           const TextureDataLayout* dataLayout,
+                                           const Extent3D* writeSize) {
+        // TODO(tommek@google.com): This should be implemented.
+        return {};
+    }
+
     MaybeError QueueBase::ValidateSubmit(uint32_t commandCount,
                                          CommandBufferBase* const* commands) const {
         TRACE_EVENT0(GetDevice()->GetPlatform(), Validation, "Queue::ValidateSubmit");
@@ -215,4 +243,33 @@
         return buffer->ValidateCanUseOnQueueNow();
     }
 
+    MaybeError QueueBase::ValidateWriteTexture(const TextureCopyView* destination,
+                                               size_t dataSize,
+                                               const TextureDataLayout* dataLayout,
+                                               const Extent3D* writeSize) const {
+        DAWN_TRY(GetDevice()->ValidateIsAlive());
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+        DAWN_TRY(GetDevice()->ValidateObject(destination->texture));
+
+        DAWN_TRY(ValidateTextureCopyView(GetDevice(), *destination));
+
+        if (dataLayout->offset > dataSize) {
+            return DAWN_VALIDATION_ERROR("Queue::WriteTexture out of range");
+        }
+
+        if (!(destination->texture->GetUsage() & wgpu::TextureUsage::CopyDst)) {
+            return DAWN_VALIDATION_ERROR("Texture needs the CopyDst usage bit");
+        }
+
+        if (destination->texture->GetSampleCount() > 1) {
+            return DAWN_VALIDATION_ERROR("The sample count of textures must be 1");
+        }
+
+        DAWN_TRY(ValidateLinearTextureData(*dataLayout, dataSize, destination->texture->GetFormat(),
+                                           *writeSize));
+        DAWN_TRY(ValidateTextureCopyRange(*destination, *writeSize));
+
+        return {};
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/Queue.h b/src/dawn_native/Queue.h
index 5fd722d..694d53b 100644
--- a/src/dawn_native/Queue.h
+++ b/src/dawn_native/Queue.h
@@ -34,6 +34,11 @@
         void Signal(Fence* fence, uint64_t signalValue);
         Fence* CreateFence(const FenceDescriptor* descriptor);
         void WriteBuffer(BufferBase* buffer, uint64_t bufferOffset, const void* data, size_t size);
+        void WriteTexture(const TextureCopyView* destination,
+                          const void* data,
+                          size_t dataSize,
+                          const TextureDataLayout* dataLayout,
+                          const Extent3D* writeSize);
 
       private:
         QueueBase(DeviceBase* device, ObjectBase::ErrorTag tag);
@@ -42,12 +47,22 @@
                                        uint64_t bufferOffset,
                                        const void* data,
                                        size_t size);
+        MaybeError WriteTextureInternal(const TextureCopyView* destination,
+                                        const void* data,
+                                        size_t dataSize,
+                                        const TextureDataLayout* dataLayout,
+                                        const Extent3D* writeSize);
 
         virtual MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands);
         virtual MaybeError WriteBufferImpl(BufferBase* buffer,
                                            uint64_t bufferOffset,
                                            const void* data,
                                            size_t size);
+        virtual MaybeError WriteTextureImpl(const TextureCopyView* destination,
+                                            const void* data,
+                                            size_t dataSize,
+                                            const TextureDataLayout* dataLayout,
+                                            const Extent3D* writeSize);
 
         MaybeError ValidateSubmit(uint32_t commandCount, CommandBufferBase* const* commands) const;
         MaybeError ValidateSignal(const Fence* fence, uint64_t signalValue) const;
@@ -55,6 +70,10 @@
         MaybeError ValidateWriteBuffer(const BufferBase* buffer,
                                        uint64_t bufferOffset,
                                        size_t size) const;
+        MaybeError ValidateWriteTexture(const TextureCopyView* destination,
+                                        size_t dataSize,
+                                        const TextureDataLayout* dataLayout,
+                                        const Extent3D* writeSize) const;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_wire/client/Queue.cpp b/src/dawn_wire/client/Queue.cpp
index 902aac8..f9ee04e 100644
--- a/src/dawn_wire/client/Queue.cpp
+++ b/src/dawn_wire/client/Queue.cpp
@@ -72,4 +72,19 @@
         device->GetClient()->SerializeCommand(cmd);
     }
 
+    void Queue::WriteTexture(const WGPUTextureCopyView* destination,
+                             const void* data,
+                             size_t dataSize,
+                             const WGPUTextureDataLayout* dataLayout,
+                             const WGPUExtent3D* writeSize) {
+        QueueWriteTextureInternalCmd cmd;
+        cmd.queueId = id;
+        cmd.destination = destination;
+        cmd.dataSize = dataSize;
+        cmd.dataLayout = dataLayout;
+        cmd.writeSize = writeSize;
+
+        device->GetClient()->SerializeCommand(cmd);
+    }
+
 }}  // namespace dawn_wire::client
diff --git a/src/dawn_wire/client/Queue.h b/src/dawn_wire/client/Queue.h
index 9e51618..866bccd 100644
--- a/src/dawn_wire/client/Queue.h
+++ b/src/dawn_wire/client/Queue.h
@@ -31,6 +31,11 @@
         WGPUFence CreateFence(const WGPUFenceDescriptor* descriptor);
         void Signal(WGPUFence fence, uint64_t signalValue);
         void WriteBuffer(WGPUBuffer cBuffer, uint64_t bufferOffset, const void* data, size_t size);
+        void WriteTexture(const WGPUTextureCopyView* destination,
+                          const void* data,
+                          size_t dataSize,
+                          const WGPUTextureDataLayout* dataLayout,
+                          const WGPUExtent3D* writeSize);
     };
 
 }}  // namespace dawn_wire::client
diff --git a/src/dawn_wire/server/ServerQueue.cpp b/src/dawn_wire/server/ServerQueue.cpp
index 6e47492..0c5a6b8 100644
--- a/src/dawn_wire/server/ServerQueue.cpp
+++ b/src/dawn_wire/server/ServerQueue.cpp
@@ -55,4 +55,21 @@
         return true;
     }
 
+    bool Server::DoQueueWriteTextureInternal(ObjectId queueId,
+                                             const WGPUTextureCopyView* destination,
+                                             const uint8_t* data,
+                                             size_t dataSize,
+                                             const WGPUTextureDataLayout* dataLayout,
+                                             const WGPUExtent3D* writeSize) {
+        // The null object isn't valid as `self` so we can combine the check with the
+        // check that the ID is valid.
+        auto* queue = QueueObjects().Get(queueId);
+        if (queue == nullptr) {
+            return false;
+        }
+
+        mProcs.queueWriteTexture(queue->handle, destination, data, dataSize, dataLayout, writeSize);
+        return true;
+    }
+
 }}  // namespace dawn_wire::server
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 853e1f0..b615518 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -192,6 +192,7 @@
     "unittests/validation/MinimumBufferSizeValidationTests.cpp",
     "unittests/validation/QuerySetValidationTests.cpp",
     "unittests/validation/QueueSubmitValidationTests.cpp",
+    "unittests/validation/QueueWriteTextureValidationTests.cpp",
     "unittests/validation/RenderBundleValidationTests.cpp",
     "unittests/validation/RenderPassDescriptorValidationTests.cpp",
     "unittests/validation/RenderPipelineValidationTests.cpp",
diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
index 53e4bd4..831b934 100644
--- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
+++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
@@ -48,25 +48,12 @@
         return tex;
     }
 
-    // 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;
-        }
-    }
-
     uint32_t BufferSizeForTextureCopy(
         uint32_t width,
         uint32_t height,
         uint32_t depth,
         wgpu::TextureFormat format = wgpu::TextureFormat::RGBA8Unorm) {
-        uint32_t bytesPerPixel = TextureFormatPixelSize(format);
+        uint32_t bytesPerPixel = utils::TextureFormatPixelSize(format);
         uint32_t bytesPerRow = Align(width * bytesPerPixel, kTextureBytesPerRowAlignment);
         return (bytesPerRow * (height - 1) + width * bytesPerPixel) * depth;
     }
@@ -492,7 +479,7 @@
                 {4, 4, 1});
 
     // Image height is larger than copy height (Valid)
-    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 4, destination, 0, {0, 0, 0},
+    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 5, destination, 0, {0, 0, 0},
                 {4, 4, 1});
 
     // Image height is less than copy height (Invalid)
@@ -1357,30 +1344,6 @@
                                                 kUsage, 1);
     }
 
-    static 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;
-        }
-    }
-
     void TestBothTBCopies(utils::Expectation expectation,
                           wgpu::Buffer buffer,
                           uint64_t bufferOffset,
@@ -1410,15 +1373,6 @@
 
     static constexpr uint32_t kWidth = 16;
     static constexpr uint32_t kHeight = 16;
-
-    const std::array<wgpu::TextureFormat, 14> kBCFormats = {
-        wgpu::TextureFormat::BC1RGBAUnorm,  wgpu::TextureFormat::BC1RGBAUnormSrgb,
-        wgpu::TextureFormat::BC2RGBAUnorm,  wgpu::TextureFormat::BC2RGBAUnormSrgb,
-        wgpu::TextureFormat::BC3RGBAUnorm,  wgpu::TextureFormat::BC3RGBAUnormSrgb,
-        wgpu::TextureFormat::BC4RUnorm,     wgpu::TextureFormat::BC4RSnorm,
-        wgpu::TextureFormat::BC5RGUnorm,    wgpu::TextureFormat::BC5RGSnorm,
-        wgpu::TextureFormat::BC6HRGBUfloat, wgpu::TextureFormat::BC6HRGBSfloat,
-        wgpu::TextureFormat::BC7RGBAUnorm,  wgpu::TextureFormat::BC7RGBAUnormSrgb};
 };
 
 // Tests to verify that bufferOffset must be a multiple of the compressed texture blocks in bytes
@@ -1427,19 +1381,19 @@
     wgpu::Buffer buffer =
         CreateBuffer(512, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
 
-    for (wgpu::TextureFormat bcFormat : kBCFormats) {
+    for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
         wgpu::Texture texture = Create2DTexture(bcFormat);
 
         // Valid usages of BufferOffset in B2T and T2B copies with compressed texture formats.
         {
-            uint32_t validBufferOffset = CompressedFormatBlockSizeInBytes(bcFormat);
+            uint32_t validBufferOffset = utils::CompressedFormatBlockSizeInBytes(bcFormat);
             TestBothTBCopies(utils::Expectation::Success, buffer, validBufferOffset, 256, 4,
                              texture, 0, {0, 0, 0}, {4, 4, 1});
         }
 
         // Failures on invalid bufferOffset.
         {
-            uint32_t kInvalidBufferOffset = CompressedFormatBlockSizeInBytes(bcFormat) / 2;
+            uint32_t kInvalidBufferOffset = utils::CompressedFormatBlockSizeInBytes(bcFormat) / 2;
             TestBothTBCopies(utils::Expectation::Failure, buffer, kInvalidBufferOffset, 256, 4,
                              texture, 0, {0, 0, 0}, {4, 4, 1});
         }
@@ -1460,7 +1414,7 @@
         // Failures on the BytesPerRow that is not large enough.
         {
             constexpr uint32_t kSmallBytesPerRow = 256;
-            for (wgpu::TextureFormat bcFormat : kBCFormats) {
+            for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
                 wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
                 TestBothTBCopies(utils::Expectation::Failure, buffer, 0, kSmallBytesPerRow, 4,
                                  texture, 0, {0, 0, 0}, {kTestWidth, 4, 1});
@@ -1469,10 +1423,10 @@
 
         // Test it is not valid to use a BytesPerRow that is not a multiple of 256.
         {
-            for (wgpu::TextureFormat bcFormat : kBCFormats) {
+            for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
                 wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
                 uint32_t inValidBytesPerRow =
-                    kTestWidth / 4 * CompressedFormatBlockSizeInBytes(bcFormat);
+                    kTestWidth / 4 * utils::CompressedFormatBlockSizeInBytes(bcFormat);
                 ASSERT_NE(0u, inValidBytesPerRow % 256);
                 TestBothTBCopies(utils::Expectation::Failure, buffer, 0, inValidBytesPerRow, 4,
                                  texture, 0, {0, 0, 0}, {kTestWidth, 4, 1});
@@ -1481,10 +1435,10 @@
 
         // Test the smallest valid BytesPerRow should work.
         {
-            for (wgpu::TextureFormat bcFormat : kBCFormats) {
+            for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
                 wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
                 uint32_t smallestValidBytesPerRow =
-                    Align(kTestWidth / 4 * CompressedFormatBlockSizeInBytes(bcFormat), 256);
+                    Align(kTestWidth / 4 * utils::CompressedFormatBlockSizeInBytes(bcFormat), 256);
                 TestBothTBCopies(utils::Expectation::Success, buffer, 0, smallestValidBytesPerRow,
                                  4, texture, 0, {0, 0, 0}, {kTestWidth, 4, 1});
             }
@@ -1498,7 +1452,7 @@
     wgpu::Buffer buffer =
         CreateBuffer(512, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
 
-    for (wgpu::TextureFormat bcFormat : kBCFormats) {
+    for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
         wgpu::Texture texture = Create2DTexture(bcFormat);
 
         // Valid usages of rowsPerImage in B2T and T2B copies with compressed texture formats.
@@ -1524,7 +1478,7 @@
     wgpu::Buffer buffer =
         CreateBuffer(512, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
 
-    for (wgpu::TextureFormat bcFormat : kBCFormats) {
+    for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
         wgpu::Texture texture = Create2DTexture(bcFormat);
         wgpu::Texture texture2 = Create2DTexture(bcFormat);
 
@@ -1571,7 +1525,7 @@
     constexpr uint32_t kTestWidth = 60;
     constexpr uint32_t kTestHeight = 60;
 
-    for (wgpu::TextureFormat bcFormat : kBCFormats) {
+    for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
         wgpu::Texture texture = Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight);
         wgpu::Texture texture2 = Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight);
 
diff --git a/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
new file mode 100644
index 0000000..74e4eda
--- /dev/null
+++ b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
@@ -0,0 +1,571 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "tests/unittests/validation/ValidationTest.h"
+
+#include "common/Math.h"
+#include "utils/WGPUHelpers.h"
+
+namespace {
+
+    class QueueWriteTextureValidationTest : public ValidationTest {
+      private:
+        void SetUp() override {
+            ValidationTest::SetUp();
+            queue = device.GetDefaultQueue();
+        }
+
+      protected:
+        wgpu::Texture Create2DTexture(wgpu::Extent3D size,
+                                      uint32_t mipLevelCount,
+                                      wgpu::TextureFormat format,
+                                      wgpu::TextureUsage usage,
+                                      uint32_t sampleCount = 1) {
+            wgpu::TextureDescriptor descriptor;
+            descriptor.dimension = wgpu::TextureDimension::e2D;
+            descriptor.size.width = size.width;
+            descriptor.size.height = size.height;
+            descriptor.size.depth = size.depth;
+            descriptor.sampleCount = sampleCount;
+            descriptor.format = format;
+            descriptor.mipLevelCount = mipLevelCount;
+            descriptor.usage = usage;
+            wgpu::Texture tex = device.CreateTexture(&descriptor);
+            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,
+                              uint32_t dataRowsPerImage,
+                              wgpu::Texture texture,
+                              uint32_t texLevel,
+                              wgpu::Origin3D texOrigin,
+                              wgpu::Extent3D size) {
+            std::vector<uint8_t> data(dataSize);
+
+            wgpu::TextureDataLayout textureDataLayout;
+            textureDataLayout.offset = dataOffset;
+            textureDataLayout.bytesPerRow = dataBytesPerRow;
+            textureDataLayout.rowsPerImage = dataRowsPerImage;
+
+            wgpu::TextureCopyView textureCopyView =
+                utils::CreateTextureCopyView(texture, texLevel, texOrigin);
+
+            queue.WriteTexture(&textureCopyView, data.data(), dataSize, &textureDataLayout, &size);
+        }
+
+        wgpu::Queue queue;
+    };
+
+    // Test the success case for WriteTexture
+    TEST_F(QueueWriteTextureValidationTest, Success) {
+        const uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        wgpu::Texture destination = Create2DTexture({16, 16, 4}, 5, wgpu::TextureFormat::RGBA8Unorm,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        // Different copies, including some that touch the OOB condition
+        {
+            // Copy 4x4 block in corner of first mip.
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1});
+            // Copy 4x4 block in opposite corner of first mip.
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {12, 12, 0}, {4, 4, 1});
+            // Copy 4x4 block in the 4x4 mip.
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 2, {0, 0, 0}, {4, 4, 1});
+            // Copy with a data offset
+            TestWriteTexture(dataSize, dataSize - 4, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1});
+        }
+
+        // Copies with a 256-byte aligned bytes per row but unaligned texture region
+        {
+            // Unaligned region
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {3, 4, 1});
+            // Unaligned region with texture offset
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {5, 7, 0}, {2, 3, 1});
+            // Unaligned region, with data offset
+            TestWriteTexture(dataSize, 31 * 4, 256, 0, destination, 0, {0, 0, 0}, {3, 3, 1});
+        }
+
+        // Empty copies are valid
+        {
+            // An empty copy
+            TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 1});
+            // An empty copy with depth = 0
+            TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 0});
+            // An empty copy touching the end of the data
+            TestWriteTexture(dataSize, dataSize, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 1});
+            // An empty copy touching the side of the texture
+            TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {16, 16, 0}, {0, 0, 1});
+            // An empty copy with depth = 1 and bytesPerRow > 0
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {0, 0, 1});
+            // An empty copy with height > 0, depth = 0, bytesPerRow > 0 and rowsPerImage > 0
+            TestWriteTexture(dataSize, 0, 256, 16, destination, 0, {0, 0, 0}, {0, 1, 0});
+        }
+    }
+
+    // Test OOB conditions on the data
+    TEST_F(QueueWriteTextureValidationTest, OutOfBoundsOnData) {
+        const uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        // OOB on the data because we copy too many pixels
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 5, 1}));
+
+        // OOB on the data because of the offset
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 4, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1}));
+
+        // OOB on the data because 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
+        {
+            uint32_t sourceDataSize = RequiredBytesInCopy(256, 0, {7, 3, 1});
+            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});
+        }
+    }
+
+    // Test OOB conditions on the texture
+    TEST_F(QueueWriteTextureValidationTest, OutOfBoundsOnTexture) {
+        const uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        wgpu::Texture destination = Create2DTexture({16, 16, 2}, 5, wgpu::TextureFormat::RGBA8Unorm,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        // OOB on the texture because x + width overflows
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {13, 12, 0}, {4, 4, 1}));
+
+        // OOB on the texture because y + width overflows
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {12, 13, 0}, {4, 4, 1}));
+
+        // OOB on the texture because we overflow a non-zero mip
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 2, {1, 0, 0}, {4, 4, 1}));
+
+        // OOB on the texture even on an empty copy when we copy to a non-existent mip.
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 0, 0, destination, 5, {0, 0, 0}, {0, 0, 1}));
+
+        // OOB on the texture because slice overflows
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 2}, {0, 0, 1}));
+    }
+
+    // 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});
+        wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        // Depth > 1 on an empty copy still errors
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 2}));
+    }
+
+    // Test WriteTexture with incorrect texture usage
+    TEST_F(QueueWriteTextureValidationTest, IncorrectUsage) {
+        const uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        wgpu::Texture sampled = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
+                                                wgpu::TextureUsage::Sampled);
+
+        // Incorrect destination usage
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 256, 0, sampled, 0, {0, 0, 0}, {4, 4, 1}));
+    }
+
+    // Test incorrect values of bytesPerRow and that values not divisible by 256 are allowed.
+    TEST_F(QueueWriteTextureValidationTest, BytesPerRowLimitations) {
+        wgpu::Texture destination = Create2DTexture({3, 7, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        // bytesPerRow = 0 is invalid
+        ASSERT_DEVICE_ERROR(TestWriteTexture(128, 0, 0, 0, destination, 0, {0, 0, 0}, {3, 7, 1}));
+
+        // bytesPerRow = 11 is invalid since a row takes 12 bytes.
+        ASSERT_DEVICE_ERROR(TestWriteTexture(128, 0, 11, 0, destination, 0, {0, 0, 0}, {3, 7, 1}));
+
+        // bytesPerRow = 12 is valid since a row takes 12 bytes.
+        TestWriteTexture(128, 0, 12, 0, destination, 0, {0, 0, 0}, {3, 7, 1});
+
+        // bytesPerRow = 13 is valid since a row takes 12 bytes.
+        TestWriteTexture(128, 0, 13, 0, destination, 0, {0, 0, 0}, {3, 7, 1});
+    }
+
+    // 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});
+        wgpu::Texture destination = Create2DTexture({16, 16, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        // Image height is zero (Valid)
+        TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1});
+
+        // Image height is equal to copy height (Valid)
+        TestWriteTexture(dataSize, 0, 256, 4, destination, 0, {0, 0, 0}, {4, 4, 1});
+
+        // Image height is larger than copy height (Valid)
+        TestWriteTexture(dataSize, 0, 256, 5, destination, 0, {0, 0, 0}, {4, 4, 1});
+
+        // Image height is less than copy height (Invalid)
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 256, 3, destination, 0, {0, 0, 0}, {4, 4, 1}));
+    }
+
+    // Test WriteTexture with incorrect data offset usage
+    TEST_F(QueueWriteTextureValidationTest, IncorrectDataOffset) {
+        uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 4, 1});
+        wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        // Correct usage
+        TestWriteTexture(dataSize, dataSize - 4, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1});
+
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, dataSize - 6, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1}));
+    }
+
+    // Test multisampled textures can be used in WriteTexture.
+    TEST_F(QueueWriteTextureValidationTest, WriteToMultisampledTexture) {
+        uint64_t dataSize = RequiredBytesInCopy(256, 0, {2, 2, 1});
+        wgpu::Texture destination = Create2DTexture({2, 2, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
+                                                    wgpu::TextureUsage::CopyDst, 4);
+
+        ASSERT_DEVICE_ERROR(
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {2, 2, 1}));
+    }
+
+    // Test WriteTexture with texture in error state causes errors.
+    TEST_F(QueueWriteTextureValidationTest, TextureInErrorState) {
+        wgpu::TextureDescriptor errorTextureDescriptor;
+        errorTextureDescriptor.size.depth = 0;
+        ASSERT_DEVICE_ERROR(wgpu::Texture errorTexture =
+                                device.CreateTexture(&errorTextureDescriptor));
+        wgpu::TextureCopyView errorTextureCopyView =
+            utils::CreateTextureCopyView(errorTexture, 0, {0, 0, 0});
+
+        wgpu::Extent3D extent3D = {1, 1, 1};
+
+        {
+            std::vector<uint8_t> data(4);
+            wgpu::TextureDataLayout textureDataLayout;
+            textureDataLayout.offset = 0;
+            textureDataLayout.bytesPerRow = 0;
+            textureDataLayout.rowsPerImage = 0;
+
+            ASSERT_DEVICE_ERROR(queue.WriteTexture(&errorTextureCopyView, data.data(), 4,
+                                                   &textureDataLayout, &extent3D));
+        }
+    }
+
+    // Regression tests for a bug in the computation of texture data size in Dawn.
+    TEST_F(QueueWriteTextureValidationTest, TextureWriteDataSizeLastRowComputation) {
+        constexpr uint32_t kBytesPerRow = 256;
+        constexpr uint32_t kWidth = 4;
+        constexpr uint32_t kHeight = 4;
+
+        constexpr std::array<wgpu::TextureFormat, 2> kFormats = {wgpu::TextureFormat::RGBA8Unorm,
+                                                                 wgpu::TextureFormat::RG8Unorm};
+
+        {
+            // kBytesPerRow * (kHeight - 1) + kWidth is not large enough to be the valid data size
+            // in this test because the data sizes in WriteTexture are not in texels but in bytes.
+            constexpr uint32_t kInvalidDataSize = kBytesPerRow * (kHeight - 1) + kWidth;
+
+            for (wgpu::TextureFormat format : kFormats) {
+                wgpu::Texture destination =
+                    Create2DTexture({kWidth, kHeight, 1}, 1, format, wgpu::TextureUsage::CopyDst);
+                ASSERT_DEVICE_ERROR(TestWriteTexture(kInvalidDataSize, 0, kBytesPerRow, 0,
+                                                     destination, 0, {0, 0, 0},
+                                                     {kWidth, kHeight, 1}));
+            }
+        }
+
+        {
+            for (wgpu::TextureFormat format : kFormats) {
+                uint32_t validDataSize =
+                    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
+                // data size in this test.
+                {
+                    uint32_t invalidDataSize = validDataSize - 1;
+                    ASSERT_DEVICE_ERROR(TestWriteTexture(invalidDataSize, 0, kBytesPerRow, 0,
+                                                         destination, 0, {0, 0, 0},
+                                                         {kWidth, kHeight, 1}));
+                }
+
+                {
+                    TestWriteTexture(validDataSize, 0, kBytesPerRow, 0, destination, 0, {0, 0, 0},
+                                     {kWidth, kHeight, 1});
+                }
+            }
+        }
+    }
+
+    // Test write from data to mip map of non square texture
+    TEST_F(QueueWriteTextureValidationTest, WriteToMipmapOfNonSquareTexture) {
+        uint64_t dataSize = RequiredBytesInCopy(256, 0, {4, 2, 1});
+        uint32_t maxMipmapLevel = 3;
+        wgpu::Texture destination =
+            Create2DTexture({4, 2, 1}, maxMipmapLevel, wgpu::TextureFormat::RGBA8Unorm,
+                            wgpu::TextureUsage::CopyDst);
+
+        // Copy to top level mip map
+        TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 1, {0, 0, 0},
+                         {1, 1, 1});
+        // Copy to high level mip map
+        TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 2, {0, 0, 0},
+                         {2, 1, 1});
+        // Mip level out of range
+        ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel,
+                                             {0, 0, 0}, {1, 1, 1}));
+        // Copy origin out of range
+        ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 2,
+                                             {1, 0, 0}, {2, 1, 1}));
+        // Copy size out of range
+        ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 2,
+                                             {0, 0, 0}, {2, 2, 1}));
+    }
+
+    class WriteTextureTest_CompressedTextureFormats : public QueueWriteTextureValidationTest {
+      public:
+        WriteTextureTest_CompressedTextureFormats() : QueueWriteTextureValidationTest() {
+            device = CreateDeviceFromAdapter(adapter, {"texture_compression_bc"});
+        }
+
+      protected:
+        wgpu::Texture Create2DTexture(wgpu::TextureFormat format,
+                                      uint32_t mipmapLevels = 1,
+                                      uint32_t width = kWidth,
+                                      uint32_t height = kHeight) {
+            constexpr wgpu::TextureUsage kUsage = wgpu::TextureUsage::CopyDst;
+            constexpr uint32_t kArrayLayers = 1;
+            return QueueWriteTextureValidationTest::Create2DTexture(
+                {width, height, kArrayLayers}, mipmapLevels, format, kUsage, 1);
+        }
+
+        void TestWriteTexture(size_t dataSize,
+                              uint32_t dataOffset,
+                              uint32_t dataBytesPerRow,
+                              uint32_t dataRowsPerImage,
+                              wgpu::Texture texture,
+                              uint32_t textLevel,
+                              wgpu::Origin3D textOrigin,
+                              wgpu::Extent3D size) {
+            QueueWriteTextureValidationTest::TestWriteTexture(dataSize, dataOffset, dataBytesPerRow,
+                                                              dataRowsPerImage, texture, textLevel,
+                                                              textOrigin, size);
+        }
+
+        static constexpr uint32_t kWidth = 16;
+        static constexpr uint32_t kHeight = 16;
+    };
+
+    // Tests to verify that data offset must be a multiple of the compressed texture blocks in bytes
+    TEST_F(WriteTextureTest_CompressedTextureFormats, DataOffset) {
+        for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
+            wgpu::Texture texture = Create2DTexture(bcFormat);
+
+            // Valid usages of data offset.
+            {
+                uint32_t validDataOffset = utils::CompressedFormatBlockSizeInBytes(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;
+                ASSERT_DEVICE_ERROR(TestWriteTexture(512, kInvalidDataOffset, 256, 4, texture, 0,
+                                                     {0, 0, 0}, {4, 4, 1}));
+            }
+        }
+    }
+
+    // Tests to verify that bytesPerRow must not be less than (width / blockWidth) *
+    // blockSizeInBytes and that it doesn't have to be a multiple of the compressed
+    // texture block width.
+    TEST_F(WriteTextureTest_CompressedTextureFormats, BytesPerRow) {
+        constexpr uint32_t kTestWidth = 160;
+        constexpr uint32_t kTestHeight = 160;
+
+        // Failures on the BytesPerRow that is not large enough.
+        {
+            constexpr uint32_t kSmallBytesPerRow = 256;
+            for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
+                wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
+                ASSERT_DEVICE_ERROR(TestWriteTexture(1024, 0, kSmallBytesPerRow, 4, texture, 0,
+                                                     {0, 0, 0}, {kTestWidth, 4, 1}));
+            }
+        }
+
+        // Test it is valid to use a BytesPerRow that is not a multiple of 256.
+        {
+            for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
+                wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
+                uint32_t ValidBytesPerRow =
+                    kTestWidth / 4 * utils::CompressedFormatBlockSizeInBytes(bcFormat);
+                ASSERT_NE(0u, ValidBytesPerRow % 256);
+                TestWriteTexture(1024, 0, ValidBytesPerRow, 4, texture, 0, {0, 0, 0},
+                                 {kTestWidth, 4, 1});
+            }
+        }
+
+        for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
+            wgpu::Texture texture = Create2DTexture(bcFormat);
+
+            // Valid usage of bytesPerRow in WriteTexture with compressed texture formats.
+            {
+                constexpr uint32_t kValidBytesPerRow = 20;
+                TestWriteTexture(512, 0, kValidBytesPerRow, 0, texture, 0, {0, 0, 0}, {4, 4, 1});
+            }
+
+            // Valid bytesPerRow.
+            // Note that image width is not a multiple of blockWidth.
+            {
+                constexpr uint32_t kValidBytesPerRow = 17;
+                TestWriteTexture(512, 0, kValidBytesPerRow, 0, texture, 0, {0, 0, 0}, {4, 4, 1});
+            }
+        }
+    }
+
+    // Tests to verify that rowsPerImage must be a multiple of the compressed texture block height
+    TEST_F(WriteTextureTest_CompressedTextureFormats, RowsPerImage) {
+        for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
+            wgpu::Texture texture = Create2DTexture(bcFormat);
+
+            // Valid usages of rowsPerImage in WriteTexture with compressed texture formats.
+            {
+                constexpr uint32_t kValidRowsPerImage = 8;
+                TestWriteTexture(512, 0, 256, kValidRowsPerImage, texture, 0, {0, 0, 0}, {4, 4, 1});
+            }
+
+            // 4 is the exact limit for rowsPerImage here.
+            {
+                constexpr uint32_t kValidRowsPerImage = 4;
+                TestWriteTexture(512, 0, 256, kValidRowsPerImage, texture, 0, {0, 0, 0}, {4, 4, 1});
+            }
+
+            // Failure on invalid rowsPerImage.
+            {
+                constexpr uint32_t kInvalidRowsPerImage = 2;
+                ASSERT_DEVICE_ERROR(TestWriteTexture(512, 0, 256, kInvalidRowsPerImage, texture, 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
+    TEST_F(WriteTextureTest_CompressedTextureFormats, ImageOffset) {
+        for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
+            wgpu::Texture texture = Create2DTexture(bcFormat);
+            wgpu::Texture texture2 = Create2DTexture(bcFormat);
+
+            constexpr wgpu::Origin3D kSmallestValidOrigin3D = {4, 4, 0};
+
+            // Valid usages of ImageOffset in WriteTexture with compressed texture formats.
+            { TestWriteTexture(512, 0, 256, 4, texture, 0, kSmallestValidOrigin3D, {4, 4, 1}); }
+
+            // Failures on invalid ImageOffset.x.
+            {
+                constexpr wgpu::Origin3D kInvalidOrigin3D = {kSmallestValidOrigin3D.x - 1,
+                                                             kSmallestValidOrigin3D.y, 0};
+                ASSERT_DEVICE_ERROR(
+                    TestWriteTexture(512, 0, 256, 4, texture, 0, kInvalidOrigin3D, {4, 4, 1}));
+            }
+
+            // Failures on invalid ImageOffset.y.
+            {
+                constexpr wgpu::Origin3D kInvalidOrigin3D = {kSmallestValidOrigin3D.x,
+                                                             kSmallestValidOrigin3D.y - 1, 0};
+                ASSERT_DEVICE_ERROR(
+                    TestWriteTexture(512, 0, 256, 4, texture, 0, kInvalidOrigin3D, {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
+    TEST_F(WriteTextureTest_CompressedTextureFormats, ImageExtent) {
+        constexpr uint32_t kMipmapLevels = 3;
+        constexpr uint32_t kTestWidth = 60;
+        constexpr uint32_t kTestHeight = 60;
+
+        for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
+            wgpu::Texture texture =
+                Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight);
+            wgpu::Texture texture2 =
+                Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight);
+
+            constexpr wgpu::Extent3D kSmallestValidExtent3D = {4, 4, 1};
+
+            // Valid usages of ImageExtent in WriteTexture with compressed texture formats.
+            { TestWriteTexture(512, 0, 256, 8, texture, 0, {0, 0, 0}, kSmallestValidExtent3D); }
+
+            // Valid usages of ImageExtent in WriteTexture with compressed texture formats
+            // and non-zero mipmap levels.
+            {
+                constexpr uint32_t kTestMipmapLevel = 2;
+                constexpr wgpu::Origin3D kTestOrigin = {
+                    (kTestWidth >> kTestMipmapLevel) - kSmallestValidExtent3D.width + 1,
+                    (kTestHeight >> kTestMipmapLevel) - kSmallestValidExtent3D.height + 1, 0};
+
+                TestWriteTexture(512, 0, 256, 4, texture, kTestMipmapLevel, kTestOrigin,
+                                 kSmallestValidExtent3D);
+            }
+
+            // Failures on invalid ImageExtent.x.
+            {
+                constexpr wgpu::Extent3D kInValidExtent3D = {kSmallestValidExtent3D.width - 1,
+                                                             kSmallestValidExtent3D.height, 1};
+                ASSERT_DEVICE_ERROR(
+                    TestWriteTexture(512, 0, 256, 4, texture, 0, {0, 0, 0}, kInValidExtent3D));
+            }
+
+            // Failures on invalid ImageExtent.y.
+            {
+                constexpr wgpu::Extent3D kInValidExtent3D = {kSmallestValidExtent3D.width,
+                                                             kSmallestValidExtent3D.height - 1, 1};
+                ASSERT_DEVICE_ERROR(
+                    TestWriteTexture(512, 0, 256, 4, texture, 0, {0, 0, 0}, kInValidExtent3D));
+            }
+        }
+    }
+
+}  // anonymous namespace
\ No newline at end of file
diff --git a/src/utils/WGPUHelpers.cpp b/src/utils/WGPUHelpers.cpp
index 686b223..3280586 100644
--- a/src/utils/WGPUHelpers.cpp
+++ b/src/utils/WGPUHelpers.cpp
@@ -417,4 +417,51 @@
 
         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,
+        wgpu::TextureFormat::BC3RGBAUnorm,  wgpu::TextureFormat::BC3RGBAUnormSrgb,
+        wgpu::TextureFormat::BC4RUnorm,     wgpu::TextureFormat::BC4RSnorm,
+        wgpu::TextureFormat::BC5RGUnorm,    wgpu::TextureFormat::BC5RGSnorm,
+        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;
+        }
+    }
+
 }  // namespace utils
diff --git a/src/utils/WGPUHelpers.h b/src/utils/WGPUHelpers.h
index fdbd864..6c7825f 100644
--- a/src/utils/WGPUHelpers.h
+++ b/src/utils/WGPUHelpers.h
@@ -151,6 +151,10 @@
         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);
+
 }  // namespace utils
 
 #endif  // UTILS_DAWNHELPERS_H_