[dawn][native] Use strong types for Command structs

Specifically, modify TextureCopy, CopyBufferToTextureCmd,
CopyTextureToBufferCmd, and CopyTextureToTextureCmd structs to use
strong types (TexelOrigin3D and TexelExtent3D), and update code that
uses them.

Also opportunistically update other code to use strong types.

Bug: 424536624
Change-Id: I36c777f3d1acc00b8a5facf437224c416b502ec2
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/276714
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Auto-Submit: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/dawn/native/BlitBufferToDepthStencil.cpp b/src/dawn/native/BlitBufferToDepthStencil.cpp
index 2e5c972..1555c55 100644
--- a/src/dawn/native/BlitBufferToDepthStencil.cpp
+++ b/src/dawn/native/BlitBufferToDepthStencil.cpp
@@ -262,7 +262,7 @@
                                  CommandEncoder* commandEncoder,
                                  TextureBase* dataTexture,
                                  const TextureCopy& dst,
-                                 const Extent3D& copyExtent) {
+                                 const TexelExtent3D& copyExtent) {
     DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
     DAWN_ASSERT(dst.texture->GetFormat().format == wgpu::TextureFormat::Depth16Unorm);
     DAWN_ASSERT(dataTexture->GetFormat().format == wgpu::TextureFormat::RG8Uint);
@@ -277,12 +277,12 @@
     Ref<BindGroupLayoutBase> bgl;
     DAWN_TRY_ASSIGN(bgl, pipeline->GetBindGroupLayout(0));
 
-    for (uint32_t z = 0; z < copyExtent.depthOrArrayLayers; ++z) {
+    for (TexelCount z{0}; z < copyExtent.depthOrArrayLayers; ++z) {
         Ref<TextureViewBase> srcView;
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = wgpu::TextureViewDimension::e2D;
-            viewDesc.baseArrayLayer = z;
+            viewDesc.baseArrayLayer = static_cast<uint32_t>(z);
             viewDesc.arrayLayerCount = 1;
             viewDesc.mipLevelCount = 1;
             DAWN_TRY_ASSIGN(srcView, dataTexture->CreateView(&viewDesc));
@@ -292,7 +292,7 @@
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = wgpu::TextureViewDimension::e2D;
-            viewDesc.baseArrayLayer = dst.origin.z + z;
+            viewDesc.baseArrayLayer = static_cast<uint32_t>(dst.origin.z + z);
             viewDesc.arrayLayerCount = 1;
             viewDesc.baseMipLevel = dst.mipLevel;
             viewDesc.mipLevelCount = 1;
@@ -309,8 +309,8 @@
 
             uint32_t* params =
                 static_cast<uint32_t*>(paramsBuffer->GetMappedRange(0, bufferDesc.size));
-            params[0] = dst.origin.x;
-            params[1] = dst.origin.y;
+            params[0] = static_cast<uint32_t>(dst.origin.x);
+            params[1] = static_cast<uint32_t>(dst.origin.y);
             DAWN_TRY(paramsBuffer->Unmap());
         }
 
@@ -342,7 +342,9 @@
         // Bind the resources.
         pass->APISetBindGroup(0, bindGroup.Get());
         // Discard all fragments outside the copy region.
-        pass->APISetScissorRect(dst.origin.x, dst.origin.y, copyExtent.width, copyExtent.height);
+        pass->APISetScissorRect(
+            static_cast<uint32_t>(dst.origin.x), static_cast<uint32_t>(dst.origin.y),
+            static_cast<uint32_t>(copyExtent.width), static_cast<uint32_t>(copyExtent.height));
 
         // Draw to perform the blit.
         pass->APISetPipeline(pipeline.Get());
@@ -412,8 +414,8 @@
         DAWN_TRY_ASSIGN(paramsBuffer, device->CreateBuffer(&bufferDesc));
 
         uint32_t* params = static_cast<uint32_t*>(paramsBuffer->GetMappedRange(0, bufferDesc.size));
-        params[0] = dst.origin.x;
-        params[1] = dst.origin.y;
+        params[0] = static_cast<uint32_t>(dst.origin.x);
+        params[1] = static_cast<uint32_t>(dst.origin.y);
         params[2] = 0;
         DAWN_TRY(paramsBuffer->Unmap());
     }
@@ -441,7 +443,7 @@
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = textureViewDimension;
-            viewDesc.baseArrayLayer = dst.origin.z + z;
+            viewDesc.baseArrayLayer = static_cast<uint32_t>(dst.origin.z) + z;
             viewDesc.arrayLayerCount = 1;
             viewDesc.baseMipLevel = dst.mipLevel;
             viewDesc.mipLevelCount = 1;
@@ -481,7 +483,9 @@
         // Bind the resources.
         pass->APISetBindGroup(0, bindGroup.Get());
         // Discard all fragments outside the copy region.
-        pass->APISetScissorRect(dst.origin.x, dst.origin.y, copyExtent.width, copyExtent.height);
+        pass->APISetScissorRect(static_cast<uint32_t>(dst.origin.x),
+                                static_cast<uint32_t>(dst.origin.y), copyExtent.width,
+                                copyExtent.height);
 
         // Clear the copy region to 0.
         pass->APISetStencilReference(0);
diff --git a/src/dawn/native/BlitBufferToTexture.cpp b/src/dawn/native/BlitBufferToTexture.cpp
index 168714e..bca1e3d 100644
--- a/src/dawn/native/BlitBufferToTexture.cpp
+++ b/src/dawn/native/BlitBufferToTexture.cpp
@@ -361,7 +361,7 @@
 
 bool IsBufferToTextureBlitSupported(BufferBase* buffer,
                                     const TextureCopy& dst,
-                                    const Extent3D& copyExtent) {
+                                    const TexelExtent3D& copyExtent) {
     if (!(buffer->GetInternalUsage() &
           (kReadOnlyStorageBuffer | kInternalStorageBuffer | wgpu::BufferUsage::Storage))) {
         return false;
@@ -382,7 +382,7 @@
     }
 
     // Must have non-zero copy size.
-    return copyExtent.width * copyExtent.height * copyExtent.depthOrArrayLayers > 0;
+    return copyExtent.width * copyExtent.height * copyExtent.depthOrArrayLayers > TexelCount{0};
 }
 
 MaybeError BlitBufferToTexture(DeviceBase* device,
@@ -390,7 +390,7 @@
                                BufferBase* buffer,
                                const TexelCopyBufferLayout& src,
                                const TextureCopy& dst,
-                               const Extent3D& copyExtent) {
+                               const TexelExtent3D& copyExtent) {
     DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
 
     // This function assumes bytesPerRow is multiples of 4. Normally it's required that
@@ -402,7 +402,7 @@
     DAWN_ASSERT(buffer->GetInternalUsage() &
                 (kReadOnlyStorageBuffer | kInternalStorageBuffer | wgpu::BufferUsage::Storage));
 
-    DAWN_ASSERT(copyExtent.width > 0 && copyExtent.height > 0 && copyExtent.depthOrArrayLayers > 0);
+    DAWN_ASSERT(!copyExtent.IsEmpty());
 
     // Allow internal usages since we need to use the destination
     // as a render attachment.
@@ -428,29 +428,30 @@
             break;
         case wgpu::TextureDimension::e3D:
             viewDimension = wgpu::TextureViewDimension::e3D;
-            baseDepth = dst.origin.z;
+            baseDepth = static_cast<uint32_t>(dst.origin.z);
             depthStep = 1;
             break;
         default:
             viewDimension = wgpu::TextureViewDimension::e2D;
-            baseArray = dst.origin.z;
+            baseArray = static_cast<uint32_t>(dst.origin.z);
             arrayStep = 1;
             break;
     }
 
-    for (uint32_t z = 0; z < copyExtent.depthOrArrayLayers; ++z) {
+    for (TexelCount z{0}; z < copyExtent.depthOrArrayLayers; ++z) {
         Ref<TextureViewBase> dstView;
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = viewDimension;
-            viewDesc.baseArrayLayer = baseArray + arrayStep * z;
+            viewDesc.baseArrayLayer = baseArray + arrayStep * static_cast<uint32_t>(z);
             viewDesc.arrayLayerCount = 1;
             viewDesc.baseMipLevel = dst.mipLevel;
             viewDesc.mipLevelCount = 1;
             DAWN_TRY_ASSIGN(dstView, dst.texture->CreateView(&viewDesc));
         }
 
-        const uint64_t srcOffset = src.offset + z * src.rowsPerImage * src.bytesPerRow;
+        const uint64_t srcOffset =
+            src.offset + static_cast<uint32_t>(z) * src.rowsPerImage * src.bytesPerRow;
         const uint64_t srcBufferBindingOffset = AlignDown(srcOffset, ssboAlignment);
         const uint32_t shaderReadOffset = static_cast<uint32_t>(srcOffset & (ssboAlignment - 1));
         Ref<BufferBase> paramsBuffer;
@@ -461,8 +462,8 @@
             uint32_t params[4];
             params[0] = shaderReadOffset;
             params[1] = src.bytesPerRow;
-            params[2] = dst.origin.x;
-            params[3] = dst.origin.y;
+            params[2] = static_cast<uint32_t>(dst.origin.x);
+            params[3] = static_cast<uint32_t>(dst.origin.y);
             commandEncoder->APIWriteBuffer(paramsBuffer.Get(), 0,
                                            reinterpret_cast<const uint8_t*>(&params[0]),
                                            sizeof(params));
@@ -479,7 +480,7 @@
         RenderPassColorAttachment colorAttachment;
         colorAttachment.view = dstView.Get();
         if (depthStep) {
-            colorAttachment.depthSlice = baseDepth + depthStep * z;
+            colorAttachment.depthSlice = baseDepth + depthStep * static_cast<uint32_t>(z);
         }
         colorAttachment.loadOp = wgpu::LoadOp::Load;
         colorAttachment.storeOp = wgpu::StoreOp::Store;
@@ -491,9 +492,11 @@
         Ref<RenderPassEncoder> pass = commandEncoder->BeginRenderPass(&rpDesc);
         // Bind the resources.
         pass->APISetBindGroup(0, bindGroup.Get());
-        pass->APISetViewport(static_cast<float>(dst.origin.x), static_cast<float>(dst.origin.y),
-                             static_cast<float>(copyExtent.width),
-                             static_cast<float>(copyExtent.height), 0.f, 1.f);
+        pass->APISetViewport(static_cast<float>(static_cast<uint32_t>(dst.origin.x)),
+                             static_cast<float>(static_cast<uint32_t>(dst.origin.y)),
+                             static_cast<float>(static_cast<uint32_t>(copyExtent.width)),
+                             static_cast<float>(static_cast<uint32_t>(copyExtent.height)), 0.f,
+                             1.f);
 
         // Draw to perform the blit.
         pass->APISetPipeline(pipeline.Get());
diff --git a/src/dawn/native/BlitBufferToTexture.h b/src/dawn/native/BlitBufferToTexture.h
index 20497a5..c524b57 100644
--- a/src/dawn/native/BlitBufferToTexture.h
+++ b/src/dawn/native/BlitBufferToTexture.h
@@ -35,18 +35,19 @@
 struct Format;
 struct TextureCopy;
 struct TexelCopyBufferLayout;
+struct TexelExtent3D;
 
 bool IsFormatSupportedByBufferToTextureBlit(wgpu::TextureFormat format);
 bool IsBufferToTextureBlitSupported(BufferBase* buffer,
                                     const TextureCopy& dst,
-                                    const Extent3D& copyExtent);
+                                    const TexelExtent3D& copyExtent);
 
 MaybeError BlitBufferToTexture(DeviceBase* device,
                                CommandEncoder* commandEncoder,
                                BufferBase* buffer,
                                const TexelCopyBufferLayout& src,
                                const TextureCopy& dst,
-                               const Extent3D& copyExtent);
+                               const TexelExtent3D& copyExtent);
 
 }  // namespace dawn::native
 
diff --git a/src/dawn/native/BlitDepthToDepth.cpp b/src/dawn/native/BlitDepthToDepth.cpp
index f7f7f8b..da615c4 100644
--- a/src/dawn/native/BlitDepthToDepth.cpp
+++ b/src/dawn/native/BlitDepthToDepth.cpp
@@ -32,6 +32,7 @@
 
 #include "dawn/common/Assert.h"
 #include "dawn/native/BindGroup.h"
+#include "dawn/native/BlockInfo.h"
 #include "dawn/native/CommandEncoder.h"
 #include "dawn/native/Device.h"
 #include "dawn/native/InternalPipelineStore.h"
@@ -109,7 +110,7 @@
                             CommandEncoder* commandEncoder,
                             const TextureCopy& src,
                             const TextureCopy& dst,
-                            const Extent3D& copyExtent) {
+                            const TexelExtent3D& copyExtent) {
     DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
     // DAWN_ASSERT that the texture have depth and are not multisampled.
     DAWN_ASSERT(src.texture->GetFormat().HasDepth());
@@ -136,12 +137,12 @@
     // also don't textureLoad in the shader at a non-zero array index correctly. Workaround this
     // issue by copying the non-zero array slices to a single-layer texture. That texture will be be
     // sampled as the source instead.
-    std::vector<Ref<TextureViewBase>> srcViews;
+    ityp::vector<TexelCount, Ref<TextureViewBase>> srcViews;
     srcViews.reserve(copyExtent.depthOrArrayLayers);
-    for (uint32_t z = 0; z < copyExtent.depthOrArrayLayers; ++z) {
-        uint32_t layer = src.origin.z + z;
+    for (TexelCount z = TexelCount{0}; z < copyExtent.depthOrArrayLayers; ++z) {
+        TexelCount layer = src.origin.z + z;
         Ref<TextureViewBase> srcView;
-        if (layer == 0u) {
+        if (layer == TexelCount{0}) {
             // The zero'th slice. We can use the original texture.
             TextureViewDescriptor viewDesc = {};
             viewDesc.aspect = wgpu::TextureAspect::DepthOnly;
@@ -155,7 +156,8 @@
             intermediateTexDesc.format = src.texture->GetFormat().format;
             intermediateTexDesc.usage =
                 wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst;
-            intermediateTexDesc.size = {copyExtent.width, copyExtent.height};
+            intermediateTexDesc.size = {static_cast<uint32_t>(copyExtent.width),
+                                        static_cast<uint32_t>(copyExtent.height)};
 
             Ref<TextureBase> intermediateTexture;
             DAWN_TRY_ASSIGN(intermediateTexture, device->CreateTexture(&intermediateTexDesc));
@@ -165,7 +167,7 @@
                 TexelCopyTextureInfo intermediateSrc;
                 intermediateSrc.texture = src.texture.Get();
                 intermediateSrc.mipLevel = src.mipLevel;
-                intermediateSrc.origin = {0, 0, layer};
+                intermediateSrc.origin = {0, 0, static_cast<uint32_t>(layer)};
                 intermediateSrc.aspect = wgpu::TextureAspect::All;
 
                 TexelCopyTextureInfo intermediateDst;
@@ -190,7 +192,7 @@
     }
 
     // For each copied layer, blit from the source into the destination.
-    for (uint32_t z = 0; z < copyExtent.depthOrArrayLayers; ++z) {
+    for (TexelCount z = TexelCount{0}; z < copyExtent.depthOrArrayLayers; ++z) {
         Ref<BindGroupBase> bindGroup;
         {
             BindGroupEntry bgEntry = {};
@@ -209,7 +211,7 @@
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = wgpu::TextureViewDimension::e2D;
-            viewDesc.baseArrayLayer = dst.origin.z + z;
+            viewDesc.baseArrayLayer = static_cast<uint32_t>(dst.origin.z + z);
             viewDesc.arrayLayerCount = 1;
             viewDesc.baseMipLevel = dst.mipLevel;
             viewDesc.mipLevelCount = 1;
diff --git a/src/dawn/native/BlitDepthToDepth.h b/src/dawn/native/BlitDepthToDepth.h
index 5818ec1..e64da44 100644
--- a/src/dawn/native/BlitDepthToDepth.h
+++ b/src/dawn/native/BlitDepthToDepth.h
@@ -35,13 +35,13 @@
 class DeviceBase;
 class CommandEncoder;
 struct TextureCopy;
-struct Extent3D;
+struct TexelExtent3D;
 
 MaybeError BlitDepthToDepth(DeviceBase* device,
                             CommandEncoder* commandEncoder,
                             const TextureCopy& src,
                             const TextureCopy& dst,
-                            const Extent3D& copyExtent);
+                            const TexelExtent3D& copyExtent);
 
 }  // namespace dawn::native
 
diff --git a/src/dawn/native/BlitTextureToBuffer.cpp b/src/dawn/native/BlitTextureToBuffer.cpp
index ebffad5..b45b715 100644
--- a/src/dawn/native/BlitTextureToBuffer.cpp
+++ b/src/dawn/native/BlitTextureToBuffer.cpp
@@ -1238,9 +1238,9 @@
         uint32_t* params =
             static_cast<uint32_t*>(uniformBuffer->GetMappedRange(0, bufferDesc.size));
         // srcOrigin: vec3u
-        params[0] = src.origin.x;
-        params[1] = src.origin.y;
-        params[2] = src.origin.z;
+        params[0] = static_cast<uint32_t>(src.origin.x);
+        params[1] = static_cast<uint32_t>(src.origin.y);
+        params[2] = static_cast<uint32_t>(src.origin.z);
 
         // packTexelCount: number of texel values (1, 2, or 4) one thread packs into the dst
         // buffer
diff --git a/src/dawn/native/BlockInfo.h b/src/dawn/native/BlockInfo.h
index 8005be5..5dab8bb 100644
--- a/src/dawn/native/BlockInfo.h
+++ b/src/dawn/native/BlockInfo.h
@@ -68,6 +68,9 @@
     constexpr Origin3D ToOrigin3D() const {
         return {static_cast<uint32_t>(x), static_cast<uint32_t>(y), static_cast<uint32_t>(z)};
     }
+
+    // Comparison operator
+    constexpr bool operator==(const TexelOrigin3D&) const = default;
 };
 
 // Stores an origin in block space
@@ -82,6 +85,9 @@
                             BlockCount y = BlockCount{0},
                             BlockCount z = BlockCount{0})
         : x(x), y(y), z(z) {}
+
+    // Comparison operator
+    constexpr bool operator==(const BlockOrigin3D&) const = default;
 };
 
 // Strong type version of Extent3D.
@@ -111,6 +117,15 @@
         return {static_cast<uint32_t>(width), static_cast<uint32_t>(height),
                 static_cast<uint32_t>(depthOrArrayLayers)};
     }
+
+    // Comparison operator
+    constexpr bool operator==(const TexelExtent3D&) const = default;
+
+    // Returns true if any extent is zero
+    bool IsEmpty() const {
+        return width == TexelCount{0} || height == TexelCount{0} ||
+               depthOrArrayLayers == TexelCount{0};
+    }
 };
 
 // Stores an extent in block space
@@ -128,6 +143,15 @@
                             BlockCount height = BlockCount{1},
                             BlockCount depthOrArrayLayers = BlockCount{1})
         : width(width), height(height), depthOrArrayLayers(depthOrArrayLayers) {}
+
+    // Comparison operator
+    constexpr bool operator==(const BlockExtent3D&) const = default;
+
+    // Returns true if any extent is zero
+    bool IsEmpty() const {
+        return width == BlockCount{0} || height == BlockCount{0} ||
+               depthOrArrayLayers == BlockCount{0};
+    }
 };
 
 // Strong type version of TexelBlockInfo that stores the dimensions of the block
diff --git a/src/dawn/native/CommandBuffer.cpp b/src/dawn/native/CommandBuffer.cpp
index d78885a..dcac779 100644
--- a/src/dawn/native/CommandBuffer.cpp
+++ b/src/dawn/native/CommandBuffer.cpp
@@ -110,12 +110,12 @@
 }
 
 bool IsCompleteSubresourceCopiedTo(const TextureBase* texture,
-                                   const Extent3D& copySize,
+                                   const TexelExtent3D& copySize,
                                    const uint32_t mipLevel,
                                    Aspect aspect) {
     DAWN_ASSERT(HasOneBit(aspect) || aspect == (Aspect::Depth | Aspect::Stencil));
 
-    Extent3D extent = texture->GetMipLevelSingleSubresourcePhysicalSize(mipLevel, aspect);
+    TexelExtent3D extent = texture->GetMipLevelSingleSubresourcePhysicalSize(mipLevel, aspect);
 
     switch (texture->GetDimension()) {
         case wgpu::TextureDimension::e1D:
@@ -132,21 +132,26 @@
 }
 
 bool IsCompleteSubresourceCopiedTo(const TextureBase* texture,
-                                   const Extent3D& copySize,
+                                   const TexelExtent3D& copySize,
                                    const uint32_t mipLevel,
                                    wgpu::TextureAspect textureAspect) {
     auto aspect = SelectFormatAspects(texture->GetFormat(), textureAspect);
     return IsCompleteSubresourceCopiedTo(texture, copySize, mipLevel, aspect);
 }
 
-SubresourceRange GetSubresourcesAffectedByCopy(const TextureCopy& copy, const Extent3D& copySize) {
+SubresourceRange GetSubresourcesAffectedByCopy(const TextureCopy& copy,
+                                               const TexelExtent3D& copySize) {
     switch (copy.texture->GetDimension()) {
         case wgpu::TextureDimension::e1D:
-            DAWN_ASSERT(copy.origin.z == 0 && copySize.depthOrArrayLayers == 1);
+            DAWN_ASSERT(copy.origin.z == TexelCount{0} &&
+                        copySize.depthOrArrayLayers == TexelCount{1});
             DAWN_ASSERT(copy.mipLevel == 0);
             return {copy.aspect, {0, 1}, {0, 1}};
         case wgpu::TextureDimension::e2D:
-            return {copy.aspect, {copy.origin.z, copySize.depthOrArrayLayers}, {copy.mipLevel, 1}};
+            return {copy.aspect,
+                    {static_cast<uint32_t>(copy.origin.z),
+                     static_cast<uint32_t>(copySize.depthOrArrayLayers)},
+                    {copy.mipLevel, 1}};
         case wgpu::TextureDimension::e3D:
             return {copy.aspect, {0, 1}, {copy.mipLevel, 1}};
         case wgpu::TextureDimension::Undefined:
@@ -276,7 +281,7 @@
 
 bool IsFullBufferOverwrittenInTextureToBufferCopy(const TextureCopy& source,
                                                   const BufferCopy& destination,
-                                                  const Extent3D& copySize_in) {
+                                                  const TexelExtent3D& copySize_in) {
     if (destination.offset > 0) {
         // The copy doesn't touch the start of the buffer.
         return false;
diff --git a/src/dawn/native/CommandBuffer.h b/src/dawn/native/CommandBuffer.h
index bd8ebce..6373f4a 100644
--- a/src/dawn/native/CommandBuffer.h
+++ b/src/dawn/native/CommandBuffer.h
@@ -83,21 +83,22 @@
 };
 
 bool IsCompleteSubresourceCopiedTo(const TextureBase* texture,
-                                   const Extent3D& copySize,
+                                   const TexelExtent3D& copySize,
                                    const uint32_t mipLevel,
                                    Aspect aspect);
 bool IsCompleteSubresourceCopiedTo(const TextureBase* texture,
-                                   const Extent3D& copySize,
+                                   const TexelExtent3D& copySize,
                                    const uint32_t mipLevel,
                                    wgpu::TextureAspect textureAspect);
-SubresourceRange GetSubresourcesAffectedByCopy(const TextureCopy& copy, const Extent3D& copySize);
+SubresourceRange GetSubresourcesAffectedByCopy(const TextureCopy& copy,
+                                               const TexelExtent3D& copySize);
 
 void LazyClearRenderPassAttachments(BeginRenderPassCmd* renderPass);
 
 bool IsFullBufferOverwrittenInTextureToBufferCopy(const CopyTextureToBufferCmd* copy);
 bool IsFullBufferOverwrittenInTextureToBufferCopy(const TextureCopy& source,
                                                   const BufferCopy& destination,
-                                                  const Extent3D& copySize);
+                                                  const TexelExtent3D& copySize);
 
 std::array<float, 4> ConvertToFloatColor(dawn::native::Color color);
 std::array<int32_t, 4> ConvertToSignedIntegerColor(dawn::native::Color color);
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp
index 3ae15f6..6a0d1b7 100644
--- a/src/dawn/native/CommandEncoder.cpp
+++ b/src/dawn/native/CommandEncoder.cpp
@@ -1987,7 +1987,8 @@
                 (aspect & Aspect::Depth) &&
                 GetDevice()->IsToggleEnabled(
                     Toggle::UseBlitForDepthTextureToTextureCopyToNonzeroSubresource) &&
-                (dst.mipLevel > 0 || dst.origin.z > 0 || copySize->depthOrArrayLayers > 1);
+                (dst.mipLevel > 0 || dst.origin.z > TexelCount{0} ||
+                 copySize->depthOrArrayLayers > 1);
 
             // If we're not using a blit, or there are aspects other than depth,
             // issue the copy. This is because if there's also stencil, we still need the copy
diff --git a/src/dawn/native/CommandValidation.cpp b/src/dawn/native/CommandValidation.cpp
index 01d47ed..c7966d5 100644
--- a/src/dawn/native/CommandValidation.cpp
+++ b/src/dawn/native/CommandValidation.cpp
@@ -244,17 +244,6 @@
     return {};
 }
 
-bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length) {
-    if (length < 1) {
-        return false;
-    }
-    return RangesOverlap<uint64_t>(
-        static_cast<uint64_t>(startA),
-        static_cast<uint64_t>(startA) + static_cast<uint64_t>(length) - 1,
-        static_cast<uint64_t>(startB),
-        static_cast<uint64_t>(startB) + static_cast<uint64_t>(length) - 1);
-}
-
 ResultOrError<uint64_t> ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo,
                                                    const Extent3D& copySize,
                                                    uint32_t bytesPerRow,
diff --git a/src/dawn/native/CommandValidation.h b/src/dawn/native/CommandValidation.h
index c6836d5..c253ddf 100644
--- a/src/dawn/native/CommandValidation.h
+++ b/src/dawn/native/CommandValidation.h
@@ -32,6 +32,7 @@
 
 #include "absl/container/inlined_vector.h"
 #include "dawn/common/Constants.h"
+#include "dawn/common/Numeric.h"
 #include "dawn/native/BlockInfo.h"
 #include "dawn/native/CommandAllocator.h"
 #include "dawn/native/Error.h"
@@ -106,7 +107,17 @@
                                         uint64_t size,
                                         BufferSizeType checkBufferSizeType = BufferSizeType::Size);
 
-bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length);
+// Returns true if [startA, startA + length[ overlaps [startB, startB + length[
+template <typename T>
+bool IsRangeOverlapped(T startA, T startB, T length) {
+    if (length < T{1}) {
+        return false;
+    }
+    return RangesOverlap(static_cast<uint64_t>(startA),
+                         static_cast<uint64_t>(startA) + static_cast<uint64_t>(length) - 1,
+                         static_cast<uint64_t>(startB),
+                         static_cast<uint64_t>(startB) + static_cast<uint64_t>(length) - 1);
+}
 
 MaybeError ValidateTextureToTextureCopyCommonRestrictions(DeviceBase const* device,
                                                           const TexelCopyTextureInfo& src,
diff --git a/src/dawn/native/Commands.h b/src/dawn/native/Commands.h
index e927a71..7519438 100644
--- a/src/dawn/native/Commands.h
+++ b/src/dawn/native/Commands.h
@@ -206,7 +206,7 @@
 
     Ref<TextureBase> texture;
     uint32_t mipLevel;
-    Origin3D origin;  // Texels / array layer
+    TexelOrigin3D origin;  // Texels / array layer
     Aspect aspect;
 };
 
@@ -227,19 +227,19 @@
 struct CopyBufferToTextureCmd {
     BufferCopy source;
     TextureCopy destination;
-    Extent3D copySize;  // Texels
+    TexelExtent3D copySize;
 };
 
 struct CopyTextureToBufferCmd {
     TextureCopy source;
     BufferCopy destination;
-    Extent3D copySize;  // Texels
+    TexelExtent3D copySize;
 };
 
 struct CopyTextureToTextureCmd {
     TextureCopy source;
     TextureCopy destination;
-    Extent3D copySize;  // Texels
+    TexelExtent3D copySize;
 };
 
 struct DispatchCmd {
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 910595d..8454057 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -1334,8 +1334,8 @@
 
 bool TextureBase::CoversFullSubresource(uint32_t mipLevel,
                                         Aspect aspect,
-                                        const Extent3D& size) const {
-    Extent3D levelSize = GetMipLevelSingleSubresourcePhysicalSize(mipLevel, aspect);
+                                        const TexelExtent3D& size) const {
+    TexelExtent3D levelSize = GetMipLevelSingleSubresourcePhysicalSize(mipLevel, aspect);
     switch (GetDimension()) {
         case wgpu::TextureDimension::Undefined:
             DAWN_UNREACHABLE();
diff --git a/src/dawn/native/Texture.h b/src/dawn/native/Texture.h
index c46a4e4..c132424 100644
--- a/src/dawn/native/Texture.h
+++ b/src/dawn/native/Texture.h
@@ -38,6 +38,7 @@
 #include "dawn/common/WeakRef.h"
 #include "dawn/common/ityp_array.h"
 #include "dawn/common/ityp_bitset.h"
+#include "dawn/native/BlockInfo.h"
 #include "dawn/native/Error.h"
 #include "dawn/native/Format.h"
 #include "dawn/native/Forward.h"
@@ -194,7 +195,7 @@
     bool IsMultisampledTexture() const;
 
     // Returns true if the size covers the whole subresource.
-    bool CoversFullSubresource(uint32_t mipLevel, Aspect aspect, const Extent3D& size) const;
+    bool CoversFullSubresource(uint32_t mipLevel, Aspect aspect, const TexelExtent3D& size) const;
 
     // For a texture with non-block-compressed texture format, its physical size is always equal
     // to its virtual size. For a texture with block compressed texture format, the physical
diff --git a/src/dawn/native/d3d11/CommandBufferD3D11.cpp b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
index 02659a8..70c70d2 100644
--- a/src/dawn/native/d3d11/CommandBufferD3D11.cpp
+++ b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
@@ -365,8 +365,7 @@
 
             case Command::CopyBufferToTexture: {
                 CopyBufferToTextureCmd* copy = mCommands.NextCommand<CopyBufferToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -410,8 +409,9 @@
                 DAWN_ASSERT(scopedMap.GetMappedData());
                 const uint8_t* data = scopedMap.GetMappedData() + bufferOffset;
                 uint64_t bytesPerRow = blockInfo.ToBytes(src.blocksPerRow);
-                DAWN_TRY(texture->Write(commandContext, subresources, dst.origin, copy->copySize,
-                                        data, static_cast<uint32_t>(bytesPerRow),
+                DAWN_TRY(texture->Write(commandContext, subresources, dst.origin.ToOrigin3D(),
+                                        copy->copySize.ToExtent3D(), data,
+                                        static_cast<uint32_t>(bytesPerRow),
                                         static_cast<uint32_t>(src.rowsPerImage)));
 
                 buffer->MarkUsedInPendingCommands();
@@ -420,8 +420,7 @@
 
             case Command::CopyTextureToBuffer: {
                 CopyTextureToBufferCmd* copy = mCommands.NextCommand<CopyTextureToBufferCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -453,8 +452,8 @@
                 uint64_t bytesPerRow = blockInfo.ToBytes(dst.blocksPerRow);
 
                 DAWN_TRY(ToBackend(src.texture)
-                             ->Read(commandContext, subresources, src.origin, copy->copySize,
-                                    static_cast<uint32_t>(bytesPerRow),
+                             ->Read(commandContext, subresources, src.origin.ToOrigin3D(),
+                                    copy->copySize.ToExtent3D(), static_cast<uint32_t>(bytesPerRow),
                                     static_cast<uint32_t>(dst.rowsPerImage), callback));
 
                 dst.buffer->MarkUsedInPendingCommands();
@@ -463,8 +462,7 @@
 
             case Command::CopyTextureToTexture: {
                 CopyTextureToTextureCmd* copy = mCommands.NextCommand<CopyTextureToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
diff --git a/src/dawn/native/d3d11/TextureD3D11.cpp b/src/dawn/native/d3d11/TextureD3D11.cpp
index d1d4e9f..a6c3e0f 100644
--- a/src/dawn/native/d3d11/TextureD3D11.cpp
+++ b/src/dawn/native/d3d11/TextureD3D11.cpp
@@ -801,7 +801,7 @@
         copyCmd.source.mipLevel = subresources.baseMipLevel;
         copyCmd.source.aspect = otherAspects;
         copyCmd.destination.texture = stagingTexture.Get();
-        copyCmd.destination.origin = {0, 0, 0};
+        copyCmd.destination.origin = {TexelCount{0}, TexelCount{0}, TexelCount{0}};
         copyCmd.destination.mipLevel = 0;
         copyCmd.destination.aspect = otherAspects;
         copyCmd.copySize = size;
@@ -840,7 +840,7 @@
     // Copy to the dest texture from the staging texture.
     CopyTextureToTextureCmd copyCmd;
     copyCmd.source.texture = stagingTexture.Get();
-    copyCmd.source.origin = {0, 0, 0};
+    copyCmd.source.origin = {TexelCount{0}, TexelCount{0}, TexelCount{0}};
     copyCmd.source.mipLevel = 0;
     copyCmd.source.aspect = GetFormat().aspects;
     copyCmd.destination.texture = this;
@@ -981,7 +981,7 @@
     copyCmd.source.mipLevel = subresources.baseMipLevel;
     copyCmd.source.aspect = subresources.aspects;
     copyCmd.destination.texture = stagingTexture.Get();
-    copyCmd.destination.origin = {0, 0, 0};
+    copyCmd.destination.origin = {TexelCount{0}, TexelCount{0}, TexelCount{0}};
     copyCmd.destination.mipLevel = 0;
     copyCmd.destination.aspect = subresources.aspects;
     copyCmd.copySize = size;
@@ -997,8 +997,7 @@
 // static
 MaybeError Texture::Copy(const ScopedCommandRecordingContext* commandContext,
                          CopyTextureToTextureCmd* copy) {
-    DAWN_ASSERT(copy->copySize.width != 0 && copy->copySize.height != 0 &&
-                copy->copySize.depthOrArrayLayers != 0);
+    DAWN_ASSERT(!copy->copySize.IsEmpty());
 
     auto& src = copy->source;
     auto& dst = copy->destination;
@@ -1040,10 +1039,10 @@
     SubresourceRange dstSubresources = GetSubresourcesAffectedByCopy(dst, copy->copySize);
 
     D3D11_BOX srcBox;
-    srcBox.left = src.origin.x;
-    srcBox.right = src.origin.x + copy->copySize.width;
-    srcBox.top = src.origin.y;
-    srcBox.bottom = src.origin.y + copy->copySize.height;
+    srcBox.left = static_cast<uint32_t>(src.origin.x);
+    srcBox.right = static_cast<uint32_t>(src.origin.x + copy->copySize.width);
+    srcBox.top = static_cast<uint32_t>(src.origin.y);
+    srcBox.bottom = static_cast<uint32_t>(src.origin.y + copy->copySize.height);
     switch (src.texture->GetDimension()) {
         case wgpu::TextureDimension::Undefined:
             DAWN_UNREACHABLE();
@@ -1053,8 +1052,8 @@
             srcBox.back = 1;
             break;
         case wgpu::TextureDimension::e3D:
-            srcBox.front = src.origin.z;
-            srcBox.back = src.origin.z + copy->copySize.depthOrArrayLayers;
+            srcBox.front = static_cast<uint32_t>(src.origin.z);
+            srcBox.back = static_cast<uint32_t>(src.origin.z + copy->copySize.depthOrArrayLayers);
             break;
     }
 
@@ -1072,8 +1071,11 @@
             dst.texture->GetSubresourceIndex(dst.mipLevel, dstSubresources.baseArrayLayer + layer,
                                              D3D11Aspect(dstSubresources.aspects));
         commandContext->CopySubresourceRegion(
-            ToBackend(dst.texture)->GetD3D11Resource(), dstSubresource, dst.origin.x, dst.origin.y,
-            dst.texture->GetDimension() == wgpu::TextureDimension::e3D ? dst.origin.z : 0,
+            ToBackend(dst.texture)->GetD3D11Resource(), dstSubresource,
+            static_cast<uint32_t>(dst.origin.x), static_cast<uint32_t>(dst.origin.y),
+            dst.texture->GetDimension() == wgpu::TextureDimension::e3D
+                ? static_cast<uint32_t>(dst.origin.z)
+                : 0,
             ToBackend(src.texture)->GetD3D11Resource(), srcSubresource,
             isWholeSubresource ? nullptr : &srcBox);
     }
diff --git a/src/dawn/native/d3d12/CommandBufferD3D12.cpp b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
index 15f6c51..599941e 100644
--- a/src/dawn/native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
@@ -83,14 +83,16 @@
     }
 }
 
-bool CanUseCopyResource(const TextureCopy& src, const TextureCopy& dst, const Extent3D& copySize) {
+bool CanUseCopyResource(const TextureCopy& src,
+                        const TextureCopy& dst,
+                        const TexelExtent3D& copySize) {
     // Checked by validation
     DAWN_ASSERT(src.texture->GetSampleCount() == dst.texture->GetSampleCount());
     DAWN_ASSERT(src.texture->GetFormat().CopyCompatibleWith(dst.texture->GetFormat()));
     DAWN_ASSERT(src.aspect == dst.aspect);
 
-    const Extent3D& srcSize = src.texture->GetSize(src.aspect);
-    const Extent3D& dstSize = dst.texture->GetSize(dst.aspect);
+    const TexelExtent3D& srcSize = src.texture->GetSize(src.aspect);
+    const TexelExtent3D& dstSize = dst.texture->GetSize(dst.aspect);
 
     // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-copyresource
     // In order to use D3D12's copy resource, the textures must be the same dimensions, and
@@ -983,8 +985,7 @@
 
             case Command::CopyBufferToTexture: {
                 CopyBufferToTextureCmd* copy = mCommands.NextCommand<CopyBufferToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -994,9 +995,9 @@
                 DAWN_TRY(buffer->EnsureDataInitialized(commandContext));
 
                 SubresourceRange subresources =
-                    GetSubresourcesAffectedByCopy(copy->destination, copy->copySize);
+                    GetSubresourcesAffectedByCopy(copy->destination, copy->copySize.ToExtent3D());
 
-                if (IsCompleteSubresourceCopiedTo(texture, copy->copySize,
+                if (IsCompleteSubresourceCopiedTo(texture, copy->copySize.ToExtent3D(),
                                                   copy->destination.mipLevel,
                                                   copy->destination.aspect)) {
                     texture->SetIsSubresourceContentInitialized(true, subresources);
@@ -1024,8 +1025,7 @@
 
             case Command::CopyTextureToBuffer: {
                 CopyTextureToBufferCmd* copy = mCommands.NextCommand<CopyTextureToBufferCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -1035,7 +1035,7 @@
                 DAWN_TRY(buffer->EnsureDataInitializedAsDestination(commandContext, copy));
 
                 SubresourceRange subresources =
-                    GetSubresourcesAffectedByCopy(copy->source, copy->copySize);
+                    GetSubresourcesAffectedByCopy(copy->source, copy->copySize.ToExtent3D());
 
                 DAWN_TRY(
                     texture->EnsureSubresourceContentInitialized(commandContext, subresources));
@@ -1060,8 +1060,7 @@
 
             case Command::CopyTextureToTexture: {
                 CopyTextureToTextureCmd* copy = mCommands.NextCommand<CopyTextureToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -1069,12 +1068,12 @@
                 Texture* destination = ToBackend(copy->destination.texture.Get());
 
                 SubresourceRange srcRange =
-                    GetSubresourcesAffectedByCopy(copy->source, copy->copySize);
+                    GetSubresourcesAffectedByCopy(copy->source, copy->copySize.ToExtent3D());
                 SubresourceRange dstRange =
-                    GetSubresourcesAffectedByCopy(copy->destination, copy->copySize);
+                    GetSubresourcesAffectedByCopy(copy->destination, copy->copySize.ToExtent3D());
 
                 DAWN_TRY(source->EnsureSubresourceContentInitialized(commandContext, srcRange));
-                if (IsCompleteSubresourceCopiedTo(destination, copy->copySize,
+                if (IsCompleteSubresourceCopiedTo(destination, copy->copySize.ToExtent3D(),
                                                   copy->destination.mipLevel,
                                                   copy->destination.aspect)) {
                     destination->SetIsSubresourceContentInitialized(true, dstRange);
@@ -1124,25 +1123,27 @@
                             ComputeD3D12BoxFromOffsetAndSize(copy->source.origin, copy->copySize);
 
                         commandList->CopyTextureRegion(
-                            &dstLocation, copy->destination.origin.x, copy->destination.origin.y,
-                            copy->destination.origin.z, &srcLocation, &sourceRegion);
+                            &dstLocation, static_cast<uint32_t>(copy->destination.origin.x),
+                            static_cast<uint32_t>(copy->destination.origin.y),
+                            static_cast<uint32_t>(copy->destination.origin.z), &srcLocation,
+                            &sourceRegion);
                     }
                 } else {
-                    const dawn::native::Extent3D copyExtentOneSlice = {copy->copySize.width,
-                                                                       copy->copySize.height, 1u};
+                    const TexelExtent3D copyExtentOneSlice = {copy->copySize.width,
+                                                              copy->copySize.height, TexelCount{1}};
 
                     for (Aspect aspect : IterateEnumMask(srcRange.aspects)) {
-                        for (uint32_t z = 0; z < copy->copySize.depthOrArrayLayers; ++z) {
+                        for (TexelCount z{0}; z < copy->copySize.depthOrArrayLayers; ++z) {
                             uint32_t sourceLayer = 0;
-                            uint32_t sourceZ = 0;
+                            TexelCount sourceZ{0};
                             switch (source->GetDimension()) {
                                 case wgpu::TextureDimension::Undefined:
                                     DAWN_UNREACHABLE();
                                 case wgpu::TextureDimension::e1D:
-                                    DAWN_ASSERT(copy->source.origin.z == 0);
+                                    DAWN_ASSERT(copy->source.origin.z == TexelCount{0});
                                     break;
                                 case wgpu::TextureDimension::e2D:
-                                    sourceLayer = copy->source.origin.z + z;
+                                    sourceLayer = static_cast<uint32_t>(copy->source.origin.z + z);
                                     break;
                                 case wgpu::TextureDimension::e3D:
                                     sourceZ = copy->source.origin.z + z;
@@ -1150,15 +1151,16 @@
                             }
 
                             uint32_t destinationLayer = 0;
-                            uint32_t destinationZ = 0;
+                            TexelCount destinationZ{0};
                             switch (destination->GetDimension()) {
                                 case wgpu::TextureDimension::Undefined:
                                     DAWN_UNREACHABLE();
                                 case wgpu::TextureDimension::e1D:
-                                    DAWN_ASSERT(copy->destination.origin.z == 0);
+                                    DAWN_ASSERT(copy->destination.origin.z == TexelCount{0});
                                     break;
                                 case wgpu::TextureDimension::e2D:
-                                    destinationLayer = copy->destination.origin.z + z;
+                                    destinationLayer =
+                                        static_cast<uint32_t>(copy->destination.origin.z + z);
                                     break;
                                 case wgpu::TextureDimension::e3D:
                                     destinationZ = copy->destination.origin.z + z;
@@ -1173,14 +1175,15 @@
                                                                      copy->destination.mipLevel,
                                                                      destinationLayer, aspect);
 
-                            Origin3D sourceOriginInSubresource = copy->source.origin;
+                            TexelOrigin3D sourceOriginInSubresource = copy->source.origin;
                             sourceOriginInSubresource.z = sourceZ;
                             D3D12_BOX sourceRegion = ComputeD3D12BoxFromOffsetAndSize(
                                 sourceOriginInSubresource, copyExtentOneSlice);
 
-                            commandList->CopyTextureRegion(&dstLocation, copy->destination.origin.x,
-                                                           copy->destination.origin.y, destinationZ,
-                                                           &srcLocation, &sourceRegion);
+                            commandList->CopyTextureRegion(
+                                &dstLocation, static_cast<uint32_t>(copy->destination.origin.x),
+                                static_cast<uint32_t>(copy->destination.origin.y),
+                                static_cast<uint32_t>(destinationZ), &srcLocation, &sourceRegion);
                         }
                     }
                 }
diff --git a/src/dawn/native/d3d12/TextureD3D12.cpp b/src/dawn/native/d3d12/TextureD3D12.cpp
index 5e50d11..ce72985 100644
--- a/src/dawn/native/d3d12/TextureD3D12.cpp
+++ b/src/dawn/native/d3d12/TextureD3D12.cpp
@@ -924,7 +924,7 @@
 
                             TextureCopy textureCopy;
                             textureCopy.texture = this;
-                            textureCopy.origin = {0, 0, layer};
+                            textureCopy.origin = {TexelCount{0}, TexelCount{0}, TexelCount{layer}};
                             textureCopy.mipLevel = level;
                             textureCopy.aspect = aspect;
                             RecordBufferTextureCopyWithBufferHandle(
diff --git a/src/dawn/native/d3d12/UtilsD3D12.cpp b/src/dawn/native/d3d12/UtilsD3D12.cpp
index 0c5304b8..07db936 100644
--- a/src/dawn/native/d3d12/UtilsD3D12.cpp
+++ b/src/dawn/native/d3d12/UtilsD3D12.cpp
@@ -386,8 +386,8 @@
         extentForTheLastImage.depthOrArrayLayers = BlockCount{1};
 
         TextureCopy textureCopyForTheLastImage = textureCopy;
-        textureCopyForTheLastImage.origin.z += static_cast<uint32_t>(
-            blockInfo.ToTexelDepth(copySize.depthOrArrayLayers) - TexelCount{1});
+        textureCopyForTheLastImage.origin.z +=
+            blockInfo.ToTexelDepth(copySize.depthOrArrayLayers) - TexelCount{1};
 
         // We offset the copy so that we skip the padding rows. This way the footprint Height
         // will be computed without this padding.
diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm
index f4e5eed..10c8d02 100644
--- a/src/dawn/native/metal/CommandBufferMTL.mm
+++ b/src/dawn/native/metal/CommandBufferMTL.mm
@@ -1236,8 +1236,7 @@
 
             case Command::CopyBufferToTexture: {
                 CopyBufferToTextureCmd* copy = mCommands.NextCommand<CopyBufferToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -1249,22 +1248,22 @@
                 const TypedTexelBlockInfo& blockInfo = GetBlockInfo(dst);
 
                 buffer->EnsureDataInitialized(commandContext);
-                DAWN_TRY(
-                    EnsureDestinationTextureInitialized(commandContext, texture, dst, copySize));
+                DAWN_TRY(EnsureDestinationTextureInitialized(commandContext, texture, dst,
+                                                             copySize.ToExtent3D()));
 
                 buffer->TrackUsage();
                 texture->SynchronizeTextureBeforeUse(commandContext);
                 RecordCopyBufferToTexture(commandContext, buffer->GetMTLBuffer(), buffer->GetSize(),
                                           src.offset, blockInfo.ToBytes(src.blocksPerRow),
                                           static_cast<uint32_t>(src.rowsPerImage), texture,
-                                          dst.mipLevel, dst.origin, dst.aspect, copySize);
+                                          dst.mipLevel, dst.origin.ToOrigin3D(), dst.aspect,
+                                          copySize.ToExtent3D());
                 break;
             }
 
             case Command::CopyTextureToBuffer: {
                 CopyTextureToBufferCmd* copy = mCommands.NextCommand<CopyTextureToBufferCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -1283,9 +1282,9 @@
                 buffer->TrackUsage();
 
                 TextureBufferCopySplit splitCopies = ComputeTextureBufferCopySplit(
-                    texture, src.mipLevel, src.origin, copySize, buffer->GetSize(), dst.offset,
-                    blockInfo.ToBytes(dst.blocksPerRow), static_cast<uint32_t>(dst.rowsPerImage),
-                    src.aspect);
+                    texture, src.mipLevel, src.origin.ToOrigin3D(), copySize.ToExtent3D(),
+                    buffer->GetSize(), dst.offset, blockInfo.ToBytes(dst.blocksPerRow),
+                    static_cast<uint32_t>(dst.rowsPerImage), src.aspect);
 
                 for (const auto& copyInfo : splitCopies) {
                     MTLBlitOption blitOption = texture->ComputeMTLBlitOption(src.aspect);
@@ -1362,8 +1361,7 @@
 
             case Command::CopyTextureToTexture: {
                 CopyTextureToTextureCmd* copy = mCommands.NextCommand<CopyTextureToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -1374,11 +1372,12 @@
                 dstTexture->SynchronizeTextureBeforeUse(commandContext);
                 DAWN_TRY(srcTexture->EnsureSubresourceContentInitialized(
                     commandContext, GetSubresourcesAffectedByCopy(copy->source, copy->copySize)));
-                DAWN_TRY(EnsureDestinationTextureInitialized(commandContext, dstTexture,
-                                                             copy->destination, copy->copySize));
+                DAWN_TRY(EnsureDestinationTextureInitialized(
+                    commandContext, dstTexture, copy->destination, copy->copySize.ToExtent3D()));
 
                 const MTLSize sizeOneSlice =
-                    MTLSizeMake(copy->copySize.width, copy->copySize.height, 1);
+                    MTLSizeMake(static_cast<uint32_t>(copy->copySize.width),
+                                static_cast<uint32_t>(copy->copySize.height), 1);
 
                 uint32_t sourceLayer = 0;
                 uint32_t sourceOriginZ = 0;
@@ -1401,9 +1400,9 @@
                 }
 
                 // TODO(crbug.com/dawn/782): Do a single T2T copy if both are 1D or 3D.
-                for (uint32_t z = 0; z < copy->copySize.depthOrArrayLayers; ++z) {
-                    *sourceZPtr = copy->source.origin.z + z;
-                    *destinationZPtr = copy->destination.origin.z + z;
+                for (TexelCount z{0}; z < copy->copySize.depthOrArrayLayers; ++z) {
+                    *sourceZPtr = static_cast<uint32_t>(copy->source.origin.z + z);
+                    *destinationZPtr = static_cast<uint32_t>(copy->destination.origin.z + z);
 
                     // Hold the ref until out of scope
                     NSPRef<id<MTLTexture>> dstTextureView =
@@ -1413,15 +1412,18 @@
                           copyFromTexture:srcTexture->GetMTLTexture(copy->source.aspect)
                               sourceSlice:sourceLayer
                               sourceLevel:copy->source.mipLevel
-                             sourceOrigin:MTLOriginMake(copy->source.origin.x,
-                                                        copy->source.origin.y, sourceOriginZ)
+                             sourceOrigin:MTLOriginMake(
+                                              static_cast<uint32_t>(copy->source.origin.x),
+                                              static_cast<uint32_t>(copy->source.origin.y),
+                                              sourceOriginZ)
                                sourceSize:sizeOneSlice
                                 toTexture:dstTextureView.Get()
                          destinationSlice:destinationLayer
                          destinationLevel:copy->destination.mipLevel
-                        destinationOrigin:MTLOriginMake(copy->destination.origin.x,
-                                                        copy->destination.origin.y,
-                                                        destinationOriginZ)];
+                        destinationOrigin:MTLOriginMake(
+                                              static_cast<uint32_t>(copy->destination.origin.x),
+                                              static_cast<uint32_t>(copy->destination.origin.y),
+                                              destinationOriginZ)];
                 }
                 break;
             }
diff --git a/src/dawn/native/metal/DeviceMTL.mm b/src/dawn/native/metal/DeviceMTL.mm
index 550cb0f..edba61b 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -359,8 +359,8 @@
     RecordCopyBufferToTexture(
         ToBackend(GetQueue())->GetPendingCommandContext(QueueBase::SubmitMode::Passive),
         ToBackend(source)->GetMTLBuffer(), source->GetSize(), dataLayout.offset,
-        dataLayout.bytesPerRow, dataLayout.rowsPerImage, texture, dst.mipLevel, dst.origin,
-        dst.aspect, copySizePixels);
+        dataLayout.bytesPerRow, dataLayout.rowsPerImage, texture, dst.mipLevel,
+        dst.origin.ToOrigin3D(), dst.aspect, copySizePixels);
     return {};
 }
 
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index 2c23af6..33d5b66e 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -707,10 +707,11 @@
 // within srcImage/dstImage. Here the size of the image refers to the virtual size, while
 // Dawn validates texture copy extent with the physical size, so we need to re-calculate the
 // texture copy extent to ensure it should fit in the virtual size of the subresource.
-Extent3D ComputeTextureCopyExtent(const TextureCopy& textureCopy, const Extent3D& copySize) {
-    Extent3D validTextureCopyExtent = copySize;
+TexelExtent3D ComputeTextureCopyExtent(const TextureCopy& textureCopy,
+                                       const TexelExtent3D& copySize) {
+    TexelExtent3D validTextureCopyExtent = copySize;
     const TextureBase* texture = textureCopy.texture.Get();
-    Extent3D virtualSizeAtLevel =
+    TexelExtent3D virtualSizeAtLevel =
         texture->GetMipLevelSingleSubresourceVirtualSize(textureCopy.mipLevel, textureCopy.aspect);
     DAWN_ASSERT(textureCopy.origin.x <= virtualSizeAtLevel.width);
     DAWN_ASSERT(textureCopy.origin.y <= virtualSizeAtLevel.height);
@@ -821,8 +822,7 @@
 
             case Command::CopyBufferToTexture: {
                 CopyBufferToTextureCmd* copy = mCommands.NextCommand<CopyBufferToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -834,9 +834,10 @@
                 DAWN_TRY(texture->SynchronizeTextureBeforeUse());
 
                 DAWN_TRY(buffer->EnsureDataInitialized());
-                SubresourceRange range = GetSubresourcesAffectedByCopy(dst, copy->copySize);
-                if (IsCompleteSubresourceCopiedTo(texture, copy->copySize, dst.mipLevel,
-                                                  dst.aspect)) {
+                SubresourceRange range =
+                    GetSubresourcesAffectedByCopy(dst, copy->copySize.ToExtent3D());
+                if (IsCompleteSubresourceCopiedTo(texture, copy->copySize.ToExtent3D(),
+                                                  dst.mipLevel, dst.aspect)) {
                     texture->SetIsSubresourceContentInitialized(true, range);
                 } else {
                     DAWN_TRY(texture->EnsureSubresourceContentInitialized(gl, range));
@@ -852,7 +853,7 @@
                 dataLayout.rowsPerImage = static_cast<uint32_t>(src.rowsPerImage);
 
                 DAWN_TRY(DoTexSubImage(gl, dst, reinterpret_cast<void*>(src.offset), dataLayout,
-                                       copy->copySize));
+                                       copy->copySize.ToExtent3D()));
                 DAWN_GL_TRY(gl, BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0));
 
                 buffer->TrackUsage();
@@ -861,8 +862,7 @@
 
             case Command::CopyTextureToBuffer: {
                 CopyTextureToBufferCmd* copy = mCommands.NextCommand<CopyTextureToBufferCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -882,7 +882,8 @@
                 DAWN_TRY(buffer->EnsureDataInitializedAsDestination(copy));
                 DAWN_TRY(texture->SynchronizeTextureBeforeUse());
 
-                SubresourceRange subresources = GetSubresourcesAffectedByCopy(src, copy->copySize);
+                SubresourceRange subresources =
+                    GetSubresourcesAffectedByCopy(src, copy->copySize.ToExtent3D());
                 DAWN_TRY(texture->EnsureSubresourceContentInitialized(gl, subresources));
                 // The only way to move data from a texture to a buffer in GL is via
                 // glReadPixels with a pack buffer. Create a temporary FBO for the copy.
@@ -938,23 +939,28 @@
                             DAWN_GL_TRY(
                                 gl, FramebufferTexture2D(GL_READ_FRAMEBUFFER, glAttachment, target,
                                                          texture->GetHandle(), src.mipLevel));
-                            DAWN_GL_TRY(gl, ReadPixels(src.origin.x, src.origin.y, copySize.width,
-                                                       copySize.height, glFormat, glType, offset));
+                            DAWN_GL_TRY(gl, ReadPixels(static_cast<uint32_t>(src.origin.x),
+                                                       static_cast<uint32_t>(src.origin.y),
+                                                       static_cast<uint32_t>(copySize.width),
+                                                       static_cast<uint32_t>(copySize.height),
+                                                       glFormat, glType, offset));
                             break;
                         } else if (target == GL_TEXTURE_CUBE_MAP) {
                             DAWN_ASSERT(texture->GetArrayLayers() == 6);
                             const uint64_t bytesPerImage =
                                 blockInfo.ToBytes(dst.blocksPerRow * dst.rowsPerImage);
-                            for (uint32_t z = 0; z < copySize.depthOrArrayLayers; ++z) {
-                                GLenum cubeMapTarget =
-                                    GL_TEXTURE_CUBE_MAP_POSITIVE_X + z + src.origin.z;
+                            for (TexelCount z{0}; z < copySize.depthOrArrayLayers; ++z) {
+                                GLenum cubeMapTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X +
+                                                       static_cast<uint32_t>(z + src.origin.z);
                                 DAWN_GL_TRY(
                                     gl, FramebufferTexture2D(GL_READ_FRAMEBUFFER, glAttachment,
                                                              cubeMapTarget, texture->GetHandle(),
                                                              src.mipLevel));
-                                DAWN_GL_TRY(gl,
-                                            ReadPixels(src.origin.x, src.origin.y, copySize.width,
-                                                       copySize.height, glFormat, glType, offset));
+                                DAWN_GL_TRY(gl, ReadPixels(static_cast<uint32_t>(src.origin.x),
+                                                           static_cast<uint32_t>(src.origin.y),
+                                                           static_cast<uint32_t>(copySize.width),
+                                                           static_cast<uint32_t>(copySize.height),
+                                                           glFormat, glType, offset));
                                 offset += bytesPerImage;
                             }
                             break;
@@ -966,13 +972,16 @@
                     case wgpu::TextureDimension::e3D: {
                         const uint64_t bytesPerImage =
                             blockInfo.ToBytes(dst.blocksPerRow * dst.rowsPerImage);
-                        for (uint32_t z = 0; z < copySize.depthOrArrayLayers; ++z) {
+                        for (TexelCount z{0}; z < copySize.depthOrArrayLayers; ++z) {
                             DAWN_GL_TRY(gl,
-                                        FramebufferTextureLayer(GL_READ_FRAMEBUFFER, glAttachment,
-                                                                texture->GetHandle(), src.mipLevel,
-                                                                src.origin.z + z));
-                            DAWN_GL_TRY(gl, ReadPixels(src.origin.x, src.origin.y, copySize.width,
-                                                       copySize.height, glFormat, glType, offset));
+                                        FramebufferTextureLayer(
+                                            GL_READ_FRAMEBUFFER, glAttachment, texture->GetHandle(),
+                                            src.mipLevel, static_cast<uint32_t>(src.origin.z + z)));
+                            DAWN_GL_TRY(gl, ReadPixels(static_cast<uint32_t>(src.origin.x),
+                                                       static_cast<uint32_t>(src.origin.y),
+                                                       static_cast<uint32_t>(copySize.width),
+                                                       static_cast<uint32_t>(copySize.height),
+                                                       glFormat, glType, offset));
 
                             offset += bytesPerImage;
                         }
@@ -991,8 +1000,7 @@
 
             case Command::CopyTextureToTexture: {
                 CopyTextureToTextureCmd* copy = mCommands.NextCommand<CopyTextureToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -1003,26 +1011,30 @@
                 // is not equal to imageExtentDst. For example when copySize fits in the virtual
                 // size of the source image but does not fit in the one of the destination
                 // image.
-                Extent3D copySize = ComputeTextureCopyExtent(dst, copy->copySize);
+                TexelExtent3D copySize = ComputeTextureCopyExtent(dst, copy->copySize);
                 Texture* srcTexture = ToBackend(src.texture.Get());
                 Texture* dstTexture = ToBackend(dst.texture.Get());
 
                 DAWN_TRY(srcTexture->SynchronizeTextureBeforeUse());
                 DAWN_TRY(dstTexture->SynchronizeTextureBeforeUse());
 
-                SubresourceRange srcRange = GetSubresourcesAffectedByCopy(src, copy->copySize);
-                SubresourceRange dstRange = GetSubresourcesAffectedByCopy(dst, copy->copySize);
+                SubresourceRange srcRange =
+                    GetSubresourcesAffectedByCopy(src, copy->copySize.ToExtent3D());
+                SubresourceRange dstRange =
+                    GetSubresourcesAffectedByCopy(dst, copy->copySize.ToExtent3D());
 
                 DAWN_TRY(srcTexture->EnsureSubresourceContentInitialized(gl, srcRange));
-                if (IsCompleteSubresourceCopiedTo(dstTexture, copySize, dst.mipLevel, dst.aspect)) {
+                if (IsCompleteSubresourceCopiedTo(dstTexture, copySize.ToExtent3D(), dst.mipLevel,
+                                                  dst.aspect)) {
                     dstTexture->SetIsSubresourceContentInitialized(true, dstRange);
                 } else {
                     DAWN_TRY(dstTexture->EnsureSubresourceContentInitialized(gl, dstRange));
                 }
                 DAWN_TRY(CopyImageSubData(gl, src.aspect, srcTexture->GetHandle(),
-                                          srcTexture->GetGLTarget(), src.mipLevel, src.origin,
-                                          dstTexture->GetHandle(), dstTexture->GetGLTarget(),
-                                          dst.mipLevel, dst.origin, copySize));
+                                          srcTexture->GetGLTarget(), src.mipLevel,
+                                          src.origin.ToOrigin3D(), dstTexture->GetHandle(),
+                                          dstTexture->GetGLTarget(), dst.mipLevel,
+                                          dst.origin.ToOrigin3D(), copySize.ToExtent3D()));
                 break;
             }
 
@@ -1612,7 +1624,7 @@
                          const TextureCopy& destination,
                          const void* data,
                          const TexelCopyBufferLayout& dataLayout,
-                         const Extent3D& copySize) {
+                         const TexelExtent3D& copySize) {
     Texture* texture = ToBackend(destination.texture.Get());
 
     const GLFormat& format = texture->GetGLFormat();
@@ -1620,17 +1632,24 @@
     data = static_cast<const uint8_t*>(data) + dataLayout.offset;
     DAWN_GL_TRY(gl, ActiveTexture(GL_TEXTURE0));
     DAWN_GL_TRY(gl, BindTexture(target, texture->GetHandle()));
-    // TODO(crbug.com/424536624): TypedTexelBlockInfo and rework code below
-    const TexelBlockInfo& blockInfo = GetBlockInfo(destination);
+    const TypedTexelBlockInfo& blockInfo = GetBlockInfo(destination);
+    const BlockExtent3D blockCopySize = blockInfo.ToBlock(copySize);
+    const uint64_t bytesPerImage = dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+    const BlockCount rowsPerImage{dataLayout.rowsPerImage};
+    // Note: bytesPerRow is not necessarily a multiple of block size because WriteTexture is
+    // directly implemented by the GL backend and doesn't have alignment constraints for
+    // bytesPerRow.
+    const uint64_t bytesPerRow = dataLayout.bytesPerRow;
 
-    uint32_t x = destination.origin.x;
-    uint32_t y = destination.origin.y;
-    uint32_t z = destination.origin.z;
+    TexelCount x = destination.origin.x;
+    TexelCount y = destination.origin.y;
+    TexelCount z = destination.origin.z;
+
     if (texture->GetFormat().isCompressed) {
-        size_t rowSize = copySize.width / blockInfo.width * blockInfo.byteSize;
-        Extent3D virtSize = texture->GetMipLevelSingleSubresourceVirtualSize(destination.mipLevel,
-                                                                             destination.aspect);
-        uint32_t width = std::min(copySize.width, virtSize.width - x);
+        size_t rowSize = blockInfo.ToBytes(blockCopySize.width);
+        TexelExtent3D virtSize = texture->GetMipLevelSingleSubresourceVirtualSize(
+            destination.mipLevel, destination.aspect);
+        const TexelCount width = std::min(copySize.width, virtSize.width - x);
 
         // In GLES glPixelStorei() doesn't affect CompressedTexSubImage*D() and
         // GL_UNPACK_COMPRESSED_BLOCK_* isn't defined, so we have to workaround
@@ -1638,41 +1657,54 @@
         // See OpenGL ES 3.2 SPEC Chapter 8.4.1, "Pixel Storage Modes and Pixel
         // Buffer Objects" for more details. For Desktop GL, we use row-by-row
         // copies only for uploads where bytesPerRow is not a multiple of byteSize.
-        if (dataLayout.bytesPerRow % blockInfo.byteSize == 0 && gl.GetVersion().IsDesktop()) {
-            size_t imageSize =
-                rowSize * (copySize.height / blockInfo.height) * copySize.depthOrArrayLayers;
+        if (bytesPerRow % blockInfo.byteSize == 0 && gl.GetVersion().IsDesktop()) {
+            const BlockCount blocksPerRow = blockInfo.BytesToBlocks(bytesPerRow);
 
-            uint32_t height = std::min(copySize.height, virtSize.height - y);
+            size_t imageSize = rowSize * blockInfo.ToBytes(blockCopySize.height *
+                                                           blockCopySize.depthOrArrayLayers);
+
+            const TexelCount height = std::min(copySize.height, virtSize.height - y);
 
             DAWN_GL_TRY(gl,
                         PixelStorei(GL_UNPACK_ROW_LENGTH,
-                                    dataLayout.bytesPerRow / blockInfo.byteSize * blockInfo.width));
+                                    static_cast<uint32_t>(blockInfo.ToTexelWidth(blocksPerRow))));
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_SIZE, blockInfo.byteSize));
-            DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_WIDTH, blockInfo.width));
-            DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_HEIGHT, blockInfo.height));
+            DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_WIDTH,
+                                        static_cast<uint32_t>(blockInfo.width)));
+            DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_HEIGHT,
+                                        static_cast<uint32_t>(blockInfo.height)));
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_DEPTH, 1));
 
             if (target == GL_TEXTURE_2D) {
                 DAWN_GL_TRY(
-                    gl, CompressedTexSubImage2D(target, destination.mipLevel, x, y, width, height,
-                                                format.internalFormat, imageSize, data));
+                    gl, CompressedTexSubImage2D(
+                            target, destination.mipLevel, static_cast<uint32_t>(x),
+                            static_cast<uint32_t>(y), static_cast<uint32_t>(width),
+                            static_cast<uint32_t>(height), format.internalFormat, imageSize, data));
             } else if (target == GL_TEXTURE_CUBE_MAP) {
                 DAWN_ASSERT(texture->GetArrayLayers() == 6);
                 const uint8_t* pointer = static_cast<const uint8_t*>(data);
-                uint32_t baseLayer = destination.origin.z;
-                for (uint32_t l = 0; l < copySize.depthOrArrayLayers; ++l) {
-                    GLenum cubeMapTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + baseLayer + l;
-                    DAWN_GL_TRY(gl, CompressedTexSubImage2D(cubeMapTarget, destination.mipLevel, x,
-                                                            y, width, height, format.internalFormat,
-                                                            imageSize, pointer));
-                    pointer += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+                TexelCount baseLayer = destination.origin.z;
+                for (TexelCount l{0}; l < copySize.depthOrArrayLayers; ++l) {
+                    GLenum cubeMapTarget =
+                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast<uint32_t>(baseLayer + l);
+                    DAWN_GL_TRY(gl, CompressedTexSubImage2D(
+                                        cubeMapTarget, destination.mipLevel,
+                                        static_cast<uint32_t>(x), static_cast<uint32_t>(y),
+                                        static_cast<uint32_t>(width), static_cast<uint32_t>(height),
+                                        format.internalFormat, imageSize, pointer));
+                    pointer += bytesPerImage;
                 }
             } else {
-                DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT,
-                                            dataLayout.rowsPerImage * blockInfo.height));
-                DAWN_GL_TRY(gl, CompressedTexSubImage3D(target, destination.mipLevel, x, y, z,
-                                                        width, height, copySize.depthOrArrayLayers,
-                                                        format.internalFormat, imageSize, data));
+                DAWN_GL_TRY(
+                    gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT,
+                                    static_cast<uint32_t>(blockInfo.ToTexelHeight(rowsPerImage))));
+                DAWN_GL_TRY(gl, CompressedTexSubImage3D(
+                                    target, destination.mipLevel, static_cast<uint32_t>(x),
+                                    static_cast<uint32_t>(y), static_cast<uint32_t>(z),
+                                    static_cast<uint32_t>(width), static_cast<uint32_t>(height),
+                                    static_cast<uint32_t>(copySize.depthOrArrayLayers),
+                                    format.internalFormat, imageSize, data));
                 DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0));
             }
 
@@ -1686,27 +1718,32 @@
                 const uint8_t* d = static_cast<const uint8_t*>(data);
 
                 for (; y < destination.origin.y + copySize.height; y += blockInfo.height) {
-                    uint32_t height = std::min(blockInfo.height, virtSize.height - y);
-                    DAWN_GL_TRY(gl,
-                                CompressedTexSubImage2D(target, destination.mipLevel, x, y, width,
-                                                        height, format.internalFormat, rowSize, d));
-                    d += dataLayout.bytesPerRow;
+                    TexelCount height = std::min(blockInfo.height, virtSize.height - y);
+                    DAWN_GL_TRY(
+                        gl, CompressedTexSubImage2D(
+                                target, destination.mipLevel, static_cast<uint32_t>(x),
+                                static_cast<uint32_t>(y), static_cast<uint32_t>(width),
+                                static_cast<uint32_t>(height), format.internalFormat, rowSize, d));
+                    d += bytesPerRow;
                 }
             } else if (target == GL_TEXTURE_CUBE_MAP) {
                 DAWN_ASSERT(texture->GetArrayLayers() == 6);
                 const uint8_t* pointer = static_cast<const uint8_t*>(data);
-                uint32_t baseLayer = destination.origin.z;
-                for (uint32_t l = 0; l < copySize.depthOrArrayLayers; ++l) {
-                    const uint8_t* d =
-                        pointer + l * dataLayout.rowsPerImage * dataLayout.bytesPerRow;
-                    GLenum cubeMapTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + baseLayer + l;
+                TexelCount baseLayer = destination.origin.z;
+                for (TexelCount l{0}; l < copySize.depthOrArrayLayers; ++l) {
+                    const uint8_t* d = pointer + static_cast<uint32_t>(l) * bytesPerImage;
+                    GLenum cubeMapTarget =
+                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast<uint32_t>(baseLayer + l);
                     for (y = destination.origin.y; y < destination.origin.y + copySize.height;
                          y += blockInfo.height) {
-                        uint32_t height = std::min(blockInfo.height, virtSize.height - y);
+                        TexelCount height = std::min(blockInfo.height, virtSize.height - y);
                         DAWN_GL_TRY(gl, CompressedTexSubImage2D(cubeMapTarget, destination.mipLevel,
-                                                                x, y, width, height,
+                                                                static_cast<uint32_t>(x),
+                                                                static_cast<uint32_t>(y),
+                                                                static_cast<uint32_t>(width),
+                                                                static_cast<uint32_t>(height),
                                                                 format.internalFormat, rowSize, d));
-                        d += dataLayout.bytesPerRow;
+                        d += bytesPerRow;
                     }
                 }
             } else {
@@ -1719,53 +1756,69 @@
 
                     for (y = destination.origin.y; y < destination.origin.y + copySize.height;
                          y += blockInfo.height) {
-                        uint32_t height = std::min(blockInfo.height, virtSize.height - y);
-                        DAWN_GL_TRY(gl, CompressedTexSubImage3D(target, destination.mipLevel, x, y,
-                                                                z, width, height, 1,
-                                                                format.internalFormat, rowSize, d));
-                        d += dataLayout.bytesPerRow;
+                        TexelCount height = std::min(blockInfo.height, virtSize.height - y);
+                        DAWN_GL_TRY(
+                            gl, CompressedTexSubImage3D(
+                                    target, destination.mipLevel, static_cast<uint32_t>(x),
+                                    static_cast<uint32_t>(y), static_cast<uint32_t>(z),
+                                    static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1,
+                                    format.internalFormat, rowSize, d));
+                        d += bytesPerRow;
                     }
 
-                    slice += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+                    slice += bytesPerImage;
                 }
             }
         }
     } else {
-        uint32_t width = copySize.width;
-        uint32_t height = copySize.height;
+        TexelCount width = copySize.width;
+        TexelCount height = copySize.height;
         GLenum adjustedFormat = format.format;
         if (format.format == GL_STENCIL) {
             DAWN_ASSERT(gl.GetVersion().IsDesktop() ||
                         gl.IsGLExtensionSupported("GL_OES_texture_stencil8"));
             adjustedFormat = GL_STENCIL_INDEX;
         }
-        if (dataLayout.bytesPerRow % blockInfo.byteSize == 0) {
+        if (bytesPerRow % blockInfo.byteSize == 0) {
+            const BlockCount blocksPerRow = blockInfo.BytesToBlocks(bytesPerRow);
+
             // Valid values for GL_UNPACK_ALIGNMENT are 1, 2, 4, 8
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_ALIGNMENT, std::min(8u, blockInfo.byteSize)));
             DAWN_GL_TRY(gl,
                         PixelStorei(GL_UNPACK_ROW_LENGTH,
-                                    dataLayout.bytesPerRow / blockInfo.byteSize * blockInfo.width));
+                                    static_cast<uint32_t>(blockInfo.ToTexelWidth(blocksPerRow))));
             if (target == GL_TEXTURE_2D) {
-                DAWN_GL_TRY(gl, TexSubImage2D(target, destination.mipLevel, x, y, width, height,
-                                              adjustedFormat, format.type, data));
+                DAWN_GL_TRY(
+                    gl, TexSubImage2D(target, destination.mipLevel, static_cast<uint32_t>(x),
+                                      static_cast<uint32_t>(y), static_cast<uint32_t>(width),
+                                      static_cast<uint32_t>(height), adjustedFormat, format.type,
+                                      data));
             } else if (target == GL_TEXTURE_CUBE_MAP) {
                 DAWN_ASSERT(texture->GetArrayLayers() == 6);
                 const uint8_t* pointer = static_cast<const uint8_t*>(data);
-                uint32_t baseLayer = destination.origin.z;
-                for (uint32_t l = 0; l < copySize.depthOrArrayLayers; ++l) {
-                    GLenum cubeMapTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + baseLayer + l;
-                    DAWN_GL_TRY(gl, TexSubImage2D(cubeMapTarget, destination.mipLevel, x, y, width,
-                                                  height, adjustedFormat, format.type, pointer));
-                    pointer += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+                TexelCount baseLayer = destination.origin.z;
+                for (TexelCount l{0}; l < copySize.depthOrArrayLayers; ++l) {
+                    GLenum cubeMapTarget =
+                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast<uint32_t>(baseLayer + l);
+                    DAWN_GL_TRY(gl, TexSubImage2D(
+                                        cubeMapTarget, destination.mipLevel,
+                                        static_cast<uint32_t>(x), static_cast<uint32_t>(y),
+                                        static_cast<uint32_t>(width), static_cast<uint32_t>(height),
+                                        adjustedFormat, format.type, pointer));
+                    pointer += bytesPerImage;
                 }
             } else {
                 DAWN_ASSERT(target == GL_TEXTURE_3D || target == GL_TEXTURE_2D_ARRAY ||
                             target == GL_TEXTURE_CUBE_MAP_ARRAY);
-                DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT,
-                                            dataLayout.rowsPerImage * blockInfo.height));
-                DAWN_GL_TRY(gl, TexSubImage3D(target, destination.mipLevel, x, y, z, width, height,
-                                              copySize.depthOrArrayLayers, adjustedFormat,
-                                              format.type, data));
+                DAWN_GL_TRY(
+                    gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT,
+                                    static_cast<uint32_t>(blockInfo.ToTexelHeight(rowsPerImage))));
+                DAWN_GL_TRY(
+                    gl, TexSubImage3D(target, destination.mipLevel, static_cast<uint32_t>(x),
+                                      static_cast<uint32_t>(y), static_cast<uint32_t>(z),
+                                      static_cast<uint32_t>(width), static_cast<uint32_t>(height),
+                                      static_cast<uint32_t>(copySize.depthOrArrayLayers),
+                                      adjustedFormat, format.type, data));
                 DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0));
             }
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_ROW_LENGTH, 0));
@@ -1774,23 +1827,29 @@
             if (target == GL_TEXTURE_2D) {
                 const uint8_t* d = static_cast<const uint8_t*>(data);
                 for (; y < destination.origin.y + height; ++y) {
-                    DAWN_GL_TRY(gl, TexSubImage2D(target, destination.mipLevel, x, y, width, 1,
-                                                  adjustedFormat, format.type, d));
-                    d += dataLayout.bytesPerRow;
+                    DAWN_GL_TRY(
+                        gl, TexSubImage2D(target, destination.mipLevel, static_cast<uint32_t>(x),
+                                          static_cast<uint32_t>(y), static_cast<uint32_t>(width), 1,
+                                          adjustedFormat, format.type, d));
+                    d += bytesPerRow;
                 }
             } else if (target == GL_TEXTURE_CUBE_MAP) {
                 DAWN_ASSERT(texture->GetArrayLayers() == 6);
                 const uint8_t* pointer = static_cast<const uint8_t*>(data);
-                uint32_t baseLayer = destination.origin.z;
-                for (uint32_t l = 0; l < copySize.depthOrArrayLayers; ++l) {
+                TexelCount baseLayer = destination.origin.z;
+                for (TexelCount l{0}; l < copySize.depthOrArrayLayers; ++l) {
                     const uint8_t* d = pointer;
-                    GLenum cubeMapTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + baseLayer + l;
+                    GLenum cubeMapTarget =
+                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast<uint32_t>(baseLayer + l);
                     for (y = destination.origin.y; y < destination.origin.y + height; ++y) {
-                        DAWN_GL_TRY(gl, TexSubImage2D(cubeMapTarget, destination.mipLevel, x, y,
-                                                      width, 1, adjustedFormat, format.type, d));
-                        d += dataLayout.bytesPerRow;
+                        DAWN_GL_TRY(
+                            gl, TexSubImage2D(cubeMapTarget, destination.mipLevel,
+                                              static_cast<uint32_t>(x), static_cast<uint32_t>(y),
+                                              static_cast<uint32_t>(width), 1, adjustedFormat,
+                                              format.type, d));
+                        d += bytesPerRow;
                     }
-                    pointer += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+                    pointer += bytesPerImage;
                 }
             } else {
                 DAWN_ASSERT(target == GL_TEXTURE_3D || target == GL_TEXTURE_2D_ARRAY ||
@@ -1799,11 +1858,14 @@
                 for (; z < destination.origin.z + copySize.depthOrArrayLayers; ++z) {
                     const uint8_t* d = slice;
                     for (y = destination.origin.y; y < destination.origin.y + height; ++y) {
-                        DAWN_GL_TRY(gl, TexSubImage3D(target, destination.mipLevel, x, y, z, width,
-                                                      1, 1, adjustedFormat, format.type, d));
-                        d += dataLayout.bytesPerRow;
+                        DAWN_GL_TRY(gl, TexSubImage3D(
+                                            target, destination.mipLevel, static_cast<uint32_t>(x),
+                                            static_cast<uint32_t>(y), static_cast<uint32_t>(z),
+                                            static_cast<uint32_t>(width), 1, 1, adjustedFormat,
+                                            format.type, d));
+                        d += bytesPerRow;
                     }
-                    slice += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+                    slice += bytesPerImage;
                 }
             }
         }
diff --git a/src/dawn/native/opengl/CommandBufferGL.h b/src/dawn/native/opengl/CommandBufferGL.h
index 3185246..2723556 100644
--- a/src/dawn/native/opengl/CommandBufferGL.h
+++ b/src/dawn/native/opengl/CommandBufferGL.h
@@ -56,7 +56,7 @@
                          const TextureCopy& destination,
                          const void* data,
                          const TexelCopyBufferLayout& dataLayout,
-                         const Extent3D& copySize);
+                         const TexelExtent3D& copySize);
 }  // namespace dawn::native::opengl
 
 #endif  // SRC_DAWN_NATIVE_OPENGL_COMMANDBUFFERGL_H_
diff --git a/src/dawn/native/opengl/TextureGL.cpp b/src/dawn/native/opengl/TextureGL.cpp
index 2abc056..33c697a 100644
--- a/src/dawn/native/opengl/TextureGL.cpp
+++ b/src/dawn/native/opengl/TextureGL.cpp
@@ -581,7 +581,7 @@
                     continue;
                 }
 
-                textureCopy.origin.z = layer;
+                textureCopy.origin.z = TexelCount{layer};
                 DAWN_TRY(DoTexSubImage(gl, textureCopy, 0, dataLayout, mipSize));
             }
         }
diff --git a/src/dawn/native/vulkan/CommandBufferVk.cpp b/src/dawn/native/vulkan/CommandBufferVk.cpp
index 265bfbd..fc2ca6d 100644
--- a/src/dawn/native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn/native/vulkan/CommandBufferVk.cpp
@@ -77,9 +77,9 @@
 
 bool HasSameTextureCopyExtent(const TextureCopy& srcCopy,
                               const TextureCopy& dstCopy,
-                              const Extent3D& copySize) {
-    Extent3D imageExtentSrc = ComputeTextureCopyExtent(srcCopy, copySize);
-    Extent3D imageExtentDst = ComputeTextureCopyExtent(dstCopy, copySize);
+                              const TexelExtent3D& copySize) {
+    TexelExtent3D imageExtentSrc = ComputeTextureCopyExtent(srcCopy, copySize);
+    TexelExtent3D imageExtentDst = ComputeTextureCopyExtent(dstCopy, copySize);
     return imageExtentSrc.width == imageExtentDst.width &&
            imageExtentSrc.height == imageExtentDst.height &&
            imageExtentSrc.depthOrArrayLayers == imageExtentDst.depthOrArrayLayers;
@@ -87,7 +87,7 @@
 
 VkImageCopy ComputeImageCopyRegion(const TextureCopy& srcCopy,
                                    const TextureCopy& dstCopy,
-                                   const Extent3D& copySize,
+                                   const TexelExtent3D& copySize,
                                    Aspect aspect) {
     const Texture* srcTexture = ToBackend(srcCopy.texture.Get());
     const Texture* dstTexture = ToBackend(dstCopy.texture.Get());
@@ -100,8 +100,8 @@
 
     bool has3DTextureInCopy = false;
 
-    region.srcOffset.x = srcCopy.origin.x;
-    region.srcOffset.y = srcCopy.origin.y;
+    region.srcOffset.x = static_cast<uint32_t>(srcCopy.origin.x);
+    region.srcOffset.y = static_cast<uint32_t>(srcCopy.origin.y);
     switch (srcTexture->GetDimension()) {
         case wgpu::TextureDimension::Undefined:
             DAWN_UNREACHABLE();
@@ -111,20 +111,20 @@
             region.srcOffset.z = 0;
             break;
         case wgpu::TextureDimension::e2D:
-            region.srcSubresource.baseArrayLayer = srcCopy.origin.z;
-            region.srcSubresource.layerCount = copySize.depthOrArrayLayers;
+            region.srcSubresource.baseArrayLayer = static_cast<uint32_t>(srcCopy.origin.z);
+            region.srcSubresource.layerCount = static_cast<uint32_t>(copySize.depthOrArrayLayers);
             region.srcOffset.z = 0;
             break;
         case wgpu::TextureDimension::e3D:
             has3DTextureInCopy = true;
             region.srcSubresource.baseArrayLayer = 0;
             region.srcSubresource.layerCount = 1;
-            region.srcOffset.z = srcCopy.origin.z;
+            region.srcOffset.z = static_cast<uint32_t>(srcCopy.origin.z);
             break;
     }
 
-    region.dstOffset.x = dstCopy.origin.x;
-    region.dstOffset.y = dstCopy.origin.y;
+    region.dstOffset.x = static_cast<uint32_t>(dstCopy.origin.x);
+    region.dstOffset.y = static_cast<uint32_t>(dstCopy.origin.y);
     switch (dstTexture->GetDimension()) {
         case wgpu::TextureDimension::Undefined:
             DAWN_UNREACHABLE();
@@ -134,23 +134,24 @@
             region.dstOffset.z = 0;
             break;
         case wgpu::TextureDimension::e2D:
-            region.dstSubresource.baseArrayLayer = dstCopy.origin.z;
-            region.dstSubresource.layerCount = copySize.depthOrArrayLayers;
+            region.dstSubresource.baseArrayLayer = static_cast<uint32_t>(dstCopy.origin.z);
+            region.dstSubresource.layerCount = static_cast<uint32_t>(copySize.depthOrArrayLayers);
             region.dstOffset.z = 0;
             break;
         case wgpu::TextureDimension::e3D:
             has3DTextureInCopy = true;
             region.dstSubresource.baseArrayLayer = 0;
             region.dstSubresource.layerCount = 1;
-            region.dstOffset.z = dstCopy.origin.z;
+            region.dstOffset.z = static_cast<uint32_t>(dstCopy.origin.z);
             break;
     }
 
     DAWN_ASSERT(HasSameTextureCopyExtent(srcCopy, dstCopy, copySize));
-    Extent3D imageExtent = ComputeTextureCopyExtent(dstCopy, copySize);
-    region.extent.width = imageExtent.width;
-    region.extent.height = imageExtent.height;
-    region.extent.depth = has3DTextureInCopy ? copySize.depthOrArrayLayers : 1;
+    TexelExtent3D imageExtent = ComputeTextureCopyExtent(dstCopy, copySize);
+    region.extent.width = static_cast<uint32_t>(imageExtent.width);
+    region.extent.height = static_cast<uint32_t>(imageExtent.height);
+    region.extent.depth =
+        has3DTextureInCopy ? static_cast<uint32_t>(copySize.depthOrArrayLayers) : 1;
 
     return region;
 }
@@ -646,11 +647,11 @@
     CommandRecordingContext* recordingContext,
     const TextureCopy& srcCopy,
     const TextureCopy& dstCopy,
-    const Extent3D& copySize_in) {
+    const TexelExtent3D& texelCopySize) {
     DAWN_ASSERT(srcCopy.texture->GetFormat().CopyCompatibleWith(dstCopy.texture->GetFormat()));
     DAWN_ASSERT(srcCopy.aspect == dstCopy.aspect);
     const TypedTexelBlockInfo& blockInfo = GetBlockInfo(srcCopy);
-    const BlockExtent3D copySize = blockInfo.ToBlock(copySize_in);
+    const BlockExtent3D copySize = blockInfo.ToBlock(texelCopySize);
     BlockCount widthInBlocks = copySize.width;
     BlockCount heightInBlocks = copySize.height;
 
@@ -756,8 +757,7 @@
 
             case Command::CopyBufferToTexture: {
                 CopyBufferToTextureCmd* copy = mCommands.NextCommand<CopyBufferToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -801,8 +801,7 @@
 
             case Command::CopyTextureToBuffer: {
                 CopyTextureToBufferCmd* copy = mCommands.NextCommand<CopyTextureToBufferCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
@@ -837,8 +836,7 @@
 
             case Command::CopyTextureToTexture: {
                 CopyTextureToTextureCmd* copy = mCommands.NextCommand<CopyTextureToTextureCmd>();
-                if (copy->copySize.width == 0 || copy->copySize.height == 0 ||
-                    copy->copySize.depthOrArrayLayers == 0) {
+                if (copy->copySize.IsEmpty()) {
                     // Skip no-op copies.
                     continue;
                 }
diff --git a/src/dawn/native/vulkan/CommandBufferVk.h b/src/dawn/native/vulkan/CommandBufferVk.h
index 8ca79ad..60945b4 100644
--- a/src/dawn/native/vulkan/CommandBufferVk.h
+++ b/src/dawn/native/vulkan/CommandBufferVk.h
@@ -68,7 +68,7 @@
     MaybeError RecordCopyImageWithTemporaryBuffer(CommandRecordingContext* recordingContext,
                                                   const TextureCopy& srcCopy,
                                                   const TextureCopy& dstCopy,
-                                                  const Extent3D& copySize);
+                                                  const TexelExtent3D& texelCopySize);
 };
 
 }  // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/TextureVk.cpp b/src/dawn/native/vulkan/TextureVk.cpp
index 7296574..d64498e 100644
--- a/src/dawn/native/vulkan/TextureVk.cpp
+++ b/src/dawn/native/vulkan/TextureVk.cpp
@@ -1290,7 +1290,7 @@
                         TextureCopy textureCopy;
                         textureCopy.aspect = range.aspects;
                         textureCopy.mipLevel = level;
-                        textureCopy.origin = {0, 0, layer};
+                        textureCopy.origin = {TexelCount{0}, TexelCount{0}, TexelCount{layer}};
                         textureCopy.texture = this;
 
                         regions.push_back(
diff --git a/src/dawn/native/vulkan/UtilsVulkan.cpp b/src/dawn/native/vulkan/UtilsVulkan.cpp
index 1fe0f93..d3fb146 100644
--- a/src/dawn/native/vulkan/UtilsVulkan.cpp
+++ b/src/dawn/native/vulkan/UtilsVulkan.cpp
@@ -140,10 +140,11 @@
 // the image refers to the virtual size, while Dawn validates texture copy extent with the
 // physical size, so we need to re-calculate the texture copy extent to ensure it should fit
 // in the virtual size of the subresource.
-Extent3D ComputeTextureCopyExtent(const TextureCopy& textureCopy, const Extent3D& copySize) {
-    Extent3D validTextureCopyExtent = copySize;
+TexelExtent3D ComputeTextureCopyExtent(const TextureCopy& textureCopy,
+                                       const TexelExtent3D& copySize) {
+    TexelExtent3D validTextureCopyExtent = copySize;
     const TextureBase* texture = textureCopy.texture.Get();
-    Extent3D virtualSizeAtLevel =
+    TexelExtent3D virtualSizeAtLevel =
         texture->GetMipLevelSingleSubresourceVirtualSize(textureCopy.mipLevel, textureCopy.aspect);
     DAWN_ASSERT(textureCopy.origin.x <= virtualSizeAtLevel.width);
     DAWN_ASSERT(textureCopy.origin.y <= virtualSizeAtLevel.height);
@@ -193,9 +194,9 @@
         case wgpu::TextureDimension::Undefined:
             DAWN_UNREACHABLE();
         case wgpu::TextureDimension::e1D:
-            DAWN_ASSERT(textureCopy.origin.z == 0 &&
+            DAWN_ASSERT(textureCopy.origin.z == TexelCount{0} &&
                         copySizeTexels.depthOrArrayLayers == TexelCount{1});
-            region.imageOffset.x = textureCopy.origin.x;
+            region.imageOffset.x = static_cast<uint32_t>(textureCopy.origin.x);
             region.imageOffset.y = 0;
             region.imageOffset.z = 0;
             region.imageSubresource.baseArrayLayer = 0;
@@ -208,32 +209,32 @@
             break;
 
         case wgpu::TextureDimension::e2D: {
-            region.imageOffset.x = textureCopy.origin.x;
-            region.imageOffset.y = textureCopy.origin.y;
+            region.imageOffset.x = static_cast<uint32_t>(textureCopy.origin.x);
+            region.imageOffset.y = static_cast<uint32_t>(textureCopy.origin.y);
             region.imageOffset.z = 0;
-            region.imageSubresource.baseArrayLayer = textureCopy.origin.z;
+            region.imageSubresource.baseArrayLayer = static_cast<uint32_t>(textureCopy.origin.z);
             region.imageSubresource.layerCount =
                 static_cast<uint32_t>(copySizeTexels.depthOrArrayLayers);
 
-            Extent3D imageExtent =
+            TexelExtent3D imageExtent =
                 ComputeTextureCopyExtent(textureCopy, copySizeTexels.ToExtent3D());
-            region.imageExtent.width = imageExtent.width;
-            region.imageExtent.height = imageExtent.height;
+            region.imageExtent.width = static_cast<uint32_t>(imageExtent.width);
+            region.imageExtent.height = static_cast<uint32_t>(imageExtent.height);
             region.imageExtent.depth = 1;
             break;
         }
 
         case wgpu::TextureDimension::e3D: {
-            region.imageOffset.x = textureCopy.origin.x;
-            region.imageOffset.y = textureCopy.origin.y;
-            region.imageOffset.z = textureCopy.origin.z;
+            region.imageOffset.x = static_cast<uint32_t>(textureCopy.origin.x);
+            region.imageOffset.y = static_cast<uint32_t>(textureCopy.origin.y);
+            region.imageOffset.z = static_cast<uint32_t>(textureCopy.origin.z);
             region.imageSubresource.baseArrayLayer = 0;
             region.imageSubresource.layerCount = 1;
 
-            Extent3D imageExtent =
+            TexelExtent3D imageExtent =
                 ComputeTextureCopyExtent(textureCopy, copySizeTexels.ToExtent3D());
-            region.imageExtent.width = imageExtent.width;
-            region.imageExtent.height = imageExtent.height;
+            region.imageExtent.width = static_cast<uint32_t>(imageExtent.width);
+            region.imageExtent.height = static_cast<uint32_t>(imageExtent.height);
             region.imageExtent.depth = static_cast<uint32_t>(copySizeTexels.depthOrArrayLayers);
             break;
         }
diff --git a/src/dawn/native/vulkan/UtilsVulkan.h b/src/dawn/native/vulkan/UtilsVulkan.h
index 77e36a9..451d224 100644
--- a/src/dawn/native/vulkan/UtilsVulkan.h
+++ b/src/dawn/native/vulkan/UtilsVulkan.h
@@ -119,7 +119,8 @@
 
 VkImageAspectFlags VulkanAspectMask(const Aspect& aspects);
 
-Extent3D ComputeTextureCopyExtent(const TextureCopy& textureCopy, const Extent3D& copySize);
+TexelExtent3D ComputeTextureCopyExtent(const TextureCopy& textureCopy,
+                                       const TexelExtent3D& copySize);
 
 // TODO(crbug.com/424536624): Remove this overload and use BufferCopy instead of
 // TexelCopyBufferLayout.
diff --git a/src/dawn/native/webgpu/CaptureContext.cpp b/src/dawn/native/webgpu/CaptureContext.cpp
index 63a65a8..1b68389 100644
--- a/src/dawn/native/webgpu/CaptureContext.cpp
+++ b/src/dawn/native/webgpu/CaptureContext.cpp
@@ -133,7 +133,7 @@
                                                     const void* data,
                                                     size_t dataSize,
                                                     const TexelCopyBufferLayout& dataLayout,
-                                                    const Extent3D& writeSizePixel) {
+                                                    const TexelExtent3D& writeSizePixel) {
     DAWN_TRY(AddResource(ToBackend(destination.texture)));
     schema::RootCommandWriteTextureCmd cmd{{
         .data = {{
@@ -165,19 +165,19 @@
     }
 }
 
-schema::Origin3D ToSchema(const Origin3D& origin) {
+schema::Origin3D ToSchema(const TexelOrigin3D& origin) {
     return {{
-        .x = origin.x,
-        .y = origin.y,
-        .z = origin.z,
+        .x = static_cast<uint32_t>(origin.x),
+        .y = static_cast<uint32_t>(origin.y),
+        .z = static_cast<uint32_t>(origin.z),
     }};
 }
 
-schema::Extent3D ToSchema(const Extent3D& extent) {
+schema::Extent3D ToSchema(const TexelExtent3D& extent) {
     return {{
-        .width = extent.width,
-        .height = extent.height,
-        .depthOrArrayLayers = extent.depthOrArrayLayers,
+        .width = static_cast<uint32_t>(extent.width),
+        .height = static_cast<uint32_t>(extent.height),
+        .depthOrArrayLayers = static_cast<uint32_t>(extent.depthOrArrayLayers),
     }};
 }
 
diff --git a/src/dawn/native/webgpu/CaptureContext.h b/src/dawn/native/webgpu/CaptureContext.h
index 310ca23..f03c11e 100644
--- a/src/dawn/native/webgpu/CaptureContext.h
+++ b/src/dawn/native/webgpu/CaptureContext.h
@@ -44,10 +44,10 @@
 class BufferBase;
 class DeviceBase;
 struct Color;
-struct Origin3D;
-struct Extent3D;
 struct BufferCopy;
 struct ProgrammableStage;
+struct TexelOrigin3D;
+struct TexelExtent3D;
 struct TextureCopy;
 struct TimestampWrites;
 struct TypedTexelBlockInfo;
@@ -170,7 +170,7 @@
                                         const void* data,
                                         size_t dataSize,
                                         const TexelCopyBufferLayout& dataLayout,
-                                        const Extent3D& writeSizePixel);
+                                        const TexelExtent3D& writeSizePixel);
 
     WGPUBuffer GetCopyBuffer();
 
@@ -196,8 +196,8 @@
 };
 
 wgpu::TextureAspect ToDawn(const Aspect aspect);
-schema::Origin3D ToSchema(const Origin3D& origin);
-schema::Extent3D ToSchema(const Extent3D& extent);
+schema::Origin3D ToSchema(const TexelOrigin3D& origin);
+schema::Extent3D ToSchema(const TexelExtent3D& extent);
 schema::Color ToSchema(const Color& color);
 schema::ProgrammableStage ToSchema(CaptureContext& captureContext, const ProgrammableStage& stage);
 schema::TexelCopyBufferLayout ToSchema(const BufferCopy& bufferCopy,
diff --git a/src/dawn/native/webgpu/ToWGPU.cpp b/src/dawn/native/webgpu/ToWGPU.cpp
index c227511..dcf4b5d 100644
--- a/src/dawn/native/webgpu/ToWGPU.cpp
+++ b/src/dawn/native/webgpu/ToWGPU.cpp
@@ -39,19 +39,19 @@
 
 namespace dawn::native::webgpu {
 
-WGPUExtent3D ToWGPU(const Extent3D& extent) {
+WGPUExtent3D ToWGPU(const TexelExtent3D& extent) {
     return {
-        .width = extent.width,
-        .height = extent.height,
-        .depthOrArrayLayers = extent.depthOrArrayLayers,
+        .width = static_cast<uint32_t>(extent.width),
+        .height = static_cast<uint32_t>(extent.height),
+        .depthOrArrayLayers = static_cast<uint32_t>(extent.depthOrArrayLayers),
     };
 }
 
-WGPUOrigin3D ToWGPU(const Origin3D& origin) {
+WGPUOrigin3D ToWGPU(const TexelOrigin3D& origin) {
     return {
-        .x = origin.x,
-        .y = origin.y,
-        .z = origin.z,
+        .x = static_cast<uint32_t>(origin.x),
+        .y = static_cast<uint32_t>(origin.y),
+        .z = static_cast<uint32_t>(origin.z),
     };
 }
 
diff --git a/src/dawn/native/webgpu/ToWGPU.h b/src/dawn/native/webgpu/ToWGPU.h
index 1ccf6b0..326043f 100644
--- a/src/dawn/native/webgpu/ToWGPU.h
+++ b/src/dawn/native/webgpu/ToWGPU.h
@@ -42,6 +42,8 @@
 struct BufferCopy;
 struct RenderPassColorAttachmentInfo;
 struct RenderPassDepthStencilAttachmentInfo;
+struct TexelOrigin3D;
+struct TexelExtent3D;
 struct TextureCopy;
 struct TimestampWrites;
 struct TypedTexelBlockInfo;
@@ -53,11 +55,11 @@
 WGPUBlendState ToWGPU(const BlendState* desc);
 WGPUColor ToWGPU(const dawn::native::Color& color);
 WGPUDepthStencilState ToWGPU(const DepthStencilState* desc);
-WGPUExtent3D ToWGPU(const Extent3D& extent);
+WGPUExtent3D ToWGPU(const TexelExtent3D& extent);
 WGPUIndexFormat ToWGPU(const wgpu::IndexFormat format);
 WGPULoadOp ToWGPU(const wgpu::LoadOp op);
 WGPUMultisampleState ToWGPU(const MultisampleState* desc);
-WGPUOrigin3D ToWGPU(const Origin3D& origin);
+WGPUOrigin3D ToWGPU(const TexelOrigin3D& origin);
 WGPUPassTimestampWrites ToWGPU(const TimestampWrites& writes);
 WGPUPrimitiveState ToWGPU(const PrimitiveState* desc);
 WGPURenderPassColorAttachment ToWGPU(const RenderPassColorAttachmentInfo& info);