CopyTextureForBrowser: Support flipY option

This CL enable CopyTextureForBrowser to accept options. The first
supported option is flipY, which can be implemented through scale and
offset uniforms.

BUG=dawn:465

Change-Id: Ia90153ee63a50e0e40beb1c13c63764d19a0b809
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/34402
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/dawn.json b/dawn.json
index 34153dc..e0f2179 100644
--- a/dawn.json
+++ b/dawn.json
@@ -507,6 +507,13 @@
             {"name": "compute stage", "type": "programmable stage descriptor"}
         ]
     },
+    "copy texture for browser options": {
+        "category": "structure",
+        "extensible": true,
+        "members": [
+            {"name": "flipY", "type": "bool", "default": "false"}
+        ]
+    },
     "create ready compute pipeline callback": {
         "category": "callback",
         "args": [
@@ -1044,10 +1051,12 @@
             },
             {
                 "name": "copy texture for browser",
+                "extensible": true,
                 "args": [
                     {"name": "source", "type": "texture copy view", "annotation": "const*"},
                     {"name": "destination", "type": "texture copy view", "annotation": "const*"},
-                    {"name": "copy size", "type": "extent 3D", "annotation": "const*"}
+                    {"name": "copy size", "type": "extent 3D", "annotation": "const*"},
+                    {"name": "options", "type": "copy texture for browser options", "annotation": "const*"}
                 ]
             }
         ]
diff --git a/src/dawn_native/CopyTextureForBrowserHelper.cpp b/src/dawn_native/CopyTextureForBrowserHelper.cpp
index 24ba6f1..0662575 100644
--- a/src/dawn_native/CopyTextureForBrowserHelper.cpp
+++ b/src/dawn_native/CopyTextureForBrowserHelper.cpp
@@ -93,6 +93,16 @@
             return {};
         }
 
+        MaybeError ValidateCopyTextureForBrowserOptions(
+            const CopyTextureForBrowserOptions* options) {
+            if (options->nextInChain != nullptr) {
+                return DAWN_VALIDATION_ERROR(
+                    "CopyTextureForBrowserOptions: nextInChain must be nullptr");
+            }
+
+            return {};
+        }
+
         RenderPipelineBase* GetOrCreateCopyTextureForBrowserPipeline(DeviceBase* device) {
             InternalPipelineStore* store = device->GetInternalPipelineStore();
 
@@ -162,7 +172,8 @@
     MaybeError ValidateCopyTextureForBrowser(DeviceBase* device,
                                              const TextureCopyView* source,
                                              const TextureCopyView* destination,
-                                             const Extent3D* copySize) {
+                                             const Extent3D* copySize,
+                                             const CopyTextureForBrowserOptions* options) {
         DAWN_TRY(device->ValidateObject(source->texture));
         DAWN_TRY(device->ValidateObject(destination->texture));
 
@@ -180,6 +191,8 @@
         DAWN_TRY(ValidateCopyTextureFormatConversion(source->texture->GetFormat().format,
                                                      destination->texture->GetFormat().format));
 
+        DAWN_TRY(ValidateCopyTextureForBrowserOptions(options));
+
         // TODO(shaobo.yan@intel.com): Support the simplest case for now that source and destination
         // texture has the same size and do full texture blit. Will address sub texture blit in
         // future and remove these validations.
@@ -197,7 +210,8 @@
     MaybeError DoCopyTextureForBrowser(DeviceBase* device,
                                        const TextureCopyView* source,
                                        const TextureCopyView* destination,
-                                       const Extent3D* copySize) {
+                                       const Extent3D* copySize,
+                                       const CopyTextureForBrowserOptions* options) {
         // TODO(shaobo.yan@intel.com): In D3D12 and Vulkan, compatible texture format can directly
         // copy to each other. This can be a potential fast path.
         RenderPipelineBase* pipeline = GetOrCreateCopyTextureForBrowserPipeline(device);
@@ -213,20 +227,24 @@
         bgDesc.entries = bindGroupEntries;
 
         // Prepare binding 0 resource: uniform buffer.
-        // TODO(shaobo.yan@intel.com): Will use scale vector and offset vector to replace the
-        // 4x4 rotation matrix here.
-        const float rotationMatrix[] = {
+        float uniformData[] = {
             1.0, 1.0,  // scale
             0.0, 0.0   // offset
         };
 
-        BufferDescriptor rotationUniformDesc = {};
-        rotationUniformDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform;
-        rotationUniformDesc.size = sizeof(rotationMatrix);
-        Ref<BufferBase> rotationUniform = AcquireRef(device->CreateBuffer(&rotationUniformDesc));
+        // Handle flipY.
+        if (options && options->flipY) {
+            uniformData[1] *= -1.0;
+            uniformData[3] += 1.0;
+        }
 
-        device->GetDefaultQueue()->WriteBuffer(rotationUniform.Get(), 0, rotationMatrix,
-                                               sizeof(rotationMatrix));
+        BufferDescriptor uniformDesc = {};
+        uniformDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform;
+        uniformDesc.size = sizeof(uniformData);
+        Ref<BufferBase> uniformBuffer = AcquireRef(device->CreateBuffer(&uniformDesc));
+
+        device->GetDefaultQueue()->WriteBuffer(uniformBuffer.Get(), 0, uniformData,
+                                               sizeof(uniformData));
 
         // Prepare binding 1 resource: sampler
         // Use default configuration, filterMode set to Nearest for min and mag.
@@ -242,8 +260,8 @@
 
         // Set bind group entries.
         bindGroupEntries[0].binding = 0;
-        bindGroupEntries[0].buffer = rotationUniform.Get();
-        bindGroupEntries[0].size = sizeof(rotationMatrix);
+        bindGroupEntries[0].buffer = uniformBuffer.Get();
+        bindGroupEntries[0].size = sizeof(uniformData);
         bindGroupEntries[1].binding = 1;
         bindGroupEntries[1].sampler = sampler.Get();
         bindGroupEntries[2].binding = 2;
diff --git a/src/dawn_native/CopyTextureForBrowserHelper.h b/src/dawn_native/CopyTextureForBrowserHelper.h
index 1894e8b..e65a37e 100644
--- a/src/dawn_native/CopyTextureForBrowserHelper.h
+++ b/src/dawn_native/CopyTextureForBrowserHelper.h
@@ -22,16 +22,19 @@
     class DeviceBase;
     struct Extent3D;
     struct TextureCopyView;
+    struct CopyTextureForBrowserOptions;
 
     MaybeError ValidateCopyTextureForBrowser(DeviceBase* device,
                                              const TextureCopyView* source,
                                              const TextureCopyView* destination,
-                                             const Extent3D* copySize);
+                                             const Extent3D* copySize,
+                                             const CopyTextureForBrowserOptions* options);
 
     MaybeError DoCopyTextureForBrowser(DeviceBase* device,
                                        const TextureCopyView* source,
                                        const TextureCopyView* destination,
-                                       const Extent3D* copySize);
+                                       const Extent3D* copySize,
+                                       const CopyTextureForBrowserOptions* options);
 
 }  // namespace dawn_native
 
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 8bf92e1..6c8b901 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -312,18 +312,23 @@
 
     void QueueBase::CopyTextureForBrowser(const TextureCopyView* source,
                                           const TextureCopyView* destination,
-                                          const Extent3D* copySize) {
-        GetDevice()->ConsumedError(CopyTextureForBrowserInternal(source, destination, copySize));
+                                          const Extent3D* copySize,
+                                          const CopyTextureForBrowserOptions* options) {
+        GetDevice()->ConsumedError(
+            CopyTextureForBrowserInternal(source, destination, copySize, options));
     }
 
-    MaybeError QueueBase::CopyTextureForBrowserInternal(const TextureCopyView* source,
-                                                        const TextureCopyView* destination,
-                                                        const Extent3D* copySize) {
+    MaybeError QueueBase::CopyTextureForBrowserInternal(
+        const TextureCopyView* source,
+        const TextureCopyView* destination,
+        const Extent3D* copySize,
+        const CopyTextureForBrowserOptions* options) {
         if (GetDevice()->IsValidationEnabled()) {
-            DAWN_TRY(ValidateCopyTextureForBrowser(GetDevice(), source, destination, copySize));
+            DAWN_TRY(
+                ValidateCopyTextureForBrowser(GetDevice(), source, destination, copySize, options));
         }
 
-        return DoCopyTextureForBrowser(GetDevice(), source, destination, copySize);
+        return DoCopyTextureForBrowser(GetDevice(), source, destination, copySize, options);
     }
 
     MaybeError QueueBase::ValidateSubmit(uint32_t commandCount,
diff --git a/src/dawn_native/Queue.h b/src/dawn_native/Queue.h
index 00a132d..59e3117 100644
--- a/src/dawn_native/Queue.h
+++ b/src/dawn_native/Queue.h
@@ -47,7 +47,8 @@
                           const Extent3D* writeSize);
         void CopyTextureForBrowser(const TextureCopyView* source,
                                    const TextureCopyView* destination,
-                                   const Extent3D* copySize);
+                                   const Extent3D* copySize,
+                                   const CopyTextureForBrowserOptions* options);
 
         void TrackTask(std::unique_ptr<TaskInFlight> task, ExecutionSerial serial);
         void Tick(ExecutionSerial finishedSerial);
@@ -68,7 +69,8 @@
                                         const Extent3D* writeSize);
         MaybeError CopyTextureForBrowserInternal(const TextureCopyView* source,
                                                  const TextureCopyView* destination,
-                                                 const Extent3D* copySize);
+                                                 const Extent3D* copySize,
+                                                 const CopyTextureForBrowserOptions* options);
 
         virtual MaybeError SubmitImpl(uint32_t commandCount,
                                       CommandBufferBase* const* commands) = 0;
diff --git a/src/tests/end2end/CopyTextureForBrowserTests.cpp b/src/tests/end2end/CopyTextureForBrowserTests.cpp
index 684139b..ab1b470 100644
--- a/src/tests/end2end/CopyTextureForBrowserTests.cpp
+++ b/src/tests/end2end/CopyTextureForBrowserTests.cpp
@@ -53,11 +53,15 @@
                                 uint32_t height,
                                 uint32_t srcTexelsPerRow,
                                 RGBA8* dstData,
-                                uint32_t dstTexelsPerRow) {
-        for (unsigned int y = 0; y < height; ++y) {
-            for (unsigned int x = 0; x < width; ++x) {
-                unsigned int src = x + y * srcTexelsPerRow;
-                unsigned int dst = x + y * dstTexelsPerRow;
+                                uint32_t dstTexelsPerRow,
+                                const wgpu::CopyTextureForBrowserOptions* options) {
+        bool isFlipY = options != nullptr && options->flipY;
+        for (uint32_t y = 0; y < height; ++y) {
+            for (uint32_t x = 0; x < width; ++x) {
+                uint32_t srcYIndex =
+                    isFlipY ? (height - y - 1) * srcTexelsPerRow : y * srcTexelsPerRow;
+                uint32_t src = x + srcYIndex;
+                uint32_t dst = x + y * dstTexelsPerRow;
                 dstData[dst] = srcData[src];
             }
         }
@@ -65,7 +69,8 @@
 
     void DoTest(const TextureSpec& srcSpec,
                 const TextureSpec& dstSpec,
-                const wgpu::Extent3D& copySize) {
+                const wgpu::Extent3D& copySize,
+                const wgpu::CopyTextureForBrowserOptions* options) {
         wgpu::TextureDescriptor srcDescriptor;
         srcDescriptor.size = srcSpec.textureSize;
         srcDescriptor.format = kTextureFormat;
@@ -119,7 +124,7 @@
 
         // Perform a copy here for testing.
         device.GetDefaultQueue().CopyTextureForBrowser(&srcTextureCopyView, &dstTextureCopyView,
-                                                       &copySize);
+                                                       &copySize, options);
 
         // Texels in single slice.
         const uint32_t texelCountInCopyRegion = utils::GetTexelCountInCopyRegion(
@@ -134,7 +139,7 @@
                 (srcSpec.copyOrigin.x + srcSpec.copyOrigin.y * copyLayout.texelBlocksPerRow);
             PackTextureData(&textureArrayCopyData[expectedTexelArrayDataStartIndex], copySize.width,
                             copySize.height, copyLayout.texelBlocksPerRow, expected.data(),
-                            copySize.width);
+                            copySize.width, options);
 
             EXPECT_TEXTURE_RGBA8_EQ(expected.data(), dstTexture, dstSpec.copyOrigin.x,
                                     dstSpec.copyOrigin.y, copySize.width, copySize.height,
@@ -160,7 +165,7 @@
     // Tests skip due to crbug.com/dawn/592.
     DAWN_SKIP_TEST_IF(IsD3D12() && IsBackendValidationEnabled());
 
-    // OpenGL tests fails due to 'WriteTexture' unimplemented.
+    // OpenGL tests fails because 'WriteTexture' is unimplemented.
     // Related bug : crbug.com/dawn/483
     DAWN_SKIP_TEST_IF(IsOpenGL());
     DAWN_SKIP_TEST_IF(IsOpenGLES());
@@ -172,14 +177,16 @@
     textureSpec.copyOrigin = {0, 0, 0};
     textureSpec.level = 0;
     textureSpec.textureSize = {kWidth, kHeight, 1};
-    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1});
+
+    wgpu::CopyTextureForBrowserOptions options = {};
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1}, &options);
 }
 
 TEST_P(CopyTextureForBrowserTests, VerifyCopyOnXDirection) {
     // Tests skip due to crbug.com/dawn/592.
     DAWN_SKIP_TEST_IF(IsD3D12() && IsBackendValidationEnabled());
 
-    // OpenGL tests fails due to 'WriteTexture' unimplemented.
+    // OpenGL tests fails because 'WriteTexture' is unimplemented.
     // Related bug : crbug.com/dawn/483
     DAWN_SKIP_TEST_IF(IsOpenGL());
     DAWN_SKIP_TEST_IF(IsOpenGLES());
@@ -191,14 +198,16 @@
     textureSpec.copyOrigin = {0, 0, 0};
     textureSpec.level = 0;
     textureSpec.textureSize = {kWidth, kHeight, 1};
-    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1});
+
+    wgpu::CopyTextureForBrowserOptions options = {};
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1}, &options);
 }
 
 TEST_P(CopyTextureForBrowserTests, VerifyCopyOnYDirection) {
     // Tests skip due to crbug.com/dawn/592.
     DAWN_SKIP_TEST_IF(IsD3D12() && IsBackendValidationEnabled());
 
-    // OpenGL tests fails due to 'WriteTexture' unimplemented.
+    // OpenGL tests fails because 'WriteTexture' is unimplemented.
     // Related bug : crbug.com/dawn/483
     DAWN_SKIP_TEST_IF(IsOpenGL());
     DAWN_SKIP_TEST_IF(IsOpenGLES());
@@ -210,14 +219,16 @@
     textureSpec.copyOrigin = {0, 0, 0};
     textureSpec.level = 0;
     textureSpec.textureSize = {kWidth, kHeight, 1};
-    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1});
+
+    wgpu::CopyTextureForBrowserOptions options = {};
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1}, &options);
 }
 
 TEST_P(CopyTextureForBrowserTests, VerifyCopyFromLargeTexture) {
     // Tests skip due to crbug.com/dawn/592.
     DAWN_SKIP_TEST_IF(IsD3D12() && IsBackendValidationEnabled());
 
-    // OpenGL tests fails due to 'WriteTexture' unimplemented.
+    // OpenGL tests fails because 'WriteTexture' is unimplemented.
     // Related bug : crbug.com/dawn/483
     DAWN_SKIP_TEST_IF(IsOpenGL());
     DAWN_SKIP_TEST_IF(IsOpenGLES());
@@ -229,7 +240,51 @@
     textureSpec.copyOrigin = {0, 0, 0};
     textureSpec.level = 0;
     textureSpec.textureSize = {kWidth, kHeight, 1};
-    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1});
+
+    wgpu::CopyTextureForBrowserOptions options = {};
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1}, &options);
+}
+
+TEST_P(CopyTextureForBrowserTests, VerifyFlipY) {
+    // Tests skip due to crbug.com/dawn/592.
+    DAWN_SKIP_TEST_IF(IsD3D12() && IsBackendValidationEnabled());
+
+    // OpenGL tests fails because 'WriteTexture' is unimplemented.
+    // Related bug : crbug.com/dawn/483
+    DAWN_SKIP_TEST_IF(IsOpenGL());
+
+    constexpr uint32_t kWidth = 901;
+    constexpr uint32_t kHeight = 1001;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+
+    wgpu::CopyTextureForBrowserOptions options = {};
+    options.flipY = true;
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1}, &options);
+}
+
+TEST_P(CopyTextureForBrowserTests, VerifyFlipYInSlimTexture) {
+    // Tests skip due to crbug.com/dawn/592.
+    DAWN_SKIP_TEST_IF(IsD3D12() && IsBackendValidationEnabled());
+
+    // OpenGL tests fails because 'WriteTexture' is unimplemented.
+    // Related bug : crbug.com/dawn/483
+    DAWN_SKIP_TEST_IF(IsOpenGL());
+
+    constexpr uint32_t kWidth = 1;
+    constexpr uint32_t kHeight = 1001;
+
+    TextureSpec textureSpec;
+    textureSpec.copyOrigin = {0, 0, 0};
+    textureSpec.level = 0;
+    textureSpec.textureSize = {kWidth, kHeight, 1};
+
+    wgpu::CopyTextureForBrowserOptions options = {};
+    options.flipY = true;
+    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1}, &options);
 }
 
 DAWN_INSTANTIATE_TEST(CopyTextureForBrowserTests,