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/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;