Support *-srgb format as dst formats in CopyTextureForBrowser

Dawn allows texture-to-texture copy happens between the textures that
formats only have diff on srgb-ness.

CopyTextureForBrowser could align on this rule to achieve copying to
*-srgb dst texture and keep the bytes the same as copying to non-srgb
formats.

This CL add support for *-srgb textures as dst textures and using an
extra gamma decoding step for this.

Bug: dawn:1195
Change-Id: I665dbca473aa84b9d87b7a35c4f90ce1897ade7b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/74580
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Shaobo Yan <shaobo.yan@intel.com>
diff --git a/src/dawn_native/CopyTextureForBrowserHelper.cpp b/src/dawn_native/CopyTextureForBrowserHelper.cpp
index 919a248..8fd87bd 100644
--- a/src/dawn_native/CopyTextureForBrowserHelper.cpp
+++ b/src/dawn_native/CopyTextureForBrowserHelper.cpp
@@ -48,14 +48,15 @@
                 padding: u32;
             };
 
-            struct Uniforms {                        // offset   align   size
-                scale: vec2<f32>;                              // 0        8       8
-                offset: vec2<f32>;                             // 8        8       8
-                steps_mask: u32;                               // 16       4       4  
-                // implicit padding;                           // 20               12
-                conversion_matrix: mat3x3<f32>;                // 32       16      48
-                gamma_decoding_params: GammaTransferParams;    // 80       4       32
-                gamma_encoding_params: GammaTransferParams;    // 112      4       32
+            struct Uniforms {                                            // offset   align   size
+                scale: vec2<f32>;                                        // 0        8       8
+                offset: vec2<f32>;                                       // 8        8       8
+                steps_mask: u32;                                         // 16       4       4
+                // implicit padding;                                     // 20               12
+                conversion_matrix: mat3x3<f32>;                          // 32       16      48
+                gamma_decoding_params: GammaTransferParams;              // 80       4       32
+                gamma_encoding_params: GammaTransferParams;              // 112      4       32
+                gamma_decoding_for_dst_srgb_params: GammaTransferParams; // 144      4       32
             };
 
             [[binding(0), group(0)]] var<uniform> uniforms : Uniforms;
@@ -141,6 +142,7 @@
                 let kConvertToDstGamutStep = 0x04u;
                 let kEncodeToGammaStep = 0x08u;
                 let kPremultiplyStep = 0x10u;
+                let kDecodeForSrgbDstFormat = 0x20u;
 
                 // Unpremultiply step. Appling color space conversion op on premultiplied source texture
                 // also needs to unpremultiply first.
@@ -180,6 +182,14 @@
                     color = vec4<f32>(color.rgb * color.a, color.a);
                 }
 
+                // Decode for copying from non-srgb formats to srgb formats
+                if (bool(uniforms.steps_mask & kDecodeForSrgbDstFormat)) {
+                    color = vec4<f32>(gamma_conversion(color.r, uniforms.gamma_decoding_for_dst_srgb_params),
+                                      gamma_conversion(color.g, uniforms.gamma_decoding_for_dst_srgb_params),
+                                      gamma_conversion(color.b, uniforms.gamma_decoding_for_dst_srgb_params),
+                                      color.a);
+                }
+
                 return color;
             }
         )";
@@ -207,8 +217,9 @@
             std::array<float, 12> conversionMatrix = {};
             GammaTransferParams gammaDecodingParams = {};
             GammaTransferParams gammaEncodingParams = {};
+            GammaTransferParams gammaDecodingForDstSrgbParams = {};
         };
-        static_assert(sizeof(Uniform) == 144, "");
+        static_assert(sizeof(Uniform) == 176, "");
 
         // TODO(crbug.com/dawn/856): Expand copyTextureForBrowser to support any
         // non-depth, non-stencil, non-compressed texture format pair copy. Now this API
@@ -232,7 +243,9 @@
                 case wgpu::TextureFormat::RG16Float:
                 case wgpu::TextureFormat::RG32Float:
                 case wgpu::TextureFormat::RGBA8Unorm:
+                case wgpu::TextureFormat::RGBA8UnormSrgb:
                 case wgpu::TextureFormat::BGRA8Unorm:
+                case wgpu::TextureFormat::BGRA8UnormSrgb:
                 case wgpu::TextureFormat::RGB10A2Unorm:
                 case wgpu::TextureFormat::RGBA16Float:
                 case wgpu::TextureFormat::RGBA32Float:
@@ -362,6 +375,17 @@
         return {};
     }
 
+    // Whether the format of dst texture of CopyTextureForBrowser() is srgb or non-srgb.
+    bool IsSrgbDstFormat(wgpu::TextureFormat format) {
+        switch (format) {
+            case wgpu::TextureFormat::RGBA8UnormSrgb:
+            case wgpu::TextureFormat::BGRA8UnormSrgb:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     MaybeError DoCopyTextureForBrowser(DeviceBase* device,
                                        const ImageCopyTexture* source,
                                        const ImageCopyTexture* destination,
@@ -375,6 +399,7 @@
             return {};
         }
 
+        bool isSrgbDstFormat = IsSrgbDstFormat(destination->texture->GetFormat().format);
         RenderPipelineBase* pipeline;
         DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyTextureForBrowserPipeline(
                                       device, destination->texture->GetFormat().format));
@@ -422,6 +447,7 @@
         constexpr uint32_t kConvertToDstGamutStep = 0x04;
         constexpr uint32_t kEncodeToGammaStep = 0x08;
         constexpr uint32_t kPremultiplyStep = 0x10;
+        constexpr uint32_t kDecodeForSrgbDstFormat = 0x20;
 
         if (options->srcAlphaMode == wgpu::AlphaMode::Premultiplied) {
             if (options->needsColorSpaceConversion ||
@@ -470,6 +496,25 @@
             }
         }
 
+        // Copy to *-srgb texture should keep the bytes exactly the same as copy
+        // to non-srgb texture. Add an extra decode-to-linear step so that after the
+        // sampler of *-srgb format texture applying encoding, the bytes keeps the same
+        // as non-srgb format texture.
+        // NOTE: CopyTextureForBrowser() doesn't need to accept *-srgb format texture as
+        // source input. But above operation also valid for *-srgb format texture input and
+        // non-srgb format dst texture.
+        // TODO(crbug.com/dawn/1195): Reinterpret to non-srgb texture view on *-srgb texture
+        // and use it as render attachment when possible.
+        // TODO(crbug.com/dawn/1195): Opt the condition for this extra step. It is possible to
+        // bypass this extra step in some cases.
+        if (isSrgbDstFormat) {
+            stepsMask |= kDecodeForSrgbDstFormat;
+            // Get gamma-linear conversion params from https://en.wikipedia.org/wiki/SRGB with some
+            // mathematics. Order: {G, A, B, C, D, E, F, }
+            uniformData.gammaDecodingForDstSrgbParams = {
+                2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 4.045e-02, 0.0, 0.0};
+        }
+
         uniformData.stepsMask = stepsMask;
 
         Ref<BufferBase> uniformBuffer;
@@ -511,9 +556,9 @@
         dstTextureViewDesc.baseArrayLayer = destination->origin.z;
         dstTextureViewDesc.arrayLayerCount = 1;
         Ref<TextureViewBase> dstView;
+
         DAWN_TRY_ASSIGN(dstView,
                         device->CreateTextureView(destination->texture, &dstTextureViewDesc));
-
         // Prepare render pass color attachment descriptor.
         RenderPassColorAttachment colorAttachmentDesc;
 
@@ -546,7 +591,6 @@
 
         // Submit command buffer.
         device->GetQueue()->APISubmit(1, &submitCommandBuffer);
-
         return {};
     }
 
diff --git a/src/tests/end2end/CopyTextureForBrowserTests.cpp b/src/tests/end2end/CopyTextureForBrowserTests.cpp
index afd7bbb..028dfd7 100644
--- a/src/tests/end2end/CopyTextureForBrowserTests.cpp
+++ b/src/tests/end2end/CopyTextureForBrowserTests.cpp
@@ -571,6 +571,17 @@
                GetParam().mDstFormat == wgpu::TextureFormat::BGRA8UnormSrgb;
     }
 
+    wgpu::TextureFormat GetNonSrgbFormat(wgpu::TextureFormat format) {
+        switch (format) {
+            case wgpu::TextureFormat::RGBA8UnormSrgb:
+                return wgpu::TextureFormat::RGBA8Unorm;
+            case wgpu::TextureFormat::BGRA8UnormSrgb:
+                return wgpu::TextureFormat::BGRA8Unorm;
+            default:
+                return format;
+        }
+    }
+
     void DoColorConversionTest() {
         TextureSpec srcTextureSpec;
         srcTextureSpec.format = GetParam().mSrcFormat;
@@ -627,8 +638,40 @@
         RunCopyExternalImageToTexture(srcTextureSpec, srcTexture, dstTextureSpec, dstTexture,
                                       copySize, options);
 
+        wgpu::Texture result;
+        TextureSpec resultSpec = dstTextureSpec;
+
+        // To construct the expected value for the case that dst texture is srgb format,
+        // we need to ensure it is byte level equal to the comparable non-srgb format texture.
+        // We schedule an copy from srgb texture to non-srgb texture which keeps the bytes
+        // same and bypass the sampler to do gamma correction when comparing the expected values
+        // in compute shader.
+        if (IsDstFormatSrgbFormats()) {
+            resultSpec.format = GetNonSrgbFormat(dstTextureSpec.format);
+            wgpu::Texture intermediateTexture = CreateTexture(
+                resultSpec, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding |
+                                wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);
+
+            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+
+            // Perform the texture to texture copy
+            wgpu::ImageCopyTexture dstImageCopyTexture =
+                utils::CreateImageCopyTexture(dstTexture, 0, {0, 0, 0});
+            wgpu::ImageCopyTexture intermediateImageCopyTexture =
+                utils::CreateImageCopyTexture(intermediateTexture, 0, {0, 0, 0});
+
+            encoder.CopyTextureToTexture(&dstImageCopyTexture, &intermediateImageCopyTexture,
+                                         &(dstTextureSpec.textureSize));
+            wgpu::CommandBuffer commands = encoder.Finish();
+            queue.Submit(1, &commands);
+
+            result = intermediateTexture;
+        } else {
+            result = dstTexture;
+        }
+
         // Check Result
-        CheckResultInBuiltInComputePipeline(srcTextureSpec, srcTexture, dstTextureSpec, dstTexture,
+        CheckResultInBuiltInComputePipeline(srcTextureSpec, srcTexture, resultSpec, result,
                                             copySize, options);
     }
 };
@@ -1045,7 +1088,8 @@
         {wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R16Float, wgpu::TextureFormat::R32Float,
          wgpu::TextureFormat::RG8Unorm, wgpu::TextureFormat::RG16Float,
          wgpu::TextureFormat::RG32Float, wgpu::TextureFormat::RGBA8Unorm,
-         wgpu::TextureFormat::BGRA8Unorm, wgpu::TextureFormat::RGB10A2Unorm,
+         wgpu::TextureFormat::RGBA8UnormSrgb, wgpu::TextureFormat::BGRA8Unorm,
+         wgpu::TextureFormat::BGRA8UnormSrgb, wgpu::TextureFormat::RGB10A2Unorm,
          wgpu::TextureFormat::RGBA16Float, wgpu::TextureFormat::RGBA32Float}));
 
 // Verify |CopyTextureForBrowser| doing subrect copy.