Add WGPU_STRIDE_UNDEFINED and update bytesPerRow/rowsPerImage validation

This makes a nearly one-to-one mapping between the JS and C APIs, which
benefits projects like Blink and Emscripten.

- JavaScript's `undefined` is equivalent to C `WGPU_STRIDE_UNDEFINED`.
- JavaScript's `0` is equivalent to C `0`.
- To implement the API correctly, Blink must special-case an actual
  value coming in from JS that is equal to WGPU_STRIDE_UNDEFINED
  (0xFFFF'FFFF), and inject an error.

Keeps but deprecates a reasonable approximation of the old behavior.

Bug: dawn:520
Change-Id: Ie9c992ffab82830090d0dfc3120731e89cd9691c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/31140
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 7d4cb18..e26f9e9 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -580,28 +580,18 @@
             }
             const TexelBlockInfo& blockInfo =
                 destination->texture->GetFormat().GetAspectInfo(destination->aspect).block;
+            TextureDataLayout srcLayout = FixUpDeprecatedTextureDataLayoutOptions(
+                GetDevice(), source->layout, blockInfo, *copySize);
             if (GetDevice()->IsValidationEnabled()) {
-                DAWN_TRY(ValidateLinearTextureCopyOffset(source->layout, blockInfo));
-                DAWN_TRY(ValidateLinearTextureData(source->layout, source->buffer->GetSize(),
-                                                   blockInfo, *copySize));
+                DAWN_TRY(ValidateLinearTextureCopyOffset(srcLayout, blockInfo));
+                DAWN_TRY(ValidateLinearTextureData(srcLayout, source->buffer->GetSize(), blockInfo,
+                                                   *copySize));
 
                 mTopLevelBuffers.insert(source->buffer);
                 mTopLevelTextures.insert(destination->texture);
             }
 
-            // Compute default value for rowsPerImage
-            uint32_t defaultedRowsPerImage = source->layout.rowsPerImage;
-            if (defaultedRowsPerImage == 0) {
-                ASSERT(copySize->height % blockInfo.height == 0);
-                defaultedRowsPerImage = copySize->height / blockInfo.height;
-            }
-
-            // In the case of one row copy bytesPerRow might not contain enough bytes
-            uint32_t bytesPerRow = source->layout.bytesPerRow;
-            if (copySize->height <= 1 && copySize->depth <= 1) {
-                bytesPerRow =
-                    Align(copySize->width * blockInfo.byteSize, kTextureBytesPerRowAlignment);
-            }
+            ApplyDefaultTextureDataLayoutOptions(&srcLayout, blockInfo, *copySize);
 
             // Skip noop copies.
             if (copySize->width != 0 && copySize->height != 0 && copySize->depth != 0) {
@@ -609,9 +599,9 @@
                 CopyBufferToTextureCmd* copy =
                     allocator->Allocate<CopyBufferToTextureCmd>(Command::CopyBufferToTexture);
                 copy->source.buffer = source->buffer;
-                copy->source.offset = source->layout.offset;
-                copy->source.bytesPerRow = bytesPerRow;
-                copy->source.rowsPerImage = defaultedRowsPerImage;
+                copy->source.offset = srcLayout.offset;
+                copy->source.bytesPerRow = srcLayout.bytesPerRow;
+                copy->source.rowsPerImage = srcLayout.rowsPerImage;
                 copy->destination.texture = destination->texture;
                 copy->destination.origin = destination->origin;
                 copy->destination.mipLevel = destination->mipLevel;
@@ -645,28 +635,18 @@
             }
             const TexelBlockInfo& blockInfo =
                 source->texture->GetFormat().GetAspectInfo(source->aspect).block;
+            TextureDataLayout dstLayout = FixUpDeprecatedTextureDataLayoutOptions(
+                GetDevice(), destination->layout, blockInfo, *copySize);
             if (GetDevice()->IsValidationEnabled()) {
-                DAWN_TRY(ValidateLinearTextureCopyOffset(destination->layout, blockInfo));
-                DAWN_TRY(ValidateLinearTextureData(
-                    destination->layout, destination->buffer->GetSize(), blockInfo, *copySize));
+                DAWN_TRY(ValidateLinearTextureCopyOffset(dstLayout, blockInfo));
+                DAWN_TRY(ValidateLinearTextureData(dstLayout, destination->buffer->GetSize(),
+                                                   blockInfo, *copySize));
 
                 mTopLevelTextures.insert(source->texture);
                 mTopLevelBuffers.insert(destination->buffer);
             }
 
-            // Compute default value for rowsPerImage
-            uint32_t defaultedRowsPerImage = destination->layout.rowsPerImage;
-            if (defaultedRowsPerImage == 0) {
-                ASSERT(copySize->height % blockInfo.height == 0);
-                defaultedRowsPerImage = copySize->height / blockInfo.height;
-            }
-
-            // In the case of one row copy bytesPerRow might not contain enough bytes
-            uint32_t bytesPerRow = destination->layout.bytesPerRow;
-            if (copySize->height <= 1 && copySize->depth <= 1) {
-                bytesPerRow =
-                    Align(copySize->width * blockInfo.byteSize, kTextureBytesPerRowAlignment);
-            }
+            ApplyDefaultTextureDataLayoutOptions(&dstLayout, blockInfo, *copySize);
 
             // Skip noop copies.
             if (copySize->width != 0 && copySize->height != 0 && copySize->depth != 0) {
@@ -678,9 +658,9 @@
                 copy->source.mipLevel = source->mipLevel;
                 copy->source.aspect = ConvertAspect(source->texture->GetFormat(), source->aspect);
                 copy->destination.buffer = destination->buffer;
-                copy->destination.offset = destination->layout.offset;
-                copy->destination.bytesPerRow = bytesPerRow;
-                copy->destination.rowsPerImage = defaultedRowsPerImage;
+                copy->destination.offset = dstLayout.offset;
+                copy->destination.bytesPerRow = dstLayout.bytesPerRow;
+                copy->destination.rowsPerImage = dstLayout.rowsPerImage;
                 copy->copySize = *copySize;
             }
 
diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp
index 8ca86f8..08ab6d7 100644
--- a/src/dawn_native/CommandValidation.cpp
+++ b/src/dawn_native/CommandValidation.cpp
@@ -397,7 +397,6 @@
         uint32_t widthInBlocks = copySize.width / blockInfo.width;
         uint32_t heightInBlocks = copySize.height / blockInfo.height;
         uint64_t bytesInLastRow = Safe32x32(widthInBlocks, blockInfo.byteSize);
-        uint64_t bytesPerImage = Safe32x32(bytesPerRow, rowsPerImage);
 
         if (copySize.depth == 0) {
             return 0;
@@ -406,7 +405,7 @@
         // Check for potential overflows for the rest of the computations. We have the following
         // inequalities:
         //
-        //   lastRowBytes <= bytesPerRow
+        //   bytesInLastRow <= bytesPerRow
         //   heightInBlocks <= rowsPerImage
         //
         // So:
@@ -418,12 +417,16 @@
         //
         // This means that if the computation of depth * bytesPerImage doesn't overflow, none of the
         // computations for requiredBytesInCopy will. (and it's not a very pessimizing check)
+        ASSERT(copySize.depth <= 1 ||
+               (bytesPerRow != wgpu::kStrideUndefined && rowsPerImage != wgpu::kStrideUndefined));
+        uint64_t bytesPerImage = Safe32x32(bytesPerRow, rowsPerImage);
         if (bytesPerImage > std::numeric_limits<uint64_t>::max() / copySize.depth) {
             return DAWN_VALIDATION_ERROR("requiredBytesInCopy is too large.");
         }
 
         uint64_t requiredBytesInCopy = bytesPerImage * (copySize.depth - 1);
         if (heightInBlocks > 0) {
+            ASSERT(heightInBlocks <= 1 || bytesPerRow != wgpu::kStrideUndefined);
             uint64_t bytesInLastImage = Safe32x32(bytesPerRow, heightInBlocks - 1) + bytesInLastRow;
             requiredBytesInCopy += bytesInLastImage;
         }
@@ -442,40 +445,98 @@
         return {};
     }
 
-    MaybeError ValidateLinearTextureData(TextureDataLayout layout,
-                                         uint64_t byteSize,
-                                         const TexelBlockInfo& blockInfo,
-                                         const Extent3D& copyExtent) {
-        ASSERT(copyExtent.width % blockInfo.width == 0);
-        uint32_t widthInBlocks = copyExtent.width / blockInfo.width;
-        ASSERT(copyExtent.height % blockInfo.height == 0);
-        uint32_t heightInBlocks = copyExtent.height / blockInfo.height;
+    TextureDataLayout FixUpDeprecatedTextureDataLayoutOptions(
+        DeviceBase* device,
+        const TextureDataLayout& originalLayout,
+        const TexelBlockInfo& blockInfo,
+        const Extent3D& copyExtent) {
+        // TODO(crbug.com/dawn/520): Remove deprecated functionality.
+        TextureDataLayout layout = originalLayout;
 
-        // Default value for rowsPerImage
-        if (layout.rowsPerImage == 0) {
-            layout.rowsPerImage = heightInBlocks;
-        }
-
-        // Validation for other members in layout:
-        ASSERT(Safe32x32(widthInBlocks, blockInfo.byteSize) <=
-               std::numeric_limits<uint32_t>::max());
-        uint32_t lastRowBytes = widthInBlocks * blockInfo.byteSize;
-        if (lastRowBytes > layout.bytesPerRow) {
-            if (copyExtent.height > 1 || copyExtent.depth > 1) {
-                return DAWN_VALIDATION_ERROR("The byte size of a row must be <= bytesPerRow.");
-            } else {
-                // bytesPerRow is unused. Populate it with a valid value for later validation.
-                layout.bytesPerRow = lastRowBytes;
+        if (copyExtent.height != 0 && layout.rowsPerImage == 0) {
+            if (copyExtent.depth > 1) {
+                device->EmitDeprecationWarning(
+                    "rowsPerImage soon must be non-zero if copy depth > 1 (it will no longer "
+                    "default to the copy height).");
+                ASSERT(copyExtent.height % blockInfo.height == 0);
+                uint32_t heightInBlocks = copyExtent.height / blockInfo.height;
+                layout.rowsPerImage = heightInBlocks;
+            } else if (copyExtent.depth == 1) {
+                device->EmitDeprecationWarning(
+                    "rowsPerImage soon must be non-zero or unspecified if copy depth == 1 (it will "
+                    "no longer default to the copy height).");
+                layout.rowsPerImage = wgpu::kStrideUndefined;
             }
         }
 
-        // TODO(tommek@google.com): to match the spec there should be another condition here
-        // on rowsPerImage >= copyExtent.height if copyExtent.depth > 1.
+        // Only bother to fix-up for height == 1 && depth == 1.
+        // The other cases that used to be allowed were zero-size copies.
+        ASSERT(copyExtent.width % blockInfo.width == 0);
+        uint32_t widthInBlocks = copyExtent.width / blockInfo.width;
+        uint32_t bytesInLastRow = widthInBlocks * blockInfo.byteSize;
+        if (copyExtent.height == 1 && copyExtent.depth == 1 &&
+            bytesInLastRow > layout.bytesPerRow) {
+            device->EmitDeprecationWarning(
+                "Soon, even if copy height == 1, bytesPerRow must be >= the byte size of each row "
+                "or left unspecified.");
+            layout.bytesPerRow = wgpu::kStrideUndefined;
+        }
+        return layout;
+    }
 
-        // Validation for the copy being in-bounds:
-        if (layout.rowsPerImage != 0 && layout.rowsPerImage < heightInBlocks) {
+    // Replace wgpu::kStrideUndefined with real values, so backends don't have to think about it.
+    void ApplyDefaultTextureDataLayoutOptions(TextureDataLayout* layout,
+                                              const TexelBlockInfo& blockInfo,
+                                              const Extent3D& copyExtent) {
+        ASSERT(layout != nullptr);
+        ASSERT(copyExtent.height % blockInfo.height == 0);
+        uint32_t heightInBlocks = copyExtent.height / blockInfo.height;
+
+        if (layout->bytesPerRow == wgpu::kStrideUndefined) {
+            ASSERT(copyExtent.width % blockInfo.width == 0);
+            uint32_t widthInBlocks = copyExtent.width / blockInfo.width;
+            uint32_t bytesInLastRow = widthInBlocks * blockInfo.byteSize;
+
+            ASSERT(heightInBlocks <= 1 && copyExtent.depth <= 1);
+            layout->bytesPerRow = Align(bytesInLastRow, kTextureBytesPerRowAlignment);
+        }
+        if (layout->rowsPerImage == wgpu::kStrideUndefined) {
+            ASSERT(copyExtent.depth <= 1);
+            layout->rowsPerImage = heightInBlocks;
+        }
+    }
+
+    MaybeError ValidateLinearTextureData(const TextureDataLayout& layout,
+                                         uint64_t byteSize,
+                                         const TexelBlockInfo& blockInfo,
+                                         const Extent3D& copyExtent) {
+        ASSERT(copyExtent.height % blockInfo.height == 0);
+        uint32_t heightInBlocks = copyExtent.height / blockInfo.height;
+
+        if (copyExtent.depth > 1 && (layout.bytesPerRow == wgpu::kStrideUndefined ||
+                                     layout.rowsPerImage == wgpu::kStrideUndefined)) {
             return DAWN_VALIDATION_ERROR(
-                "rowsPerImage must not be less than the copy height in blocks.");
+                "If copy depth > 1, bytesPerRow and rowsPerImage must be specified.");
+        }
+        if (heightInBlocks > 1 && layout.bytesPerRow == wgpu::kStrideUndefined) {
+            return DAWN_VALIDATION_ERROR("If heightInBlocks > 1, bytesPerRow must be specified.");
+        }
+
+        // Validation for other members in layout:
+        ASSERT(copyExtent.width % blockInfo.width == 0);
+        uint32_t widthInBlocks = copyExtent.width / blockInfo.width;
+        ASSERT(Safe32x32(widthInBlocks, blockInfo.byteSize) <=
+               std::numeric_limits<uint32_t>::max());
+        uint32_t bytesInLastRow = widthInBlocks * blockInfo.byteSize;
+
+        // These != wgpu::kStrideUndefined checks are technically redundant with the > checks, but
+        // they should get optimized out.
+        if (layout.bytesPerRow != wgpu::kStrideUndefined && bytesInLastRow > layout.bytesPerRow) {
+            return DAWN_VALIDATION_ERROR("The byte size of each row must be <= bytesPerRow.");
+        }
+        if (layout.rowsPerImage != wgpu::kStrideUndefined && heightInBlocks > layout.rowsPerImage) {
+            return DAWN_VALIDATION_ERROR(
+                "The height of each image, in blocks, must be <= rowsPerImage.");
         }
 
         // We compute required bytes in copy after validating texel block alignments
@@ -499,8 +560,10 @@
     MaybeError ValidateBufferCopyView(DeviceBase const* device,
                                       const BufferCopyView& bufferCopyView) {
         DAWN_TRY(device->ValidateObject(bufferCopyView.buffer));
-        if (bufferCopyView.layout.bytesPerRow % kTextureBytesPerRowAlignment != 0) {
-            return DAWN_VALIDATION_ERROR("bytesPerRow must be a multiple of 256");
+        if (bufferCopyView.layout.bytesPerRow != wgpu::kStrideUndefined) {
+            if (bufferCopyView.layout.bytesPerRow % kTextureBytesPerRowAlignment != 0) {
+                return DAWN_VALIDATION_ERROR("bytesPerRow must be a multiple of 256");
+            }
         }
 
         return {};
diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h
index 0eb04f5..3e46774 100644
--- a/src/dawn_native/CommandValidation.h
+++ b/src/dawn_native/CommandValidation.h
@@ -51,7 +51,15 @@
                                                        uint32_t bytesPerRow,
                                                        uint32_t rowsPerImage);
 
-    MaybeError ValidateLinearTextureData(TextureDataLayout layout,
+    TextureDataLayout FixUpDeprecatedTextureDataLayoutOptions(
+        DeviceBase* device,
+        const TextureDataLayout& originalLayout,
+        const TexelBlockInfo& blockInfo,
+        const Extent3D& copyExtent);
+    void ApplyDefaultTextureDataLayoutOptions(TextureDataLayout* layout,
+                                              const TexelBlockInfo& blockInfo,
+                                              const Extent3D& copyExtent);
+    MaybeError ValidateLinearTextureData(const TextureDataLayout& layout,
                                          uint64_t byteSize,
                                          const TexelBlockInfo& blockInfo,
                                          const Extent3D& copyExtent);
diff --git a/src/dawn_native/Format.cpp b/src/dawn_native/Format.cpp
index 74831b3..f57b7b4 100644
--- a/src/dawn_native/Format.cpp
+++ b/src/dawn_native/Format.cpp
@@ -127,6 +127,10 @@
             // formats are set exactly once.
             ASSERT(!formatsSet[index]);
 
+            // Vulkan describes bytesPerRow in units of texels. If there's any format for which this
+            // ASSERT isn't true, then additional validation on bytesPerRow must be added.
+            ASSERT((kTextureBytesPerRowAlignment % format.firstAspect.block.byteSize) == 0);
+
             table[index] = format;
             formatsSet.set(index);
         };
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 11b7f5e..8bf92e1 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -441,10 +441,13 @@
         // copyExtent.height by blockHeight while the divisibility conditions are
         // checked in validating texture copy range.
         DAWN_TRY(ValidateTextureCopyRange(*destination, *writeSize));
-        DAWN_TRY(ValidateLinearTextureData(
-            *dataLayout, dataSize,
-            destination->texture->GetFormat().GetAspectInfo(destination->aspect).block,
-            *writeSize));
+
+        const TexelBlockInfo& blockInfo =
+            destination->texture->GetFormat().GetAspectInfo(destination->aspect).block;
+
+        TextureDataLayout layout = FixUpDeprecatedTextureDataLayoutOptions(GetDevice(), *dataLayout,
+                                                                           blockInfo, *writeSize);
+        DAWN_TRY(ValidateLinearTextureData(layout, dataSize, blockInfo, *writeSize));
 
         DAWN_TRY(destination->texture->ValidateCanUseInSubmitNow());
 
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index 3040fa7..171f2f6 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -889,7 +889,7 @@
     wgpu::TextureCopyView textureCopyView =
         utils::CreateTextureCopyView(texture, level, {x, y, slice}, aspect);
     wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(readback.buffer, readback.offset, bytesPerRow, 0);
+        utils::CreateBufferCopyView(readback.buffer, readback.offset, bytesPerRow, rowsPerImage);
     wgpu::Extent3D copySize = {width, height, 1};
 
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
diff --git a/src/tests/end2end/BindGroupTests.cpp b/src/tests/end2end/BindGroupTests.cpp
index 4239e7d..abc85b2 100644
--- a/src/tests/end2end/BindGroupTests.cpp
+++ b/src/tests/end2end/BindGroupTests.cpp
@@ -304,7 +304,7 @@
 
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
     wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(stagingBuffer, 0, widthInBytes, 0);
+        utils::CreateBufferCopyView(stagingBuffer, 0, widthInBytes);
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
     wgpu::Extent3D copySize = {width, height, 1};
     encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copySize);
diff --git a/src/tests/end2end/CompressedTextureFormatTests.cpp b/src/tests/end2end/CompressedTextureFormatTests.cpp
index 43b8b89..5ad5a32 100644
--- a/src/tests/end2end/CompressedTextureFormatTests.cpp
+++ b/src/tests/end2end/CompressedTextureFormatTests.cpp
@@ -30,7 +30,7 @@
     uint32_t viewMipmapLevel = 0;
     uint32_t bufferOffset = 0;
     uint32_t bytesPerRowAlignment = kTextureBytesPerRowAlignment;
-    uint32_t rowsPerImage = 0;
+    uint32_t rowsPerImage = wgpu::kStrideUndefined;
 };
 
 class CompressedTextureBCFormatTest : public DawnTest {
@@ -60,7 +60,7 @@
                               utils::GetTexelBlockSizeInBytes(copyConfig.textureDescriptor.format);
         }
         uint32_t copyRowsPerImage = copyConfig.rowsPerImage;
-        if (copyRowsPerImage == 0) {
+        if (copyRowsPerImage == wgpu::kStrideUndefined) {
             copyRowsPerImage = copyHeightInBlock;
         }
         uint32_t copyBytesPerImage = copyBytesPerRow * copyRowsPerImage;
@@ -1028,6 +1028,7 @@
     CopyConfig config;
     config.textureDescriptor.usage = kDefaultBCFormatTextureUsage;
     config.textureDescriptor.size = {8, 8, kArrayLayerCount};
+    config.rowsPerImage = 8;
 
     config.copyExtent3D = config.textureDescriptor.size;
     config.copyExtent3D.depth = kArrayLayerCount;
@@ -1054,6 +1055,7 @@
     CopyConfig config;
     config.textureDescriptor.usage = kDefaultBCFormatTextureUsage;
     config.textureDescriptor.size = {8, 8, kArrayLayerCount};
+    config.rowsPerImage = 8;
 
     constexpr uint32_t kCopyBaseArrayLayer = 1;
     constexpr uint32_t kCopyLayerCount = 2;
@@ -1087,7 +1089,7 @@
     wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
 
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
-    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(buffer, 0, 256, 0);
+    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(buffer, 0, 256);
     wgpu::Extent3D copyExtent = {4, 4, 1};
 
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index d1c171e..98a1f091 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -21,6 +21,9 @@
 #include "utils/TextureFormatUtils.h"
 #include "utils/WGPUHelpers.h"
 
+// For MinimumBufferSpec bytesPerRow and rowsPerImage, compute a default from the copy extent.
+constexpr uint32_t kStrideComputeDefault = 0xFFFF'FFFEul;
+
 class CopyTests : public DawnTest {
   protected:
     static constexpr wgpu::TextureFormat kTextureFormat = wgpu::TextureFormat::RGBA8Unorm;
@@ -56,18 +59,27 @@
         return textureData;
     }
 
-    static BufferSpec MinimumBufferSpec(uint32_t width,
-                                        uint32_t height,
-                                        uint32_t arrayLayer = 1,
-                                        bool testZeroRowsPerImage = true) {
-        const uint32_t bytesPerRow = utils::GetMinimumBytesPerRow(kTextureFormat, width);
-        const uint32_t rowsPerImage = height;
-        const uint32_t totalBufferSize = utils::RequiredBytesInCopy(
-            bytesPerRow, rowsPerImage, {width, height, arrayLayer}, kTextureFormat);
-        uint32_t appliedRowsPerImage = testZeroRowsPerImage ? 0 : height;
-        return {totalBufferSize, 0, bytesPerRow, appliedRowsPerImage};
+    static BufferSpec MinimumBufferSpec(uint32_t width, uint32_t height, uint32_t depth = 1) {
+        return MinimumBufferSpec({width, height, depth}, kStrideComputeDefault,
+                                 depth == 1 ? wgpu::kStrideUndefined : kStrideComputeDefault);
     }
 
+    static BufferSpec MinimumBufferSpec(wgpu::Extent3D copyExtent,
+                                        uint32_t overrideBytesPerRow = kStrideComputeDefault,
+                                        uint32_t overrideRowsPerImage = kStrideComputeDefault) {
+        uint32_t bytesPerRow = utils::GetMinimumBytesPerRow(kTextureFormat, copyExtent.width);
+        if (overrideBytesPerRow != kStrideComputeDefault) {
+            bytesPerRow = overrideBytesPerRow;
+        }
+        uint32_t rowsPerImage = copyExtent.height;
+        if (overrideRowsPerImage != kStrideComputeDefault) {
+            rowsPerImage = overrideRowsPerImage;
+        }
+
+        uint32_t totalDataSize =
+            utils::RequiredBytesInCopy(bytesPerRow, rowsPerImage, copyExtent, kTextureFormat);
+        return {totalDataSize, 0, bytesPerRow, rowsPerImage};
+    }
     static void PackTextureData(const RGBA8* srcData,
                                 uint32_t width,
                                 uint32_t height,
@@ -100,10 +112,11 @@
         descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
         wgpu::Texture texture = device.CreateTexture(&descriptor);
 
+        // Layout for initial data upload to texture.
+        // Some parts of this result are also reused later.
         const utils::TextureDataCopyLayout copyLayout =
             utils::GetTextureDataCopyLayoutForTexture2DAtLevel(
-                kTextureFormat, textureSpec.textureSize, textureSpec.level,
-                bufferSpec.rowsPerImage);
+                kTextureFormat, textureSpec.textureSize, textureSpec.level);
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
 
@@ -113,7 +126,7 @@
             wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(
                 device, textureArrayData.data(), copyLayout.byteLength, wgpu::BufferUsage::CopySrc);
             wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(
-                uploadBuffer, 0, copyLayout.bytesPerRow, bufferSpec.rowsPerImage);
+                uploadBuffer, 0, copyLayout.bytesPerRow, copyLayout.rowsPerImage);
             wgpu::TextureCopyView textureCopyView =
                 utils::CreateTextureCopyView(texture, textureSpec.level, {0, 0, 0});
             encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyLayout.mipSize);
@@ -156,22 +169,23 @@
                 texelIndexOffset + (textureSpec.copyOrigin.x +
                                     textureSpec.copyOrigin.y * copyLayout.texelBlocksPerRow);
 
-            PackTextureData(&textureArrayData[expectedTexelArrayDataStartIndex], copySize.width,
-                            copySize.height, copyLayout.texelBlocksPerRow, expected.data(),
-                            bufferSpec.bytesPerRow / bytesPerTexel);
+            PackTextureData(textureArrayData.data() + expectedTexelArrayDataStartIndex,
+                            copySize.width, copySize.height, copyLayout.texelBlocksPerRow,
+                            expected.data(), bufferSpec.bytesPerRow / bytesPerTexel);
 
             EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<const uint32_t*>(expected.data()), buffer,
                                        bufferOffset, static_cast<uint32_t>(expected.size()))
                 << "Texture to Buffer copy failed copying region [(" << textureSpec.copyOrigin.x
-                << ", " << textureSpec.copyOrigin.y << "), ("
+                << ", " << textureSpec.copyOrigin.y << ", " << textureSpec.copyOrigin.z << "), ("
                 << textureSpec.copyOrigin.x + copySize.width << ", "
-                << textureSpec.copyOrigin.y + copySize.height << ")) from "
+                << textureSpec.copyOrigin.y + copySize.height << ", "
+                << textureSpec.copyOrigin.z + copySize.depth << ")) from "
                 << textureSpec.textureSize.width << " x " << textureSpec.textureSize.height
                 << " texture at mip level " << textureSpec.level << " layer " << slice << " to "
                 << bufferSpec.size << "-byte buffer with offset " << bufferOffset
                 << " and bytes per row " << bufferSpec.bytesPerRow << std::endl;
 
-            bufferOffset += copyLayout.bytesPerImage;
+            bufferOffset += bufferSpec.bytesPerRow * bufferSpec.rowsPerImage;
         }
     }
 };
@@ -232,7 +246,7 @@
             // Pack the data used to create the buffer in the specified copy region to have the same
             // format as the expected texture data.
             std::vector<RGBA8> expected(texelCountLastLayer);
-            PackTextureData(&bufferData[bufferOffset / bytesPerTexel], copySize.width,
+            PackTextureData(bufferData.data() + bufferOffset / bytesPerTexel, copySize.width,
                             copySize.height, bufferSpec.bytesPerRow / bytesPerTexel,
                             expected.data(), copySize.width);
 
@@ -288,14 +302,14 @@
             utils::GetTextureDataCopyLayoutForTexture2DAtLevel(
                 kTextureFormat,
                 {srcSpec.textureSize.width, srcSpec.textureSize.height, copySize.depth},
-                srcSpec.level, 0);
+                srcSpec.level);
 
         const std::vector<RGBA8> textureArrayCopyData = GetExpectedTextureData(copyLayout);
 
         wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(
             device, textureArrayCopyData.data(), copyLayout.byteLength, wgpu::BufferUsage::CopySrc);
-        wgpu::BufferCopyView bufferCopyView =
-            utils::CreateBufferCopyView(uploadBuffer, 0, copyLayout.bytesPerRow, 0);
+        wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(
+            uploadBuffer, 0, copyLayout.bytesPerRow, copyLayout.rowsPerImage);
         wgpu::TextureCopyView textureCopyView =
             utils::CreateTextureCopyView(srcTexture, srcSpec.level, {0, 0, srcSpec.copyOrigin.z});
         encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyLayout.mipSize);
@@ -633,7 +647,7 @@
 
 // Test that copying without a 512-byte aligned buffer offset that is greater than the bytes per row
 // works
-TEST_P(CopyTests_T2B, OffsetBufferUnalignedSmallRowPitch) {
+TEST_P(CopyTests_T2B, OffsetBufferUnalignedSmallBytesPerRow) {
     constexpr uint32_t kWidth = 32;
     constexpr uint32_t kHeight = 128;
 
@@ -652,7 +666,7 @@
 }
 
 // Test that copying with a greater bytes per row than needed on a 256-byte aligned texture works
-TEST_P(CopyTests_T2B, RowPitchAligned) {
+TEST_P(CopyTests_T2B, BytesPerRowAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
 
@@ -671,7 +685,7 @@
 
 // Test that copying with a greater bytes per row than needed on a texture that is not 256-byte
 // aligned works
-TEST_P(CopyTests_T2B, RowPitchUnaligned) {
+TEST_P(CopyTests_T2B, BytesPerRowUnaligned) {
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
 
@@ -699,18 +713,55 @@
     textureSpec.textureSize = {kWidth, kHeight, 1};
     textureSpec.level = 0;
 
-    // bytesPerRow = 0
     {
         BufferSpec bufferSpec = MinimumBufferSpec(5, 1);
+
+        // bytesPerRow = 0
+        // TODO(crbug.com/dawn/520): This behavior is deprecated; remove this case.
         bufferSpec.bytesPerRow = 0;
+        EXPECT_DEPRECATION_WARNING(DoTest(textureSpec, bufferSpec, {5, 1, 1}));
+
+        // bytesPerRow undefined
+        bufferSpec.bytesPerRow = wgpu::kStrideUndefined;
         DoTest(textureSpec, bufferSpec, {5, 1, 1});
     }
 
     // bytesPerRow < bytesInACompleteRow
+    // TODO(crbug.com/dawn/520): This behavior is deprecated; remove this case.
     {
         BufferSpec bufferSpec = MinimumBufferSpec(259, 1);
         bufferSpec.bytesPerRow = 256;
-        DoTest(textureSpec, bufferSpec, {259, 1, 1});
+        EXPECT_DEPRECATION_WARNING(DoTest(textureSpec, bufferSpec, {259, 1, 1}));
+    }
+}
+
+TEST_P(CopyTests_T2B, StrideSpecialCases) {
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {4, 4, 4};
+    textureSpec.level = 0;
+
+    // bytesPerRow 0
+    for (const wgpu::Extent3D copyExtent :
+         {wgpu::Extent3D{0, 2, 2}, {0, 0, 2}, {0, 2, 0}, {0, 0, 0}}) {
+        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 0, 2), copyExtent);
+    }
+
+    // bytesPerRow undefined
+    for (const wgpu::Extent3D copyExtent :
+         {wgpu::Extent3D{2, 1, 1}, {2, 0, 1}, {2, 1, 0}, {2, 0, 0}}) {
+        DoTest(textureSpec, MinimumBufferSpec(copyExtent, wgpu::kStrideUndefined, 2), copyExtent);
+    }
+
+    // rowsPerImage 0
+    for (const wgpu::Extent3D copyExtent :
+         {wgpu::Extent3D{2, 0, 2}, {2, 0, 0}, {0, 0, 2}, {0, 0, 0}}) {
+        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 256, 0), copyExtent);
+    }
+
+    // rowsPerImage undefined
+    for (const wgpu::Extent3D copyExtent : {wgpu::Extent3D{2, 2, 1}, {2, 2, 0}}) {
+        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 256, wgpu::kStrideUndefined), copyExtent);
     }
 }
 
@@ -780,7 +831,7 @@
     textureSpec.textureSize = {kWidth, kHeight, kLayers};
     textureSpec.level = 0;
 
-    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
+    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
     bufferSpec.rowsPerImage = kRowsPerImage;
     DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
 }
@@ -801,7 +852,7 @@
     textureSpec.textureSize = {kWidth, kHeight, kLayers};
     textureSpec.level = 0;
 
-    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
+    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
     bufferSpec.offset += 128u;
     bufferSpec.size += 128u;
     bufferSpec.rowsPerImage = kRowsPerImage;
@@ -824,7 +875,7 @@
     textureSpec.textureSize = {kWidth, kHeight, kLayers};
     textureSpec.level = 0;
 
-    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
+    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
     bufferSpec.offset += 128u;
     bufferSpec.size += 128u;
     bufferSpec.rowsPerImage = kRowsPerImage;
@@ -1083,7 +1134,7 @@
 
 // Test that copying without a 512-byte aligned buffer offset that is greater than the bytes per row
 // works
-TEST_P(CopyTests_B2T, OffsetBufferUnalignedSmallRowPitch) {
+TEST_P(CopyTests_B2T, OffsetBufferUnalignedSmallBytesPerRow) {
     constexpr uint32_t kWidth = 32;
     constexpr uint32_t kHeight = 128;
 
@@ -1102,7 +1153,7 @@
 }
 
 // Test that copying with a greater bytes per row than needed on a 256-byte aligned texture works
-TEST_P(CopyTests_B2T, RowPitchAligned) {
+TEST_P(CopyTests_B2T, BytesPerRowAligned) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
 
@@ -1121,7 +1172,7 @@
 
 // Test that copying with a greater bytes per row than needed on a texture that is not 256-byte
 // aligned works
-TEST_P(CopyTests_B2T, RowPitchUnaligned) {
+TEST_P(CopyTests_B2T, BytesPerRowUnaligned) {
     constexpr uint32_t kWidth = 259;
     constexpr uint32_t kHeight = 127;
 
@@ -1149,18 +1200,55 @@
     textureSpec.textureSize = {kWidth, kHeight, 1};
     textureSpec.level = 0;
 
-    // bytesPerRow = 0
     {
         BufferSpec bufferSpec = MinimumBufferSpec(5, 1);
+
+        // bytesPerRow = 0
+        // TODO(crbug.com/dawn/520): This behavior is deprecated; remove this case.
         bufferSpec.bytesPerRow = 0;
+        EXPECT_DEPRECATION_WARNING(DoTest(textureSpec, bufferSpec, {5, 1, 1}));
+
+        // bytesPerRow undefined
+        bufferSpec.bytesPerRow = wgpu::kStrideUndefined;
         DoTest(textureSpec, bufferSpec, {5, 1, 1});
     }
 
     // bytesPerRow < bytesInACompleteRow
+    // TODO(crbug.com/dawn/520): This behavior is deprecated; remove this case.
     {
         BufferSpec bufferSpec = MinimumBufferSpec(259, 1);
         bufferSpec.bytesPerRow = 256;
-        DoTest(textureSpec, bufferSpec, {259, 1, 1});
+        EXPECT_DEPRECATION_WARNING(DoTest(textureSpec, bufferSpec, {259, 1, 1}));
+    }
+}
+
+TEST_P(CopyTests_B2T, StrideSpecialCases) {
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {4, 4, 4};
+    textureSpec.level = 0;
+
+    // bytesPerRow 0
+    for (const wgpu::Extent3D copyExtent :
+         {wgpu::Extent3D{0, 2, 2}, {0, 0, 2}, {0, 2, 0}, {0, 0, 0}}) {
+        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 0, 2), copyExtent);
+    }
+
+    // bytesPerRow undefined
+    for (const wgpu::Extent3D copyExtent :
+         {wgpu::Extent3D{2, 1, 1}, {2, 0, 1}, {2, 1, 0}, {2, 0, 0}}) {
+        DoTest(textureSpec, MinimumBufferSpec(copyExtent, wgpu::kStrideUndefined, 2), copyExtent);
+    }
+
+    // rowsPerImage 0
+    for (const wgpu::Extent3D copyExtent :
+         {wgpu::Extent3D{2, 0, 2}, {2, 0, 0}, {0, 0, 2}, {0, 0, 0}}) {
+        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 256, 0), copyExtent);
+    }
+
+    // rowsPerImage undefined
+    for (const wgpu::Extent3D copyExtent : {wgpu::Extent3D{2, 2, 1}, {2, 2, 0}}) {
+        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 256, wgpu::kStrideUndefined), copyExtent);
     }
 }
 
@@ -1211,7 +1299,7 @@
     textureSpec.textureSize = {kWidth, kHeight, kLayers};
     textureSpec.level = 0;
 
-    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
+    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
     bufferSpec.rowsPerImage = kRowsPerImage;
     DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
 }
@@ -1232,7 +1320,7 @@
     textureSpec.textureSize = {kWidth, kHeight, kLayers};
     textureSpec.level = 0;
 
-    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
+    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
     bufferSpec.offset += 128u;
     bufferSpec.size += 128u;
     bufferSpec.rowsPerImage = kRowsPerImage;
@@ -1255,7 +1343,7 @@
     textureSpec.textureSize = {kWidth, kHeight, kLayers};
     textureSpec.level = 0;
 
-    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
+    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
     bufferSpec.offset += 128u;
     bufferSpec.size += 128u;
     bufferSpec.rowsPerImage = kRowsPerImage;
diff --git a/src/tests/end2end/CopyTextureForBrowserTests.cpp b/src/tests/end2end/CopyTextureForBrowserTests.cpp
index 1ab8a44..c38cbee 100644
--- a/src/tests/end2end/CopyTextureForBrowserTests.cpp
+++ b/src/tests/end2end/CopyTextureForBrowserTests.cpp
@@ -92,7 +92,7 @@
             utils::GetTextureDataCopyLayoutForTexture2DAtLevel(
                 kTextureFormat,
                 {srcSpec.textureSize.width, srcSpec.textureSize.height, copySize.depth},
-                srcSpec.level, 0);
+                srcSpec.level);
 
         const std::vector<RGBA8> textureArrayCopyData = GetExpectedTextureData(copyLayout);
         wgpu::TextureCopyView textureCopyView =
@@ -101,7 +101,7 @@
         wgpu::TextureDataLayout textureDataLayout;
         textureDataLayout.offset = 0;
         textureDataLayout.bytesPerRow = copyLayout.bytesPerRow;
-        textureDataLayout.rowsPerImage = copyLayout.bytesPerImage / copyLayout.bytesPerRow;
+        textureDataLayout.rowsPerImage = copyLayout.rowsPerImage;
 
         device.GetDefaultQueue().WriteTexture(&textureCopyView, textureArrayCopyData.data(),
                                               textureArrayCopyData.size() * sizeof(RGBA8),
diff --git a/src/tests/end2end/NonzeroTextureCreationTests.cpp b/src/tests/end2end/NonzeroTextureCreationTests.cpp
index 2e574cc..efb301b 100644
--- a/src/tests/end2end/NonzeroTextureCreationTests.cpp
+++ b/src/tests/end2end/NonzeroTextureCreationTests.cpp
@@ -134,7 +134,7 @@
     wgpu::Buffer bufferDst = utils::CreateBufferFromData(
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
 
-    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(bufferDst, 0, kSize * 4, 0);
+    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(bufferDst, 0, kSize * 4);
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
     wgpu::Extent3D copySize = {kSize, kSize, 1};
 
@@ -168,7 +168,7 @@
     wgpu::Buffer bufferDst = utils::CreateBufferFromData(
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
 
-    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(bufferDst, 0, kSize * 4, 0);
+    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(bufferDst, 0, kSize * 4);
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 1});
     wgpu::Extent3D copySize = {kSize, kSize, 1};
 
diff --git a/src/tests/end2end/QueueTests.cpp b/src/tests/end2end/QueueTests.cpp
index bcda12f..6502315 100644
--- a/src/tests/end2end/QueueTests.cpp
+++ b/src/tests/end2end/QueueTests.cpp
@@ -199,6 +199,9 @@
                       OpenGLBackend(),
                       VulkanBackend());
 
+// For MinimumDataSpec bytesPerRow and rowsPerImage, compute a default from the copy extent.
+constexpr uint32_t kStrideComputeDefault = 0xFFFF'FFFEul;
+
 class QueueWriteTextureTests : public DawnTest {
   protected:
     static constexpr wgpu::TextureFormat kTextureFormat = wgpu::TextureFormat::RGBA8Unorm;
@@ -217,14 +220,17 @@
     };
 
     static DataSpec MinimumDataSpec(wgpu::Extent3D writeSize,
-                                    uint32_t bytesPerRow = 0,
-                                    uint32_t rowsPerImage = 0) {
-        if (bytesPerRow == 0) {
-            bytesPerRow = writeSize.width * utils::GetTexelBlockSizeInBytes(kTextureFormat);
+                                    uint32_t overrideBytesPerRow = kStrideComputeDefault,
+                                    uint32_t overrideRowsPerImage = kStrideComputeDefault) {
+        uint32_t bytesPerRow = writeSize.width * utils::GetTexelBlockSizeInBytes(kTextureFormat);
+        if (overrideBytesPerRow != kStrideComputeDefault) {
+            bytesPerRow = overrideBytesPerRow;
         }
-        if (rowsPerImage == 0) {
-            rowsPerImage = writeSize.height;
+        uint32_t rowsPerImage = writeSize.height;
+        if (overrideRowsPerImage != kStrideComputeDefault) {
+            rowsPerImage = overrideRowsPerImage;
         }
+
         uint32_t totalDataSize =
             utils::RequiredBytesInCopy(bytesPerRow, rowsPerImage, writeSize, kTextureFormat);
         return {totalDataSize, 0, bytesPerRow, rowsPerImage};
@@ -282,10 +288,14 @@
         wgpu::Extent3D mipSize = {textureSpec.textureSize.width >> textureSpec.level,
                                   textureSpec.textureSize.height >> textureSpec.level,
                                   textureSpec.textureSize.depth};
-        uint32_t alignedBytesPerRow = Align(dataSpec.bytesPerRow, bytesPerTexel);
+        uint32_t bytesPerRow = dataSpec.bytesPerRow;
+        if (bytesPerRow == wgpu::kStrideUndefined) {
+            bytesPerRow = mipSize.width * bytesPerTexel;
+        }
+        uint32_t alignedBytesPerRow = Align(bytesPerRow, bytesPerTexel);
         uint32_t appliedRowsPerImage =
             dataSpec.rowsPerImage > 0 ? dataSpec.rowsPerImage : mipSize.height;
-        uint32_t bytesPerImage = dataSpec.bytesPerRow * appliedRowsPerImage;
+        uint32_t bytesPerImage = bytesPerRow * appliedRowsPerImage;
 
         const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depth;
 
@@ -296,7 +306,7 @@
             // Pack the data in the specified copy region to have the same
             // format as the expected texture data.
             std::vector<RGBA8> expected(texelCountLastLayer);
-            PackTextureData(&data[dataOffset], copySize.width, copySize.height,
+            PackTextureData(data.data() + dataOffset, copySize.width, copySize.height,
                             dataSpec.bytesPerRow, expected.data(), copySize.width, bytesPerTexel);
 
             EXPECT_TEXTURE_RGBA8_EQ(expected.data(), texture, textureSpec.copyOrigin.x,
@@ -468,7 +478,7 @@
         textureSpec.textureSize = {kWidth, kHeight, kDepth};
         textureSpec.level = 0;
 
-        DataSpec dataSpec = MinimumDataSpec(copySize, 0, copySize.height + r);
+        DataSpec dataSpec = MinimumDataSpec(copySize, kStrideComputeDefault, copySize.height + r);
         DoTest(textureSpec, dataSpec, copySize);
     }
 }
@@ -488,7 +498,7 @@
     for (unsigned int b : {1, 2, 3, 4}) {
         uint32_t bytesPerRow =
             copyExtent.width * utils::GetTexelBlockSizeInBytes(kTextureFormat) + b;
-        DoTest(textureSpec, MinimumDataSpec(copyExtent, bytesPerRow, 0), copyExtent);
+        DoTest(textureSpec, MinimumDataSpec(copyExtent, bytesPerRow), copyExtent);
     }
 }
 
@@ -503,22 +513,27 @@
     textureSpec.textureSize = {kWidth, kHeight, 1};
     textureSpec.level = 0;
 
-    // bytesPerRow = 0
     {
         constexpr wgpu::Extent3D copyExtent = {5, 1, 1};
-
         DataSpec dataSpec = MinimumDataSpec(copyExtent);
+
+        // bytesPerRow = 0
+        // TODO(crbug.com/dawn/520): This behavior is deprecated; remove this case.
         dataSpec.bytesPerRow = 0;
+        EXPECT_DEPRECATION_WARNING(DoTest(textureSpec, dataSpec, copyExtent));
+
+        // bytesPerRow undefined
+        dataSpec.bytesPerRow = wgpu::kStrideUndefined;
         DoTest(textureSpec, dataSpec, copyExtent);
     }
 
     // bytesPerRow < bytesInACompleteRow
+    // TODO(crbug.com/dawn/520): This behavior is deprecated; remove this case.
     {
         constexpr wgpu::Extent3D copyExtent = {259, 1, 1};
-
         DataSpec dataSpec = MinimumDataSpec(copyExtent);
         dataSpec.bytesPerRow = 256;
-        DoTest(textureSpec, dataSpec, copyExtent);
+        EXPECT_DEPRECATION_WARNING(DoTest(textureSpec, dataSpec, copyExtent));
     }
 }
 
@@ -552,6 +567,37 @@
     }
 }
 
+// Test valid special cases of bytesPerRow and rowsPerImage (0 or undefined).
+TEST_P(QueueWriteTextureTests, StrideSpecialCases) {
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.textureSize = {4, 4, 4};
+    textureSpec.level = 0;
+
+    // bytesPerRow 0
+    for (const wgpu::Extent3D copyExtent :
+         {wgpu::Extent3D{0, 2, 2}, {0, 0, 2}, {0, 2, 0}, {0, 0, 0}}) {
+        DoTest(textureSpec, MinimumDataSpec(copyExtent, 0, 2), copyExtent);
+    }
+
+    // bytesPerRow undefined
+    for (const wgpu::Extent3D copyExtent :
+         {wgpu::Extent3D{2, 1, 1}, {2, 0, 1}, {2, 1, 0}, {2, 0, 0}}) {
+        DoTest(textureSpec, MinimumDataSpec(copyExtent, wgpu::kStrideUndefined, 2), copyExtent);
+    }
+
+    // rowsPerImage 0
+    for (const wgpu::Extent3D copyExtent :
+         {wgpu::Extent3D{2, 0, 2}, {2, 0, 0}, {0, 0, 2}, {0, 0, 0}}) {
+        DoTest(textureSpec, MinimumDataSpec(copyExtent, 256, 0), copyExtent);
+    }
+
+    // rowsPerImage undefined
+    for (const wgpu::Extent3D copyExtent : {wgpu::Extent3D{2, 2, 1}, {2, 2, 0}}) {
+        DoTest(textureSpec, MinimumDataSpec(copyExtent, 256, wgpu::kStrideUndefined), copyExtent);
+    }
+}
+
 // Testing a special code path: writing when dynamic uploader already contatins some unaligned
 // data, it might be necessary to use a ring buffer with properly aligned offset.
 TEST_P(QueueWriteTextureTests, UnalignedDynamicUploader) {
diff --git a/src/tests/end2end/RenderPassLoadOpTests.cpp b/src/tests/end2end/RenderPassLoadOpTests.cpp
index 645ccb5..af351b1 100644
--- a/src/tests/end2end/RenderPassLoadOpTests.cpp
+++ b/src/tests/end2end/RenderPassLoadOpTests.cpp
@@ -123,7 +123,7 @@
 
         wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
         wgpu::BufferCopyView bufferCopyView =
-            utils::CreateBufferCopyView(buffer, 0, kTextureBytesPerRowAlignment, 0);
+            utils::CreateBufferCopyView(buffer, 0, kTextureBytesPerRowAlignment);
         encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, &kTextureSize);
 
         wgpu::CommandBuffer commandBuffer = encoder.Finish();
diff --git a/src/tests/end2end/SamplerTests.cpp b/src/tests/end2end/SamplerTests.cpp
index ad5ed92..b3483d6 100644
--- a/src/tests/end2end/SamplerTests.cpp
+++ b/src/tests/end2end/SamplerTests.cpp
@@ -104,7 +104,7 @@
 
         wgpu::Buffer stagingBuffer =
             utils::CreateBufferFromData(device, data, sizeof(data), wgpu::BufferUsage::CopySrc);
-        wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(stagingBuffer, 0, 256, 0);
+        wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(stagingBuffer, 0, 256);
         wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
         wgpu::Extent3D copySize = {2, 2, 1};
 
diff --git a/src/tests/end2end/StorageTextureTests.cpp b/src/tests/end2end/StorageTextureTests.cpp
index 6e6a97f..536766a 100644
--- a/src/tests/end2end/StorageTextureTests.cpp
+++ b/src/tests/end2end/StorageTextureTests.cpp
@@ -464,7 +464,7 @@
 
         const wgpu::Extent3D copyExtent = {kWidth, kHeight, arrayLayerCount};
         wgpu::BufferCopyView bufferCopyView =
-            utils::CreateBufferCopyView(uploadBuffer, 0, kTextureBytesPerRowAlignment, 0);
+            utils::CreateBufferCopyView(uploadBuffer, 0, kTextureBytesPerRowAlignment, kHeight);
         wgpu::TextureCopyView textureCopyView;
         textureCopyView.texture = outputTexture;
         encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyExtent);
@@ -640,7 +640,7 @@
         wgpu::TextureCopyView textureCopyView =
             utils::CreateTextureCopyView(writeonlyStorageTexture, 0, {0, 0, 0});
         wgpu::BufferCopyView bufferCopyView =
-            utils::CreateBufferCopyView(resultBuffer, 0, kTextureBytesPerRowAlignment, 0);
+            utils::CreateBufferCopyView(resultBuffer, 0, kTextureBytesPerRowAlignment, kHeight);
         encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, &copyExtent);
         wgpu::CommandBuffer commandBuffer = encoder.Finish();
         queue.Submit(1, &commandBuffer);
diff --git a/src/tests/end2end/TextureFormatTests.cpp b/src/tests/end2end/TextureFormatTests.cpp
index 1055267..f26153f 100644
--- a/src/tests/end2end/TextureFormatTests.cpp
+++ b/src/tests/end2end/TextureFormatTests.cpp
@@ -240,7 +240,7 @@
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
 
         {
-            wgpu::BufferCopyView bufferView = utils::CreateBufferCopyView(uploadBuffer, 0, 256, 0);
+            wgpu::BufferCopyView bufferView = utils::CreateBufferCopyView(uploadBuffer, 0, 256);
             wgpu::TextureCopyView textureView =
                 utils::CreateTextureCopyView(sampleTexture, 0, {0, 0, 0});
             wgpu::Extent3D extent{width, 1, 1};
@@ -255,8 +255,7 @@
         renderPass.EndPass();
 
         {
-            wgpu::BufferCopyView bufferView =
-                utils::CreateBufferCopyView(readbackBuffer, 0, 256, 0);
+            wgpu::BufferCopyView bufferView = utils::CreateBufferCopyView(readbackBuffer, 0, 256);
             wgpu::TextureCopyView textureView =
                 utils::CreateTextureCopyView(renderTarget, 0, {0, 0, 0});
             wgpu::Extent3D extent{width, 1, 1};
diff --git a/src/tests/end2end/TextureViewTests.cpp b/src/tests/end2end/TextureViewTests.cpp
index 597ce35..139e5c6 100644
--- a/src/tests/end2end/TextureViewTests.cpp
+++ b/src/tests/end2end/TextureViewTests.cpp
@@ -134,7 +134,7 @@
                 wgpu::Buffer stagingBuffer = utils::CreateBufferFromData(
                     device, data.data(), data.size() * sizeof(RGBA8), wgpu::BufferUsage::CopySrc);
                 wgpu::BufferCopyView bufferCopyView =
-                    utils::CreateBufferCopyView(stagingBuffer, 0, kTextureBytesPerRowAlignment, 0);
+                    utils::CreateBufferCopyView(stagingBuffer, 0, kTextureBytesPerRowAlignment);
                 wgpu::TextureCopyView textureCopyView =
                     utils::CreateTextureCopyView(mTexture, level, {0, 0, layer});
                 wgpu::Extent3D copySize = {texWidth, texHeight, 1};
diff --git a/src/tests/end2end/TextureZeroInitTests.cpp b/src/tests/end2end/TextureZeroInitTests.cpp
index bb9231b..e8ec0cc 100644
--- a/src/tests/end2end/TextureZeroInitTests.cpp
+++ b/src/tests/end2end/TextureZeroInitTests.cpp
@@ -153,7 +153,7 @@
     wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
 
     const wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(buffer, 0, bytesPerRow, 0);
+        utils::CreateBufferCopyView(buffer, 0, bytesPerRow, kSize);
     const wgpu::TextureCopyView textureCopyView =
         utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
     const wgpu::Extent3D copySize = {kSize, kSize, kArrayLayers};
@@ -275,7 +275,7 @@
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
 
     wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * sizeof(uint32_t), 0);
+        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * sizeof(uint32_t));
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
     wgpu::Extent3D copySize = {kSize, kSize, 1};
 
@@ -306,7 +306,7 @@
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
 
     wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * sizeof(uint16_t), 0);
+        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * sizeof(uint16_t));
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
     wgpu::Extent3D copySize = {kSize / 2, kSize, 1};
 
@@ -340,7 +340,7 @@
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
 
     const wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * kFormatBlockByteSize, 0);
+        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * kFormatBlockByteSize, kSize);
     const wgpu::TextureCopyView textureCopyView =
         utils::CreateTextureCopyView(texture, 0, {0, 0, kBaseArrayLayer});
     const wgpu::Extent3D copySize = {kSize, kSize, kCopyLayerCount};
@@ -414,7 +414,7 @@
         wgpu::Buffer stagingBuffer = utils::CreateBufferFromData(
             device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
         wgpu::BufferCopyView bufferCopyView =
-            utils::CreateBufferCopyView(stagingBuffer, 0, kSize * kFormatBlockByteSize, 0);
+            utils::CreateBufferCopyView(stagingBuffer, 0, kSize * kFormatBlockByteSize);
         wgpu::TextureCopyView textureCopyView =
             utils::CreateTextureCopyView(srcTexture, 0, {0, 0, 0});
         wgpu::Extent3D copySize = {kSize, kSize, 1};
@@ -965,7 +965,7 @@
     wgpu::Buffer bufferDst = utils::CreateBufferFromData(
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
 
-    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(bufferDst, 0, bytesPerRow, 0);
+    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(bufferDst, 0, bytesPerRow);
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
     wgpu::Extent3D copySize = {kSize, kSize, 1};
 
@@ -996,7 +996,7 @@
     std::vector<uint8_t> data(bufferSize, 100);
     wgpu::Buffer bufferDst = utils::CreateBufferFromData(
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
-    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(bufferDst, 0, bytesPerRow, 0);
+    wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(bufferDst, 0, bytesPerRow);
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
     wgpu::Extent3D copySize = {kUnalignedSize, kUnalignedSize, 1};
 
@@ -1026,7 +1026,7 @@
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
 
     wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(bufferDst, 0, kSize * kFormatBlockByteSize, 0);
+        utils::CreateBufferCopyView(bufferDst, 0, kSize * kFormatBlockByteSize);
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 1});
     wgpu::Extent3D copySize = {kSize, kSize, 1};
 
@@ -1064,7 +1064,7 @@
     wgpu::Buffer stagingBuffer = utils::CreateBufferFromData(
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
     wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * kFormatBlockByteSize, 0);
+        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * kFormatBlockByteSize);
     wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
     wgpu::Extent3D copySize = {kSize, kSize, 1};
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
@@ -1212,7 +1212,7 @@
     wgpu::Buffer stagingBuffer = utils::CreateBufferFromData(
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
     wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(stagingBuffer, 0, mipSize * kFormatBlockByteSize, 0);
+        utils::CreateBufferCopyView(stagingBuffer, 0, mipSize * kFormatBlockByteSize);
     wgpu::TextureCopyView textureCopyView =
         utils::CreateTextureCopyView(sampleTexture, 1, {0, 0, 0});
     wgpu::Extent3D copySize = {mipSize, mipSize, 1};
@@ -1290,7 +1290,7 @@
     wgpu::Buffer stagingBuffer = utils::CreateBufferFromData(
         device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc);
     wgpu::BufferCopyView bufferCopyView =
-        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * kFormatBlockByteSize, 0);
+        utils::CreateBufferCopyView(stagingBuffer, 0, kSize * kFormatBlockByteSize);
     wgpu::TextureCopyView textureCopyView =
         utils::CreateTextureCopyView(sampleTexture, 0, {0, 0, 1});
     wgpu::Extent3D copySize = {kSize, kSize, 1};
@@ -1374,8 +1374,7 @@
                                                           bufferSize, wgpu::BufferUsage::CopyDst);
 
         wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
-        wgpu::BufferCopyView bufferCopyView =
-            utils::CreateBufferCopyView(buffer, 0, bytesPerRow, 0);
+        wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView(buffer, 0, bytesPerRow);
         wgpu::Extent3D copySize = {kUnalignedSize, kUnalignedSize, 1};
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
index 55c5fbc..2804078 100644
--- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
+++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
@@ -367,51 +367,75 @@
     // Different copies, including some that touch the OOB condition
     {
         // Copy 4x4 block in corner of first mip.
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 4, destination, 0, {0, 0, 0},
                     {4, 4, 1});
         // Copy 4x4 block in opposite corner of first mip.
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {12, 12, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 4, destination, 0, {12, 12, 0},
                     {4, 4, 1});
         // Copy 4x4 block in the 4x4 mip.
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 2, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 4, destination, 2, {0, 0, 0},
                     {4, 4, 1});
         // Copy with a buffer offset
-        TestB2TCopy(utils::Expectation::Success, source, bufferSize - 4, 256, 0, destination, 0,
+        TestB2TCopy(utils::Expectation::Success, source, bufferSize - 4, 256, 1, destination, 0,
                     {0, 0, 0}, {1, 1, 1});
+        TestB2TCopy(utils::Expectation::Success, source, bufferSize - 4, 256,
+                    wgpu::kStrideUndefined, destination, 0, {0, 0, 0}, {1, 1, 1});
     }
 
     // Copies with a 256-byte aligned bytes per row but unaligned texture region
     {
         // Unaligned region
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 4, destination, 0, {0, 0, 0},
                     {3, 4, 1});
         // Unaligned region with texture offset
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {5, 7, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 3, destination, 0, {5, 7, 0},
                     {2, 3, 1});
         // Unaligned region, with buffer offset
-        TestB2TCopy(utils::Expectation::Success, source, 31 * 4, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 31 * 4, 256, 3, destination, 0, {0, 0, 0},
                     {3, 3, 1});
     }
 
+    // bytesPerRow is undefined
+    {
+        TestB2TCopy(utils::Expectation::Success, source, 0, wgpu::kStrideUndefined, 2, destination,
+                    0, {0, 0, 0}, {1, 1, 1});
+        TestB2TCopy(utils::Expectation::Success, source, 0, wgpu::kStrideUndefined, 2, destination,
+                    0, {0, 0, 0}, {3, 1, 1});
+        // Fail because height or depth is greater than 1:
+        TestB2TCopy(utils::Expectation::Failure, source, 0, wgpu::kStrideUndefined, 2, destination,
+                    0, {0, 0, 0}, {1, 2, 1});
+        TestB2TCopy(utils::Expectation::Failure, source, 0, wgpu::kStrideUndefined, 2, destination,
+                    0, {0, 0, 0}, {1, 1, 2});
+    }
+
     // Empty copies are valid
     {
         // An empty copy
         TestB2TCopy(utils::Expectation::Success, source, 0, 0, 0, destination, 0, {0, 0, 0},
                     {0, 0, 1});
+        TestB2TCopy(utils::Expectation::Success, source, 0, wgpu::kStrideUndefined, 0, destination,
+                    0, {0, 0, 0}, {0, 0, 1});
         // An empty copy with depth = 0
         TestB2TCopy(utils::Expectation::Success, source, 0, 0, 0, destination, 0, {0, 0, 0},
                     {0, 0, 0});
+        TestB2TCopy(utils::Expectation::Success, source, 0, wgpu::kStrideUndefined, 0, destination,
+                    0, {0, 0, 0}, {0, 0, 0});
         // An empty copy touching the end of the buffer
         TestB2TCopy(utils::Expectation::Success, source, bufferSize, 0, 0, destination, 0,
                     {0, 0, 0}, {0, 0, 1});
+        TestB2TCopy(utils::Expectation::Success, source, bufferSize, wgpu::kStrideUndefined, 0,
+                    destination, 0, {0, 0, 0}, {0, 0, 1});
         // An empty copy touching the side of the texture
         TestB2TCopy(utils::Expectation::Success, source, 0, 0, 0, destination, 0, {16, 16, 0},
                     {0, 0, 1});
+        TestB2TCopy(utils::Expectation::Success, source, 0, wgpu::kStrideUndefined, 0, destination,
+                    0, {16, 16, 0}, {0, 0, 1});
+
         // An empty copy with depth = 1 and bytesPerRow > 0
         TestB2TCopy(utils::Expectation::Success, source, 0, kTextureBytesPerRowAlignment, 0,
                     destination, 0, {0, 0, 0}, {0, 0, 1});
         // An empty copy with height > 0, depth = 0, bytesPerRow > 0 and rowsPerImage > 0
-        TestB2TCopy(utils::Expectation::Success, source, 0, kTextureBytesPerRowAlignment, 16,
+        TestB2TCopy(utils::Expectation::Success, source, 0, kTextureBytesPerRowAlignment, 3,
                     destination, 0, {0, 0, 0}, {0, 1, 0});
     }
 }
@@ -424,16 +448,16 @@
         Create2DTexture(16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
 
     // OOB on the buffer because we copy too many pixels
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 5, destination, 0, {0, 0, 0},
                 {4, 5, 1});
 
     // OOB on the buffer because of the offset
-    TestB2TCopy(utils::Expectation::Failure, source, 4, 256, 0, destination, 0, {0, 0, 0},
+    TestB2TCopy(utils::Expectation::Failure, source, 4, 256, 4, destination, 0, {0, 0, 0},
                 {4, 4, 1});
 
     // OOB on the buffer because (bytes per row * (height - 1) + width * bytesPerPixel) * depth
     // overflows
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 512, 0, destination, 0, {0, 0, 0},
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 512, 3, destination, 0, {0, 0, 0},
                 {4, 3, 1});
 
     // Not OOB on the buffer although bytes per row * height overflows
@@ -443,7 +467,7 @@
         ASSERT_TRUE(256 * 3 > sourceBufferSize) << "bytes per row * height should overflow buffer";
         wgpu::Buffer sourceBuffer = CreateBuffer(sourceBufferSize, wgpu::BufferUsage::CopySrc);
 
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 3, destination, 0, {0, 0, 0},
                     {7, 3, 1});
     }
 }
@@ -456,15 +480,15 @@
         Create2DTexture(16, 16, 5, 2, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
 
     // OOB on the texture because x + width overflows
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {13, 12, 0},
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 4, destination, 0, {13, 12, 0},
                 {4, 4, 1});
 
     // OOB on the texture because y + width overflows
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {12, 13, 0},
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 4, destination, 0, {12, 13, 0},
                 {4, 4, 1});
 
     // OOB on the texture because we overflow a non-zero mip
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 2, {1, 0, 0},
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 4, destination, 2, {1, 0, 0},
                 {4, 4, 1});
 
     // OOB on the texture even on an empty copy when we copy to a non-existent mip.
@@ -494,80 +518,106 @@
         Create2DTexture(16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::Sampled);
 
     // Incorrect source usage
-    TestB2TCopy(utils::Expectation::Failure, vertex, 0, 256, 0, destination, 0, {0, 0, 0},
+    TestB2TCopy(utils::Expectation::Failure, vertex, 0, 256, 4, destination, 0, {0, 0, 0},
                 {4, 4, 1});
 
     // Incorrect destination usage
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, sampled, 0, {0, 0, 0}, {4, 4, 1});
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 4, sampled, 0, {0, 0, 0}, {4, 4, 1});
 }
 
-TEST_F(CopyCommandTest_B2T, IncorrectBytesPerRow) {
+TEST_F(CopyCommandTest_B2T, BytesPerRowConstraints) {
     uint64_t bufferSize = BufferSizeForTextureCopy(128, 16, 1);
     wgpu::Buffer source = CreateBuffer(bufferSize, wgpu::BufferUsage::CopySrc);
-    wgpu::Texture destination = Create2DTexture(128, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
+    wgpu::Texture destination = Create2DTexture(128, 16, 5, 5, wgpu::TextureFormat::RGBA8Unorm,
                                                 wgpu::TextureUsage::CopyDst);
 
     // bytes per row is 0
     {
         // copyHeight > 1
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 0, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 0, 4, destination, 0, {0, 0, 0},
                     {64, 4, 1});
+        TestB2TCopy(utils::Expectation::Success, source, 0, 0, 4, destination, 0, {0, 0, 0},
+                    {0, 4, 1});
 
         // copyDepth > 1
         TestB2TCopy(utils::Expectation::Failure, source, 0, 0, 1, destination, 0, {0, 0, 0},
                     {64, 1, 4});
+        TestB2TCopy(utils::Expectation::Success, source, 0, 0, 1, destination, 0, {0, 0, 0},
+                    {0, 1, 4});
 
         // copyHeight = 1 and copyDepth = 1
-        TestB2TCopy(utils::Expectation::Success, source, 0, 0, 0, destination, 0, {0, 0, 0},
-                    {64, 1, 1});
+        // TODO(crbug.com/dawn/520): Change to ::Failure.
+        EXPECT_DEPRECATION_WARNING(TestB2TCopy(utils::Expectation::Success, source, 0, 0, 1,
+                                               destination, 0, {0, 0, 0}, {64, 1, 1}));
     }
 
     // bytes per row is not 256-byte aligned
     {
         // copyHeight > 1
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 128, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 128, 4, destination, 0, {0, 0, 0},
                     {4, 4, 1});
 
         // copyHeight = 1 and copyDepth = 1
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 128, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 128, 1, destination, 0, {0, 0, 0},
                     {4, 1, 1});
     }
 
     // bytes per row is less than width * bytesPerPixel
     {
         // copyHeight > 1
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 2, destination, 0, {0, 0, 0},
                     {65, 2, 1});
+        // copyHeight == 0
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+                    {65, 0, 1});
 
         // copyDepth > 1
         TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 1, destination, 0, {0, 0, 0},
                     {65, 1, 2});
+        // copyDepth == 0
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 1, destination, 0, {0, 0, 0},
+                    {65, 1, 0});
 
         // copyHeight = 1 and copyDepth = 1
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {0, 0, 0},
-                    {65, 1, 1});
+        // TODO(crbug.com/dawn/520): Change to ::Failure.
+        EXPECT_DEPRECATION_WARNING(TestB2TCopy(utils::Expectation::Success, source, 0, 256, 1,
+                                               destination, 0, {0, 0, 0}, {65, 1, 1}));
     }
 }
 
-TEST_F(CopyCommandTest_B2T, ImageHeightConstraint) {
-    uint64_t bufferSize = BufferSizeForTextureCopy(5, 5, 1);
+TEST_F(CopyCommandTest_B2T, RowsPerImageConstraints) {
+    uint64_t bufferSize = BufferSizeForTextureCopy(5, 5, 6);
     wgpu::Buffer source = CreateBuffer(bufferSize, wgpu::BufferUsage::CopySrc);
     wgpu::Texture destination =
-        Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
+        Create2DTexture(16, 16, 1, 5, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
 
-    // Image height is zero (Valid)
-    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {0, 0, 0},
+    // rowsPerImage is zero
+    // TODO(crbug.com/dawn/520): Change to ::Failure.
+    EXPECT_DEPRECATION_WARNING(TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0,
+                                           destination, 0, {0, 0, 0}, {1, 1, 1}));
+    EXPECT_DEPRECATION_WARNING(TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0,
+                                           destination, 0, {0, 0, 0}, {4, 4, 1}));
+
+    // rowsPerImage is undefined
+    TestB2TCopy(utils::Expectation::Success, source, 0, 256, wgpu::kStrideUndefined, destination, 0,
+                {0, 0, 0}, {4, 4, 1});
+    // Fail because depth > 1:
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, wgpu::kStrideUndefined, destination, 0,
+                {0, 0, 0}, {4, 4, 2});
+
+    // rowsPerImage is equal to copy height (Valid)
+    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 4, destination, 0, {0, 0, 0},
                 {4, 4, 1});
+    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 4, destination, 0, {0, 0, 0},
+                {4, 4, 2});
 
-    // Image height is equal to copy height (Valid)
-    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {0, 0, 0},
-                {4, 4, 1});
-
-    // Image height is larger than copy height (Valid)
+    // rowsPerImage is larger than copy height (Valid)
     TestB2TCopy(utils::Expectation::Success, source, 0, 256, 5, destination, 0, {0, 0, 0},
                 {4, 4, 1});
+    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 5, destination, 0, {0, 0, 0},
+                {4, 4, 2});
 
-    // Image height is less than copy height (Invalid)
+    // rowsPerImage is less than copy height (Invalid)
     TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 3, destination, 0, {0, 0, 0},
                 {4, 4, 1});
 }
@@ -580,16 +630,16 @@
         Create2DTexture(16, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
 
     // Correct usage
-    TestB2TCopy(utils::Expectation::Success, source, bufferSize - 4, 256, 0, destination, 0,
+    TestB2TCopy(utils::Expectation::Success, source, bufferSize - 4, 256, 1, destination, 0,
                 {0, 0, 0}, {1, 1, 1});
 
     // Incorrect usages
     {
-        TestB2TCopy(utils::Expectation::Failure, source, bufferSize - 5, 256, 0, destination, 0,
+        TestB2TCopy(utils::Expectation::Failure, source, bufferSize - 5, 256, 1, destination, 0,
                     {0, 0, 0}, {1, 1, 1});
-        TestB2TCopy(utils::Expectation::Failure, source, bufferSize - 6, 256, 0, destination, 0,
+        TestB2TCopy(utils::Expectation::Failure, source, bufferSize - 6, 256, 1, destination, 0,
                     {0, 0, 0}, {1, 1, 1});
-        TestB2TCopy(utils::Expectation::Failure, source, bufferSize - 7, 256, 0, destination, 0,
+        TestB2TCopy(utils::Expectation::Failure, source, bufferSize - 7, 256, 1, destination, 0,
                     {0, 0, 0}, {1, 1, 1});
     }
 }
@@ -601,7 +651,7 @@
     wgpu::Texture destination = Create2DTexture(2, 2, 1, 1, wgpu::TextureFormat::RGBA8Unorm,
                                                 wgpu::TextureUsage::CopyDst, 4);
 
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 2, destination, 0, {0, 0, 0},
                 {2, 2, 1});
 }
 
@@ -663,8 +713,8 @@
             wgpu::Buffer source = CreateBuffer(kInvalidBufferSize, wgpu::BufferUsage::CopySrc);
             wgpu::Texture destination =
                 Create2DTexture(kWidth, kHeight, 1, 1, format, wgpu::TextureUsage::CopyDst);
-            TestB2TCopy(utils::Expectation::Failure, source, 0, kBytesPerRow, 0, destination, 0,
-                        {0, 0, 0}, {kWidth, kHeight, 1});
+            TestB2TCopy(utils::Expectation::Failure, source, 0, kBytesPerRow, kHeight, destination,
+                        0, {0, 0, 0}, {kWidth, kHeight, 1});
         }
     }
 
@@ -679,14 +729,14 @@
             {
                 uint32_t invalidBuffferSize = validBufferSize - 1;
                 wgpu::Buffer source = CreateBuffer(invalidBuffferSize, wgpu::BufferUsage::CopySrc);
-                TestB2TCopy(utils::Expectation::Failure, source, 0, kBytesPerRow, 0, destination, 0,
-                            {0, 0, 0}, {kWidth, kHeight, 1});
+                TestB2TCopy(utils::Expectation::Failure, source, 0, kBytesPerRow, kHeight,
+                            destination, 0, {0, 0, 0}, {kWidth, kHeight, 1});
             }
 
             {
                 wgpu::Buffer source = CreateBuffer(validBufferSize, wgpu::BufferUsage::CopySrc);
-                TestB2TCopy(utils::Expectation::Success, source, 0, kBytesPerRow, 0, destination, 0,
-                            {0, 0, 0}, {kWidth, kHeight, 1});
+                TestB2TCopy(utils::Expectation::Success, source, 0, kBytesPerRow, kHeight,
+                            destination, 0, {0, 0, 0}, {kWidth, kHeight, 1});
             }
         }
     }
@@ -701,19 +751,19 @@
         4, 2, maxMipmapLevel, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopyDst);
 
     // Copy to top level mip map
-    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, maxMipmapLevel - 1,
+    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 1, destination, maxMipmapLevel - 1,
                 {0, 0, 0}, {1, 1, 1});
     // Copy to high level mip map
-    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, maxMipmapLevel - 2,
+    TestB2TCopy(utils::Expectation::Success, source, 0, 256, 1, destination, maxMipmapLevel - 2,
                 {0, 0, 0}, {2, 1, 1});
     // Mip level out of range
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, maxMipmapLevel,
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 1, destination, maxMipmapLevel,
                 {0, 0, 0}, {1, 1, 1});
     // Copy origin out of range
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, maxMipmapLevel - 2,
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 1, destination, maxMipmapLevel - 2,
                 {1, 0, 0}, {2, 1, 1});
     // Copy size out of range
-    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, maxMipmapLevel - 2,
+    TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 2, destination, maxMipmapLevel - 2,
                 {0, 0, 0}, {2, 2, 1});
 }
 
@@ -727,10 +777,10 @@
         wgpu::Texture destination = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::Depth32Float,
                                                     wgpu::TextureUsage::CopyDst);
 
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 16, destination, 0, {0, 0, 0},
                     {16, 16, 1}, wgpu::TextureAspect::All);
 
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 16, destination, 0, {0, 0, 0},
                     {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
     }
 
@@ -760,12 +810,12 @@
         wgpu::Texture destination = Create2DTexture(
             16, 16, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureUsage::CopyDst);
 
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 16, destination, 0, {0, 0, 0},
                     {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
 
         // And that it fails if the buffer is one byte too small
         wgpu::Buffer sourceSmall = CreateBuffer(bufferSize - 1, wgpu::BufferUsage::CopySrc);
-        TestB2TCopy(utils::Expectation::Failure, sourceSmall, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, sourceSmall, 0, 256, 16, destination, 0, {0, 0, 0},
                     {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
     }
 
@@ -777,7 +827,7 @@
         wgpu::Texture destination = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::Depth24Plus,
                                                     wgpu::TextureUsage::CopyDst);
 
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 16, destination, 0, {0, 0, 0},
                     {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
     }
 
@@ -789,7 +839,7 @@
         wgpu::Texture destination = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::RGBA8Uint,
                                                     wgpu::TextureUsage::CopyDst);
 
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 16, destination, 0, {0, 0, 0},
                     {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
     }
 
@@ -802,10 +852,10 @@
             Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8,
                             wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 15, destination, 0, {0, 0, 0},
                     {15, 15, 1}, wgpu::TextureAspect::StencilOnly);
 
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 1, destination, 0, {0, 0, 0},
                     {1, 1, 1}, wgpu::TextureAspect::StencilOnly);
     }
 
@@ -820,14 +870,14 @@
                             wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
         // Whole mip is success
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 1, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 8, destination, 1, {0, 0, 0},
                     {8, 8, 1}, wgpu::TextureAspect::StencilOnly);
 
         // Partial mip fails
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 1, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 7, destination, 1, {0, 0, 0},
                     {7, 7, 1}, wgpu::TextureAspect::StencilOnly);
 
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 1, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 1, destination, 1, {0, 0, 0},
                     {1, 1, 1}, wgpu::TextureAspect::StencilOnly);
     }
 
@@ -842,14 +892,14 @@
                             wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
 
         // Whole mip is success
-        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 1, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 8, destination, 1, {0, 0, 0},
                     {8, 8, 1}, wgpu::TextureAspect::StencilOnly);
 
         // Partial mip fails
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 1, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 7, destination, 1, {0, 0, 0},
                     {7, 7, 1}, wgpu::TextureAspect::StencilOnly);
 
-        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 1, {0, 0, 0},
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 1, destination, 1, {0, 0, 0},
                     {1, 1, 1}, wgpu::TextureAspect::StencilOnly);
     }
 }
@@ -880,46 +930,76 @@
     // Different copies, including some that touch the OOB condition
     {
         // Copy from 4x4 block in corner of first mip.
-        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 4,
                     {4, 4, 1});
         // Copy from 4x4 block in opposite corner of first mip.
-        TestT2BCopy(utils::Expectation::Success, source, 0, {12, 12, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 0, {12, 12, 0}, destination, 0, 256, 4,
                     {4, 4, 1});
         // Copy from 4x4 block in the 4x4 mip.
-        TestT2BCopy(utils::Expectation::Success, source, 2, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 2, {0, 0, 0}, destination, 0, 256, 4,
                     {4, 4, 1});
         // Copy with a buffer offset
         TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, bufferSize - 4,
-                    256, 0, {1, 1, 1});
+                    256, 1, {1, 1, 1});
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, bufferSize - 4,
+                    256, wgpu::kStrideUndefined, {1, 1, 1});
     }
 
     // Copies with a 256-byte aligned bytes per row but unaligned texture region
     {
         // Unaligned region
-        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 4,
                     {3, 4, 1});
         // Unaligned region with texture offset
-        TestT2BCopy(utils::Expectation::Success, source, 0, {5, 7, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 0, {5, 7, 0}, destination, 0, 256, 3,
                     {2, 3, 1});
         // Unaligned region, with buffer offset
-        TestT2BCopy(utils::Expectation::Success, source, 2, {0, 0, 0}, destination, 31 * 4, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 2, {0, 0, 0}, destination, 31 * 4, 256, 3,
                     {3, 3, 1});
     }
 
+    // bytesPerRow is undefined
+    {
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
+                    wgpu::kStrideUndefined, 2, {1, 1, 1});
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
+                    wgpu::kStrideUndefined, 2, {3, 1, 1});
+        // Fail because height or depth is greater than 1:
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
+                    wgpu::kStrideUndefined, 2, {1, 2, 1});
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
+                    wgpu::kStrideUndefined, 2, {1, 1, 2});
+    }
+
     // Empty copies are valid
     {
         // An empty copy
         TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 0, 0,
                     {0, 0, 1});
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
+                    wgpu::kStrideUndefined, 0, {0, 0, 1});
         // An empty copy with depth = 0
         TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 0, 0,
                     {0, 0, 0});
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
+                    wgpu::kStrideUndefined, 0, {0, 0, 0});
         // An empty copy touching the end of the buffer
         TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, bufferSize, 0,
                     0, {0, 0, 1});
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, bufferSize,
+                    wgpu::kStrideUndefined, 0, {0, 0, 1});
         // An empty copy touching the side of the texture
         TestT2BCopy(utils::Expectation::Success, source, 0, {16, 16, 0}, destination, 0, 0, 0,
                     {0, 0, 1});
+        TestT2BCopy(utils::Expectation::Success, source, 0, {16, 16, 0}, destination, 0,
+                    wgpu::kStrideUndefined, 0, {0, 0, 1});
+
+        // An empty copy with depth = 1 and bytesPerRow > 0
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
+                    kTextureBytesPerRowAlignment, 0, {0, 0, 1});
+        // An empty copy with height > 0, depth = 0, bytesPerRow > 0 and rowsPerImage > 0
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
+                    kTextureBytesPerRowAlignment, 3, {0, 1, 0});
     }
 }
 
@@ -959,19 +1039,19 @@
     wgpu::Buffer destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);
 
     // OOB on the texture because x + width overflows
-    TestT2BCopy(utils::Expectation::Failure, source, 0, {13, 12, 0}, destination, 0, 256, 0,
+    TestT2BCopy(utils::Expectation::Failure, source, 0, {13, 12, 0}, destination, 0, 256, 4,
                 {4, 4, 1});
 
     // OOB on the texture because y + width overflows
-    TestT2BCopy(utils::Expectation::Failure, source, 0, {12, 13, 0}, destination, 0, 256, 0,
+    TestT2BCopy(utils::Expectation::Failure, source, 0, {12, 13, 0}, destination, 0, 256, 4,
                 {4, 4, 1});
 
     // OOB on the texture because we overflow a non-zero mip
-    TestT2BCopy(utils::Expectation::Failure, source, 2, {1, 0, 0}, destination, 0, 256, 0,
+    TestT2BCopy(utils::Expectation::Failure, source, 2, {1, 0, 0}, destination, 0, 256, 4,
                 {4, 4, 1});
 
     // OOB on the texture even on an empty copy when we copy from a non-existent mip.
-    TestT2BCopy(utils::Expectation::Failure, source, 5, {0, 0, 0}, destination, 0, 0, 0, {0, 0, 1});
+    TestT2BCopy(utils::Expectation::Failure, source, 5, {0, 0, 0}, destination, 0, 0, 4, {0, 0, 1});
 }
 
 // Test OOB conditions on the buffer
@@ -982,16 +1062,16 @@
     wgpu::Buffer destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);
 
     // OOB on the buffer because we copy too many pixels
-    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 5,
                 {4, 5, 1});
 
     // OOB on the buffer because of the offset
-    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 4, 256, 0,
+    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 4, 256, 4,
                 {4, 4, 1});
 
     // OOB on the buffer because (bytes per row * (height - 1) + width * bytesPerPixel) * depth
     // overflows
-    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 512, 0,
+    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 512, 3,
                 {4, 3, 1});
 
     // Not OOB on the buffer although bytes per row * height overflows
@@ -1002,7 +1082,7 @@
             << "bytes per row * height should overflow buffer";
         wgpu::Buffer destinationBuffer =
             CreateBuffer(destinationBufferSize, wgpu::BufferUsage::CopyDst);
-        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destinationBuffer, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destinationBuffer, 0, 256, 3,
                     {7, 3, 1});
     }
 }
@@ -1029,80 +1109,106 @@
     wgpu::Buffer vertex = CreateBuffer(bufferSize, wgpu::BufferUsage::Vertex);
 
     // Incorrect source usage
-    TestT2BCopy(utils::Expectation::Failure, sampled, 0, {0, 0, 0}, destination, 0, 256, 0,
+    TestT2BCopy(utils::Expectation::Failure, sampled, 0, {0, 0, 0}, destination, 0, 256, 4,
                 {4, 4, 1});
 
     // Incorrect destination usage
-    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, vertex, 0, 256, 0, {4, 4, 1});
+    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, vertex, 0, 256, 4, {4, 4, 1});
 }
 
-TEST_F(CopyCommandTest_T2B, IncorrectBytesPerRow) {
+TEST_F(CopyCommandTest_T2B, BytesPerRowConstraints) {
     uint64_t bufferSize = BufferSizeForTextureCopy(128, 16, 1);
-    wgpu::Texture source = Create2DTexture(128, 16, 5, 1, wgpu::TextureFormat::RGBA8Unorm,
+    wgpu::Texture source = Create2DTexture(128, 16, 5, 5, wgpu::TextureFormat::RGBA8Unorm,
                                            wgpu::TextureUsage::CopySrc);
     wgpu::Buffer destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);
 
     // bytes per row is 0
     {
         // copyHeight > 1
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 0, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 0, 4,
                     {64, 4, 1});
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 0, 4,
+                    {0, 4, 1});
 
         // copyDepth > 1
         TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 0, 1,
                     {64, 1, 4});
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 0, 1,
+                    {0, 1, 4});
 
         // copyHeight = 1 and copyDepth = 1
-        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 0, 0,
-                    {64, 1, 1});
+        // TODO(crbug.com/dawn/520): Change to ::Failure.
+        EXPECT_DEPRECATION_WARNING(TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0},
+                                               destination, 0, 0, 1, {64, 1, 1}));
     }
 
     // bytes per row is not 256-byte aligned
     {
         // copyHeight > 1
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 128, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 128, 4,
                     {4, 4, 1});
 
         // copyHeight = 1 and copyDepth = 1
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 128, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 128, 1,
                     {4, 1, 1});
     }
 
     // bytes per row is less than width * bytesPerPixel
     {
         // copyHeight > 1
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 2,
                     {65, 2, 1});
+        // copyHeight == 0
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+                    {65, 0, 1});
 
         // copyDepth > 1
         TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 1,
                     {65, 1, 2});
+        // copyDepth == 0
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 1,
+                    {65, 1, 0});
 
         // copyHeight = 1 and copyDepth = 1
-        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
-                    {65, 1, 1});
+        // TODO(crbug.com/dawn/520): Change to ::Failure.
+        EXPECT_DEPRECATION_WARNING(TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0},
+                                               destination, 0, 256, 1, {65, 1, 1}));
     }
 }
 
-TEST_F(CopyCommandTest_T2B, ImageHeightConstraint) {
-    uint64_t bufferSize = BufferSizeForTextureCopy(5, 5, 1);
+TEST_F(CopyCommandTest_T2B, RowsPerImageConstraints) {
+    uint64_t bufferSize = BufferSizeForTextureCopy(5, 5, 6);
     wgpu::Texture source =
-        Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopySrc);
+        Create2DTexture(16, 16, 1, 5, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::CopySrc);
     wgpu::Buffer destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);
 
-    // Image height is zero (Valid)
-    TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
-                {4, 4, 1});
+    // rowsPerImage is zero (Valid)
+    // TODO(crbug.com/dawn/520): Change to ::Failure.
+    EXPECT_DEPRECATION_WARNING(TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0},
+                                           destination, 0, 256, 0, {1, 1, 1}));
+    EXPECT_DEPRECATION_WARNING(TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0},
+                                           destination, 0, 256, 0, {4, 4, 1}));
 
-    // Image height is equal to copy height (Valid)
+    // rowsPerImage is undefined
+    TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256,
+                wgpu::kStrideUndefined, {4, 4, 1});
+    // Fail because depth > 1:
+    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256,
+                wgpu::kStrideUndefined, {4, 4, 2});
+
+    // rowsPerImage is equal to copy height (Valid)
     TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 4,
                 {4, 4, 1});
+    TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 4,
+                {4, 4, 2});
 
-    // Image height exceeds copy height (Valid)
+    // rowsPerImage exceeds copy height (Valid)
     TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 5,
                 {4, 4, 1});
+    TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 5,
+                {4, 4, 2});
 
-    // Image height is less than copy height (Invalid)
+    // rowsPerImage is less than copy height (Invalid)
     TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 3,
                 {4, 4, 1});
 }
@@ -1116,15 +1222,15 @@
 
     // Correct usage
     TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, bufferSize - 4, 256,
-                0, {1, 1, 1});
+                1, {1, 1, 1});
 
     // Incorrect usages
     TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, bufferSize - 5, 256,
-                0, {1, 1, 1});
+                1, {1, 1, 1});
     TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, bufferSize - 6, 256,
-                0, {1, 1, 1});
+                1, {1, 1, 1});
     TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, bufferSize - 7, 256,
-                0, {1, 1, 1});
+                1, {1, 1, 1});
 }
 
 // Test multisampled textures cannot be used in T2B copies.
@@ -1134,7 +1240,7 @@
     uint64_t bufferSize = BufferSizeForTextureCopy(16, 16, 1);
     wgpu::Buffer destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);
 
-    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+    TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 2,
                 {2, 2, 1});
 }
 
@@ -1198,7 +1304,7 @@
 
             wgpu::Buffer destination = CreateBuffer(kInvalidBufferSize, wgpu::BufferUsage::CopyDst);
             TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
-                        kBytesPerRow, 0, {kWidth, kHeight, 1});
+                        kBytesPerRow, kHeight, {kWidth, kHeight, 1});
         }
     }
 
@@ -1215,14 +1321,14 @@
                 wgpu::Buffer destination =
                     CreateBuffer(invalidBufferSize, wgpu::BufferUsage::CopyDst);
                 TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
-                            kBytesPerRow, 0, {kWidth, kHeight, 1});
+                            kBytesPerRow, kHeight, {kWidth, kHeight, 1});
             }
 
             {
                 wgpu::Buffer destination =
                     CreateBuffer(validBufferSize, wgpu::BufferUsage::CopyDst);
                 TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
-                            kBytesPerRow, 0, {kWidth, kHeight, 1});
+                            kBytesPerRow, kHeight, {kWidth, kHeight, 1});
             }
         }
     }
@@ -1238,19 +1344,19 @@
 
     // Copy from top level mip map
     TestT2BCopy(utils::Expectation::Success, source, maxMipmapLevel - 1, {0, 0, 0}, destination, 0,
-                256, 0, {1, 1, 1});
+                256, 1, {1, 1, 1});
     // Copy from high level mip map
     TestT2BCopy(utils::Expectation::Success, source, maxMipmapLevel - 2, {0, 0, 0}, destination, 0,
-                256, 0, {2, 1, 1});
+                256, 1, {2, 1, 1});
     // Mip level out of range
     TestT2BCopy(utils::Expectation::Failure, source, maxMipmapLevel, {0, 0, 0}, destination, 0, 256,
-                0, {2, 1, 1});
+                1, {2, 1, 1});
     // Copy origin out of range
     TestT2BCopy(utils::Expectation::Failure, source, maxMipmapLevel - 2, {2, 0, 0}, destination, 0,
-                256, 0, {2, 1, 1});
+                256, 1, {2, 1, 1});
     // Copy size out of range
     TestT2BCopy(utils::Expectation::Failure, source, maxMipmapLevel - 2, {1, 0, 0}, destination, 0,
-                256, 0, {2, 1, 1});
+                256, 1, {2, 1, 1});
 }
 
 // Test copy from only the depth aspect of a texture
@@ -1262,11 +1368,11 @@
                                                wgpu::TextureUsage::CopySrc);
 
         // Test "all" of a depth texture which is only the depth aspect.
-        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 16,
                     {16, 16, 1}, wgpu::TextureAspect::All);
 
         // Test it is valid to copy the depth aspect of a depth texture
-        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 16,
                     {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
     }
     {
@@ -1274,7 +1380,7 @@
                                                wgpu::TextureUsage::CopySrc);
 
         // Test it is invalid to copy from the depth aspect of depth24plus
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 16,
                     {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
     }
     {
@@ -1282,7 +1388,7 @@
             16, 16, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureUsage::CopySrc);
 
         // Test it is invalid to copy from the depth aspect of depth24plus-stencil8
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 16,
                     {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
     }
     {
@@ -1290,7 +1396,7 @@
                                                wgpu::TextureUsage::CopySrc);
 
         // Test it is invalid to copy from the depth aspect of a color texture
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 16,
                     {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
     }
 }
@@ -1304,12 +1410,12 @@
             16, 16, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureUsage::CopySrc);
 
         // Test it is valid to copy from the stencil aspect of a depth24plus-stencil8 texture
-        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 16,
                     {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
 
         // Test it is invalid if the buffer is too small
         wgpu::Buffer destinationSmall = CreateBuffer(bufferSize - 1, wgpu::BufferUsage::CopyDst);
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destinationSmall, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destinationSmall, 0, 256, 16,
                     {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
     }
     {
@@ -1317,7 +1423,7 @@
             Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::R8Uint, wgpu::TextureUsage::CopySrc);
 
         // Test it is invalid to copy from the stencil aspect of a color texture
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 16,
                     {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
     }
     {
@@ -1325,7 +1431,7 @@
                                                wgpu::TextureUsage::CopySrc);
 
         // Test it is invalid to copy from the stencil aspect of a depth-only texture
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 16,
                     {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
     }
 
@@ -1335,10 +1441,10 @@
         wgpu::Texture source = Create2DTexture(
             16, 16, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureUsage::CopySrc);
 
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 15,
                     {15, 15, 1}, wgpu::TextureAspect::StencilOnly);
 
-        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 1,
                     {1, 1, 1}, wgpu::TextureAspect::StencilOnly);
     }
 
@@ -1349,14 +1455,14 @@
             16, 16, 2, 1, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureUsage::CopySrc);
 
         // Whole mip is success
-        TestT2BCopy(utils::Expectation::Success, source, 1, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 1, {0, 0, 0}, destination, 0, 256, 8,
                     {8, 8, 1}, wgpu::TextureAspect::StencilOnly);
 
         // Partial mip fails
-        TestT2BCopy(utils::Expectation::Failure, source, 1, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 1, {0, 0, 0}, destination, 0, 256, 7,
                     {7, 7, 1}, wgpu::TextureAspect::StencilOnly);
 
-        TestT2BCopy(utils::Expectation::Failure, source, 1, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 1, {0, 0, 0}, destination, 0, 256, 1,
                     {1, 1, 1}, wgpu::TextureAspect::StencilOnly);
     }
 
@@ -1367,14 +1473,14 @@
             17, 17, 2, 1, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureUsage::CopySrc);
 
         // Whole mip is success
-        TestT2BCopy(utils::Expectation::Success, source, 1, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Success, source, 1, {0, 0, 0}, destination, 0, 256, 8,
                     {8, 8, 1}, wgpu::TextureAspect::StencilOnly);
 
         // Partial mip fails
-        TestT2BCopy(utils::Expectation::Failure, source, 1, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 1, {0, 0, 0}, destination, 0, 256, 7,
                     {7, 7, 1}, wgpu::TextureAspect::StencilOnly);
 
-        TestT2BCopy(utils::Expectation::Failure, source, 1, {0, 0, 0}, destination, 0, 256, 0,
+        TestT2BCopy(utils::Expectation::Failure, source, 1, {0, 0, 0}, destination, 0, 256, 1,
                     {1, 1, 1}, wgpu::TextureAspect::StencilOnly);
     }
 }
diff --git a/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
index 8cb7bc5..b3c339d 100644
--- a/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
+++ b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
@@ -99,38 +99,53 @@
         // Different copies, including some that touch the OOB condition
         {
             // Copy 4x4 block in corner of first mip.
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1});
+            TestWriteTexture(dataSize, 0, 256, 4, destination, 0, {0, 0, 0}, {4, 4, 1});
             // Copy 4x4 block in opposite corner of first mip.
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {12, 12, 0}, {4, 4, 1});
+            TestWriteTexture(dataSize, 0, 256, 4, destination, 0, {12, 12, 0}, {4, 4, 1});
             // Copy 4x4 block in the 4x4 mip.
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 2, {0, 0, 0}, {4, 4, 1});
+            TestWriteTexture(dataSize, 0, 256, 4, destination, 2, {0, 0, 0}, {4, 4, 1});
             // Copy with a data offset
-            TestWriteTexture(dataSize, dataSize - 4, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1});
+            TestWriteTexture(dataSize, dataSize - 4, 256, 1, destination, 0, {0, 0, 0}, {1, 1, 1});
+            TestWriteTexture(dataSize, dataSize - 4, 256, wgpu::kStrideUndefined, destination, 0,
+                             {0, 0, 0}, {1, 1, 1});
         }
 
         // Copies with a 256-byte aligned bytes per row but unaligned texture region
         {
             // Unaligned region
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {3, 4, 1});
+            TestWriteTexture(dataSize, 0, 256, 4, destination, 0, {0, 0, 0}, {3, 4, 1});
             // Unaligned region with texture offset
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {5, 7, 0}, {2, 3, 1});
+            TestWriteTexture(dataSize, 0, 256, 3, destination, 0, {5, 7, 0}, {2, 3, 1});
             // Unaligned region, with data offset
-            TestWriteTexture(dataSize, 31 * 4, 256, 0, destination, 0, {0, 0, 0}, {3, 3, 1});
+            TestWriteTexture(dataSize, 31 * 4, 256, 3, destination, 0, {0, 0, 0}, {3, 3, 1});
         }
 
         // Empty copies are valid
         {
             // An empty copy
             TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 1});
+            TestWriteTexture(dataSize, 0, 0, wgpu::kStrideUndefined, destination, 0, {0, 0, 0},
+                             {0, 0, 1});
             // An empty copy with depth = 0
             TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 0});
+            TestWriteTexture(dataSize, 0, 0, wgpu::kStrideUndefined, destination, 0, {0, 0, 0},
+                             {0, 0, 0});
             // An empty copy touching the end of the data
             TestWriteTexture(dataSize, dataSize, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 1});
+            TestWriteTexture(dataSize, dataSize, 0, wgpu::kStrideUndefined, destination, 0,
+                             {0, 0, 0}, {0, 0, 1});
             // An empty copy touching the side of the texture
             TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {16, 16, 0}, {0, 0, 1});
+            TestWriteTexture(dataSize, 0, 0, wgpu::kStrideUndefined, destination, 0, {16, 16, 0},
+                             {0, 0, 1});
             // An empty copy with depth = 1 and bytesPerRow > 0
             TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {0, 0, 1});
+            TestWriteTexture(dataSize, 0, 256, wgpu::kStrideUndefined, destination, 0, {0, 0, 0},
+                             {0, 0, 1});
             // An empty copy with height > 0, depth = 0, bytesPerRow > 0 and rowsPerImage > 0
+            TestWriteTexture(dataSize, 0, 256, wgpu::kStrideUndefined, destination, 0, {0, 0, 0},
+                             {0, 1, 0});
+            TestWriteTexture(dataSize, 0, 256, 1, destination, 0, {0, 0, 0}, {0, 1, 0});
             TestWriteTexture(dataSize, 0, 256, 16, destination, 0, {0, 0, 0}, {0, 1, 0});
         }
     }
@@ -144,15 +159,15 @@
 
         // OOB on the data because we copy too many pixels
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 5, 1}));
+            TestWriteTexture(dataSize, 0, 256, 5, destination, 0, {0, 0, 0}, {4, 5, 1}));
 
         // OOB on the data because of the offset
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, 4, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1}));
+            TestWriteTexture(dataSize, 4, 256, 4, destination, 0, {0, 0, 0}, {4, 4, 1}));
 
         // OOB on the data because utils::RequiredBytesInCopy overflows
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, 0, 512, 0, destination, 0, {0, 0, 0}, {4, 3, 1}));
+            TestWriteTexture(dataSize, 0, 512, 3, destination, 0, {0, 0, 0}, {4, 3, 1}));
 
         // Not OOB on the data although bytes per row * height overflows
         // but utils::RequiredBytesInCopy * depth does not overflow
@@ -161,7 +176,7 @@
                 utils::RequiredBytesInCopy(256, 0, {7, 3, 1}, wgpu::TextureFormat::RGBA8Unorm);
             ASSERT_TRUE(256 * 3 > sourceDataSize) << "bytes per row * height should overflow data";
 
-            TestWriteTexture(sourceDataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {7, 3, 1});
+            TestWriteTexture(sourceDataSize, 0, 256, 3, destination, 0, {0, 0, 0}, {7, 3, 1});
         }
     }
 
@@ -174,15 +189,15 @@
 
         // OOB on the texture because x + width overflows
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {13, 12, 0}, {4, 4, 1}));
+            TestWriteTexture(dataSize, 0, 256, 4, destination, 0, {13, 12, 0}, {4, 4, 1}));
 
         // OOB on the texture because y + width overflows
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {12, 13, 0}, {4, 4, 1}));
+            TestWriteTexture(dataSize, 0, 256, 4, destination, 0, {12, 13, 0}, {4, 4, 1}));
 
         // OOB on the texture because we overflow a non-zero mip
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 2, {1, 0, 0}, {4, 4, 1}));
+            TestWriteTexture(dataSize, 0, 256, 4, destination, 2, {1, 0, 0}, {4, 4, 1}));
 
         // OOB on the texture even on an empty copy when we copy to a non-existent mip.
         ASSERT_DEVICE_ERROR(
@@ -214,68 +229,91 @@
 
         // Incorrect destination usage
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, 0, 256, 0, sampled, 0, {0, 0, 0}, {4, 4, 1}));
+            TestWriteTexture(dataSize, 0, 256, 4, sampled, 0, {0, 0, 0}, {4, 4, 1}));
     }
 
     // Test incorrect values of bytesPerRow and that values not divisible by 256 are allowed.
-    TEST_F(QueueWriteTextureValidationTest, BytesPerRowLimitations) {
-        wgpu::Texture destination = Create2DTexture({3, 7, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
+    TEST_F(QueueWriteTextureValidationTest, BytesPerRowConstraints) {
+        wgpu::Texture destination = Create2DTexture({3, 7, 2}, 1, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst);
 
-        // bytesPerRow = 0
+        // bytesPerRow = 0 or wgpu::kStrideUndefined
         {
             // copyHeight > 1
             ASSERT_DEVICE_ERROR(
-                TestWriteTexture(128, 0, 0, 0, destination, 0, {0, 0, 0}, {3, 7, 1}));
+                TestWriteTexture(128, 0, 0, 7, destination, 0, {0, 0, 0}, {3, 7, 1}));
+            TestWriteTexture(128, 0, 0, 7, destination, 0, {0, 0, 0}, {0, 7, 1});
+            ASSERT_DEVICE_ERROR(TestWriteTexture(128, 0, wgpu::kStrideUndefined, 7, destination, 0,
+                                                 {0, 0, 0}, {0, 7, 1}));
 
             // copyDepth > 1
             ASSERT_DEVICE_ERROR(
                 TestWriteTexture(128, 0, 0, 1, destination, 0, {0, 0, 0}, {3, 1, 2}));
+            TestWriteTexture(128, 0, 0, 1, destination, 0, {0, 0, 0}, {0, 1, 2});
+            ASSERT_DEVICE_ERROR(TestWriteTexture(128, 0, wgpu::kStrideUndefined, 1, destination, 0,
+                                                 {0, 0, 0}, {0, 1, 2}));
 
             // copyHeight = 1 and copyDepth = 1
-            TestWriteTexture(128, 0, 0, 0, destination, 0, {0, 0, 0}, {3, 1, 1});
+            // TODO(crbug.com/dawn/520): Change to ASSERT_DEVICE_ERROR.
+            EXPECT_DEPRECATION_WARNING(
+                TestWriteTexture(128, 0, 0, 1, destination, 0, {0, 0, 0}, {3, 1, 1}));
+            TestWriteTexture(128, 0, wgpu::kStrideUndefined, 1, destination, 0, {0, 0, 0},
+                             {3, 1, 1});
         }
 
         // bytesPerRow = 11 is invalid since a row takes 12 bytes.
         {
             // copyHeight > 1
             ASSERT_DEVICE_ERROR(
-                TestWriteTexture(128, 0, 11, 0, destination, 0, {0, 0, 0}, {3, 7, 1}));
+                TestWriteTexture(128, 0, 11, 7, destination, 0, {0, 0, 0}, {3, 7, 1}));
+            // copyHeight == 0
+            ASSERT_DEVICE_ERROR(
+                TestWriteTexture(128, 0, 11, 0, destination, 0, {0, 0, 0}, {3, 0, 1}));
 
             // copyDepth > 1
             ASSERT_DEVICE_ERROR(
                 TestWriteTexture(128, 0, 11, 1, destination, 0, {0, 0, 0}, {3, 1, 2}));
+            // copyDepth == 0
+            ASSERT_DEVICE_ERROR(
+                TestWriteTexture(128, 0, 11, 1, destination, 0, {0, 0, 0}, {3, 1, 0}));
 
             // copyHeight = 1 and copyDepth = 1
-            TestWriteTexture(128, 0, 11, 0, destination, 0, {0, 0, 0}, {3, 1, 1});
+            // TODO(crbug.com/dawn/520): Change to ASSERT_DEVICE_ERROR. bytesPerRow used to be only
+            // validated if height > 1 || depth > 1.
+            EXPECT_DEPRECATION_WARNING(
+                TestWriteTexture(128, 0, 11, 1, destination, 0, {0, 0, 0}, {3, 1, 1}));
         }
 
         // bytesPerRow = 12 is valid since a row takes 12 bytes.
-        TestWriteTexture(128, 0, 12, 0, destination, 0, {0, 0, 0}, {3, 7, 1});
+        TestWriteTexture(128, 0, 12, 7, destination, 0, {0, 0, 0}, {3, 7, 1});
 
         // bytesPerRow = 13 is valid since a row takes 12 bytes.
-        TestWriteTexture(128, 0, 13, 0, destination, 0, {0, 0, 0}, {3, 7, 1});
+        TestWriteTexture(128, 0, 13, 7, destination, 0, {0, 0, 0}, {3, 7, 1});
     }
 
     // Test that if rowsPerImage is greater than 0, it must be at least copy height.
-    TEST_F(QueueWriteTextureValidationTest, ImageHeightConstraint) {
+    TEST_F(QueueWriteTextureValidationTest, RowsPerImageConstraints) {
         uint64_t dataSize =
-            utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
-        wgpu::Texture destination = Create2DTexture({16, 16, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
+            utils::RequiredBytesInCopy(256, 5, {4, 4, 2}, wgpu::TextureFormat::RGBA8Unorm);
+        wgpu::Texture destination = Create2DTexture({16, 16, 2}, 1, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst);
 
-        // Image height is zero (Valid)
-        TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1});
+        // rowsPerImage is wgpu::kStrideUndefined
+        TestWriteTexture(dataSize, 0, 256, wgpu::kStrideUndefined, destination, 0, {0, 0, 0},
+                         {4, 4, 1});
 
-        // Image height is equal to copy height (Valid)
+        // rowsPerImage is equal to copy height (Valid)
         TestWriteTexture(dataSize, 0, 256, 4, destination, 0, {0, 0, 0}, {4, 4, 1});
 
-        // Image height is larger than copy height (Valid)
+        // rowsPerImage is larger than copy height (Valid)
         TestWriteTexture(dataSize, 0, 256, 5, destination, 0, {0, 0, 0}, {4, 4, 1});
+        TestWriteTexture(dataSize, 0, 256, 5, destination, 0, {0, 0, 0}, {4, 4, 2});
 
-        // Image height is less than copy height (Invalid)
+        // rowsPerImage is less than copy height (Invalid)
         ASSERT_DEVICE_ERROR(
             TestWriteTexture(dataSize, 0, 256, 3, destination, 0, {0, 0, 0}, {4, 4, 1}));
+        EXPECT_DEPRECATION_WARNING(
+            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1}));
     }
 
     // Test WriteTexture with data offset
@@ -286,12 +324,12 @@
                                                     wgpu::TextureUsage::CopyDst);
 
         // Offset aligned
-        TestWriteTexture(dataSize, dataSize - 4, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1});
+        TestWriteTexture(dataSize, dataSize - 4, 256, 1, destination, 0, {0, 0, 0}, {1, 1, 1});
         // Offset not aligned
-        TestWriteTexture(dataSize, dataSize - 5, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1});
+        TestWriteTexture(dataSize, dataSize - 5, 256, 1, destination, 0, {0, 0, 0}, {1, 1, 1});
         // Offset+size too large
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, dataSize - 3, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1}));
+            TestWriteTexture(dataSize, dataSize - 3, 256, 1, destination, 0, {0, 0, 0}, {1, 1, 1}));
     }
 
     // Test multisampled textures can be used in WriteTexture.
@@ -302,7 +340,7 @@
                                                     wgpu::TextureUsage::CopyDst, 4);
 
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {2, 2, 1}));
+            TestWriteTexture(dataSize, 0, 256, 2, destination, 0, {0, 0, 0}, {2, 2, 1}));
     }
 
     // Test that WriteTexture cannot be run with a destroyed texture.
@@ -366,7 +404,7 @@
             for (wgpu::TextureFormat format : kFormats) {
                 wgpu::Texture destination =
                     Create2DTexture({kWidth, kHeight, 1}, 1, format, wgpu::TextureUsage::CopyDst);
-                ASSERT_DEVICE_ERROR(TestWriteTexture(kInvalidDataSize, 0, kBytesPerRow, 0,
+                ASSERT_DEVICE_ERROR(TestWriteTexture(kInvalidDataSize, 0, kBytesPerRow, kHeight,
                                                      destination, 0, {0, 0, 0},
                                                      {kWidth, kHeight, 1}));
             }
@@ -383,14 +421,14 @@
                 // data size in this test.
                 {
                     uint32_t invalidDataSize = validDataSize - 1;
-                    ASSERT_DEVICE_ERROR(TestWriteTexture(invalidDataSize, 0, kBytesPerRow, 0,
+                    ASSERT_DEVICE_ERROR(TestWriteTexture(invalidDataSize, 0, kBytesPerRow, kHeight,
                                                          destination, 0, {0, 0, 0},
                                                          {kWidth, kHeight, 1}));
                 }
 
                 {
-                    TestWriteTexture(validDataSize, 0, kBytesPerRow, 0, destination, 0, {0, 0, 0},
-                                     {kWidth, kHeight, 1});
+                    TestWriteTexture(validDataSize, 0, kBytesPerRow, kHeight, destination, 0,
+                                     {0, 0, 0}, {kWidth, kHeight, 1});
                 }
             }
         }
@@ -406,19 +444,19 @@
                             wgpu::TextureUsage::CopyDst);
 
         // Copy to top level mip map
-        TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 1, {0, 0, 0},
+        TestWriteTexture(dataSize, 0, 256, 1, destination, maxMipmapLevel - 1, {0, 0, 0},
                          {1, 1, 1});
         // Copy to high level mip map
-        TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 2, {0, 0, 0},
+        TestWriteTexture(dataSize, 0, 256, 1, destination, maxMipmapLevel - 2, {0, 0, 0},
                          {2, 1, 1});
         // Mip level out of range
-        ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel,
+        ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 1, destination, maxMipmapLevel,
                                              {0, 0, 0}, {1, 1, 1}));
         // Copy origin out of range
-        ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 2,
+        ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 1, destination, maxMipmapLevel - 2,
                                              {1, 0, 0}, {2, 1, 1}));
         // Copy size out of range
-        ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 2,
+        ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 2, destination, maxMipmapLevel - 2,
                                              {0, 0, 0}, {2, 2, 1}));
     }
 
@@ -460,10 +498,10 @@
             wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
                 {4, 4, 1}, 1, wgpu::TextureFormat::Depth32Float, wgpu::TextureUsage::CopyDst);
 
-            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 4, destination, 0,
                                                  {0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All));
 
-            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 4, destination, 0,
                                                  {0, 0, 0}, {4, 4, 1},
                                                  wgpu::TextureAspect::DepthOnly));
         }
@@ -473,10 +511,10 @@
             wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
                 {4, 4, 1}, 1, wgpu::TextureFormat::Depth24Plus, wgpu::TextureUsage::CopyDst);
 
-            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 4, destination, 0,
                                                  {0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All));
 
-            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 4, destination, 0,
                                                  {0, 0, 0}, {4, 4, 1},
                                                  wgpu::TextureAspect::DepthOnly));
         }
@@ -494,16 +532,16 @@
                 {4, 4, 1}, 1, wgpu::TextureFormat::Depth24PlusStencil8,
                 wgpu::TextureUsage::CopyDst);
 
-            TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0, {0, 0, 0}, {4, 4, 1},
-                             wgpu::TextureAspect::StencilOnly);
+            TestWriteTexture(dataSize, 0, bytesPerRow, wgpu::kStrideUndefined, destination, 0,
+                             {0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::StencilOnly);
 
             // And that it fails if the buffer is one byte too small
-            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize - 1, 0, bytesPerRow, 0, destination, 0,
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize - 1, 0, bytesPerRow, 4, destination, 0,
                                                  {0, 0, 0}, {4, 4, 1},
                                                  wgpu::TextureAspect::StencilOnly));
 
             // It is invalid to write just part of the subresource size
-            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 3, destination, 0,
                                                  {0, 0, 0}, {3, 3, 1},
                                                  wgpu::TextureAspect::StencilOnly));
         }
@@ -513,7 +551,7 @@
             wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
                 {4, 4, 1}, 1, wgpu::TextureFormat::Depth24Plus, wgpu::TextureUsage::CopyDst);
 
-            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 4, destination, 0,
                                                  {0, 0, 0}, {4, 4, 1},
                                                  wgpu::TextureAspect::StencilOnly));
         }
@@ -608,14 +646,14 @@
             // Valid usage of bytesPerRow in WriteTexture with compressed texture formats.
             {
                 constexpr uint32_t kValidBytesPerRow = 20;
-                TestWriteTexture(512, 0, kValidBytesPerRow, 0, texture, 0, {0, 0, 0}, {4, 4, 1});
+                TestWriteTexture(512, 0, kValidBytesPerRow, 4, texture, 0, {0, 0, 0}, {4, 4, 1});
             }
 
             // Valid bytesPerRow.
             // Note that image width is not a multiple of blockWidth.
             {
                 constexpr uint32_t kValidBytesPerRow = 17;
-                TestWriteTexture(512, 0, kValidBytesPerRow, 0, texture, 0, {0, 0, 0}, {4, 4, 1});
+                TestWriteTexture(512, 0, kValidBytesPerRow, 4, texture, 0, {0, 0, 0}, {4, 4, 1});
             }
         }
     }
diff --git a/src/tests/white_box/VulkanImageWrappingTestsDmaBuf.cpp b/src/tests/white_box/VulkanImageWrappingTestsDmaBuf.cpp
index 529c86e..6f68696 100644
--- a/src/tests/white_box/VulkanImageWrappingTestsDmaBuf.cpp
+++ b/src/tests/white_box/VulkanImageWrappingTestsDmaBuf.cpp
@@ -533,7 +533,7 @@
         // Copy |deviceWrappedTexture| into |copyDstBuffer|
         wgpu::TextureCopyView copySrc =
             utils::CreateTextureCopyView(deviceWrappedTexture, 0, {0, 0, 0});
-        wgpu::BufferCopyView copyDst = utils::CreateBufferCopyView(copyDstBuffer, 0, 256, 0);
+        wgpu::BufferCopyView copyDst = utils::CreateBufferCopyView(copyDstBuffer, 0, 256);
 
         wgpu::Extent3D copySize = {1, 1, 1};
 
@@ -585,7 +585,7 @@
             utils::CreateBufferFromData(secondDevice, wgpu::BufferUsage::CopySrc, {0x04030201});
 
         // Copy |copySrcBuffer| into |secondDeviceWrappedTexture|
-        wgpu::BufferCopyView copySrc = utils::CreateBufferCopyView(copySrcBuffer, 0, 256, 0);
+        wgpu::BufferCopyView copySrc = utils::CreateBufferCopyView(copySrcBuffer, 0, 256);
         wgpu::TextureCopyView copyDst =
             utils::CreateTextureCopyView(secondDeviceWrappedTexture, 0, {0, 0, 0});
 
@@ -817,7 +817,7 @@
             wgpu::Buffer copySrcBuffer = utils::CreateBufferFromData(
                 secondDevice, data.data(), data.size(), wgpu::BufferUsage::CopySrc);
             wgpu::BufferCopyView copySrc =
-                utils::CreateBufferCopyView(copySrcBuffer, 0, bytesPerRow, 0);
+                utils::CreateBufferCopyView(copySrcBuffer, 0, bytesPerRow);
             wgpu::TextureCopyView copyDst =
                 utils::CreateTextureCopyView(wrappedTexture, 0, {0, 0, 0});
             wgpu::Extent3D copySize = {width, height, 1};
@@ -846,7 +846,7 @@
             wgpu::TextureCopyView copySrc =
                 utils::CreateTextureCopyView(nextWrappedTexture, 0, {0, 0, 0});
             wgpu::BufferCopyView copyDst =
-                utils::CreateBufferCopyView(copyDstBuffer, 0, bytesPerRow, 0);
+                utils::CreateBufferCopyView(copyDstBuffer, 0, bytesPerRow);
 
             wgpu::Extent3D copySize = {width, height, 1};
 
diff --git a/src/tests/white_box/VulkanImageWrappingTestsOpaqueFD.cpp b/src/tests/white_box/VulkanImageWrappingTestsOpaqueFD.cpp
index a01a863..5eb1eb6 100644
--- a/src/tests/white_box/VulkanImageWrappingTestsOpaqueFD.cpp
+++ b/src/tests/white_box/VulkanImageWrappingTestsOpaqueFD.cpp
@@ -667,7 +667,7 @@
         // Copy |deviceWrappedTexture| into |copyDstBuffer|
         wgpu::TextureCopyView copySrc =
             utils::CreateTextureCopyView(deviceWrappedTexture, 0, {0, 0, 0});
-        wgpu::BufferCopyView copyDst = utils::CreateBufferCopyView(copyDstBuffer, 0, 256, 0);
+        wgpu::BufferCopyView copyDst = utils::CreateBufferCopyView(copyDstBuffer, 0, 256);
 
         wgpu::Extent3D copySize = {1, 1, 1};
 
@@ -721,7 +721,7 @@
             utils::CreateBufferFromData(secondDevice, wgpu::BufferUsage::CopySrc, {0x04030201});
 
         // Copy |copySrcBuffer| into |secondDeviceWrappedTexture|
-        wgpu::BufferCopyView copySrc = utils::CreateBufferCopyView(copySrcBuffer, 0, 256, 0);
+        wgpu::BufferCopyView copySrc = utils::CreateBufferCopyView(copySrcBuffer, 0, 256);
         wgpu::TextureCopyView copyDst =
             utils::CreateTextureCopyView(secondDeviceWrappedTexture, 0, {0, 0, 0});
 
@@ -980,7 +980,7 @@
             wgpu::Buffer copySrcBuffer = utils::CreateBufferFromData(
                 secondDevice, data.data(), data.size(), wgpu::BufferUsage::CopySrc);
             wgpu::BufferCopyView copySrc =
-                utils::CreateBufferCopyView(copySrcBuffer, 0, bytesPerRow, 0);
+                utils::CreateBufferCopyView(copySrcBuffer, 0, bytesPerRow);
             wgpu::TextureCopyView copyDst =
                 utils::CreateTextureCopyView(wrappedTexture, 0, {0, 0, 0});
             wgpu::Extent3D copySize = {width, height, 1};
@@ -1012,7 +1012,7 @@
             wgpu::TextureCopyView copySrc =
                 utils::CreateTextureCopyView(nextWrappedTexture, 0, {0, 0, 0});
             wgpu::BufferCopyView copyDst =
-                utils::CreateBufferCopyView(copyDstBuffer, 0, bytesPerRow, 0);
+                utils::CreateBufferCopyView(copyDstBuffer, 0, bytesPerRow);
 
             wgpu::Extent3D copySize = {width, height, 1};
 
diff --git a/src/utils/TestUtils.cpp b/src/utils/TestUtils.cpp
index ae36ecc..e5487d3 100644
--- a/src/utils/TestUtils.cpp
+++ b/src/utils/TestUtils.cpp
@@ -44,8 +44,12 @@
 
         layout.bytesPerRow = GetMinimumBytesPerRow(format, layout.mipSize.width);
 
-        uint32_t appliedRowsPerImage = rowsPerImage > 0 ? rowsPerImage : layout.mipSize.height;
-        layout.bytesPerImage = layout.bytesPerRow * appliedRowsPerImage;
+        if (rowsPerImage == wgpu::kStrideUndefined) {
+            rowsPerImage = layout.mipSize.height;
+        }
+        layout.rowsPerImage = rowsPerImage;
+
+        layout.bytesPerImage = layout.bytesPerRow * rowsPerImage;
 
         // TODO(kainino@chromium.org): Remove this intermediate variable.
         // It is currently needed because of an issue in the D3D12 copy splitter
@@ -54,9 +58,9 @@
         // the actual height.
         wgpu::Extent3D mipSizeWithHeightWorkaround = layout.mipSize;
         mipSizeWithHeightWorkaround.height =
-            appliedRowsPerImage * utils::GetTextureFormatBlockHeight(format);
+            rowsPerImage * utils::GetTextureFormatBlockHeight(format);
 
-        layout.byteLength = RequiredBytesInCopy(layout.bytesPerRow, appliedRowsPerImage,
+        layout.byteLength = RequiredBytesInCopy(layout.bytesPerRow, rowsPerImage,
                                                 mipSizeWithHeightWorkaround, format);
 
         const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(format);
@@ -120,7 +124,8 @@
         wgpu::Texture texture = device.CreateTexture(&descriptor);
 
         wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(texture, 0, {0, 0, 0});
-        wgpu::TextureDataLayout textureDataLayout = utils::CreateTextureDataLayout(0, 0, 0);
+        wgpu::TextureDataLayout textureDataLayout =
+            utils::CreateTextureDataLayout(0, wgpu::kStrideUndefined);
         wgpu::Extent3D copyExtent = {1, 1, 1};
 
         // WriteTexture with exactly 1 byte of data.
diff --git a/src/utils/TestUtils.h b/src/utils/TestUtils.h
index 1b2e0e0..d4ecb57 100644
--- a/src/utils/TestUtils.h
+++ b/src/utils/TestUtils.h
@@ -23,6 +23,7 @@
         uint64_t byteLength;
         uint64_t texelBlockCount;
         uint32_t bytesPerRow;
+        uint32_t rowsPerImage;
         uint32_t texelBlocksPerRow;
         uint32_t bytesPerImage;
         uint32_t texelBlocksPerImage;
@@ -34,7 +35,7 @@
         wgpu::TextureFormat format,
         wgpu::Extent3D textureSizeAtLevel0,
         uint32_t mipmapLevel,
-        uint32_t rowsPerImage);
+        uint32_t rowsPerImage = wgpu::kStrideUndefined);
 
     uint64_t RequiredBytesInCopy(uint64_t bytesPerRow,
                                  uint64_t rowsPerImage,
diff --git a/src/utils/WGPUHelpers.h b/src/utils/WGPUHelpers.h
index b77b6a4..16d2be4 100644
--- a/src/utils/WGPUHelpers.h
+++ b/src/utils/WGPUHelpers.h
@@ -53,7 +53,7 @@
     wgpu::BufferCopyView CreateBufferCopyView(wgpu::Buffer buffer,
                                               uint64_t offset,
                                               uint32_t bytesPerRow,
-                                              uint32_t rowsPerImage);
+                                              uint32_t rowsPerImage = wgpu::kStrideUndefined);
     wgpu::TextureCopyView CreateTextureCopyView(
         wgpu::Texture texture,
         uint32_t level,
@@ -61,7 +61,7 @@
         wgpu::TextureAspect aspect = wgpu::TextureAspect::All);
     wgpu::TextureDataLayout CreateTextureDataLayout(uint64_t offset,
                                                     uint32_t bytesPerRow,
-                                                    uint32_t rowsPerImage);
+                                                    uint32_t rowsPerImage = wgpu::kStrideUndefined);
 
     struct ComboRenderPassDescriptor : public wgpu::RenderPassDescriptor {
       public: