WebGPU: Enable non-sRGB output color spaces

Add a PredefinedColorSpace member to GPUCanvasContext, and populate
it in GPUCanvasContext::configure.

Pass that color space to WebGPUSwapBufferProvider, which also stores
it as a member variable.

Replace hardcoded use of SRGB in WebGPUSwapBufferProvider and
GPUCanvasContext with the appropriate color space.

Bug: 1241375
Change-Id: Icc3d5e73626e536974d8b7c102fc42d51b830d16
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4026764
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: ccameron chromium <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1072156}
diff --git a/content/test/data/gpu/pixel_webgpu_display_p3.html b/content/test/data/gpu/pixel_webgpu_display_p3.html
new file mode 100644
index 0000000..f067aa4e
--- /dev/null
+++ b/content/test/data/gpu/pixel_webgpu_display_p3.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+<!--
+Clears a WebGGPU canvas to the value (0.91749, 0.20029, 0.13856, 1.0). When
+interpreted in Display P3, this appears the same as rgb(100% 0% 0%). Display
+the canvas next to the correct color and a likely incorrect color.
+-->
+<head>
+  <style type="text/css">
+    .nomargin {
+      margin: 0;
+    }
+  </style>
+  <script type="text/javascript">
+    var g_swapsBeforeAck = 15;
+
+    function waitForFinish() {
+      if (g_swapsBeforeAck == 0) {
+        sendResult("SUCCESS");
+      } else {
+        g_swapsBeforeAck--;
+        window.requestAnimationFrame(waitForFinish);
+      }
+    }
+
+    function sendResult(status) {
+      if (window.domAutomationController) {
+        window.domAutomationController.send(status);
+      } else {
+        console.log(status);
+      }
+    }
+
+    var g_swapsBeforeAck = 15;
+    function waitForFinish() {
+      if (g_swapsBeforeAck == 0) {
+        sendResult("SUCCESS");
+      } else {
+        g_swapsBeforeAck--;
+        window.requestAnimationFrame(waitForFinish);
+      }
+    }
+
+    async function main() {
+      const canvas = document.getElementById('canvas_gpu');
+      const adapter = await navigator.gpu?.requestAdapter();
+      const device = await adapter?.requestDevice();
+      const context = canvas.getContext('webgpu');
+      if (!device || !context) {
+        console.error("Failed to initialize WebGPU");
+        sendResult("FAILURE");
+      }
+
+      context.configure({
+        device: device,
+        format: 'bgra8unorm',
+        usage: GPUTextureUsage.RENDER_ATTACHMENT,
+        alphaMode: 'opaque',
+        colorSpace: 'display-p3',
+      });
+
+      const renderPassDescriptor = {
+        colorAttachments: [
+          {
+            view: context.getCurrentTexture().createView(),
+            clearValue: { r: 0.91749, g: 0.20029, b: 0.13856, a: 1.0 },
+            loadOp: 'clear',
+            storeOp: 'store',
+          },
+        ],
+      };
+
+      const commandEncoder = device.createCommandEncoder();
+      const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
+      passEncoder.end();
+      device.queue.submit([commandEncoder.finish()]);
+
+      waitForFinish();
+    }
+  </script>
+</head>
+
+<body onload="main()" style="background:white;">
+  <canvas id="canvas_gpu" style="width:150px; height:150px; position:absolute; top:0px; left:0px; background: rgb(0% 100% 0%);"></canvas>
+  <div style="width:150px; height:150px; position:absolute; top:150px; left:0px; background: rgb(91.749% 20.029% 13.856%);">
+    <p>The canvas above SHOULD NOT match this color</p>
+  </div>
+  <div style="width:150px; height:150px; position:absolute; top:0px; left:150px; background: rgb(100% 0% 0%);">
+    <p>The canvas to the left SHOULD match this color</p>
+  </div>
+</body>
+
+</html>
diff --git a/content/test/gpu/gpu_tests/pixel_test_pages.py b/content/test/gpu/gpu_tests/pixel_test_pages.py
index 00af39b..ef1a1d3 100644
--- a/content/test/gpu/gpu_tests/pixel_test_pages.py
+++ b/content/test/gpu/gpu_tests/pixel_test_pages.py
@@ -506,6 +506,10 @@
                         base_name + '_WebGPUCopyExternalImageWebGPUCanvas',
                         test_rect=[0, 0, 400, 200],
                         browser_args=webgpu_args),
+          PixelTestPage('pixel_webgpu_display_p3.html',
+                        base_name + '_WebGPUDisplayP3',
+                        test_rect=[0, 0, 300, 300],
+                        browser_args=webgpu_args),
       ]
 
     return (webgpu_pages_helper(base_name, mode=Mode.WEBGPU_DEFAULT) +
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
index 1059852..3a177a4 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_canvas_configuration.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_canvasrenderingcontext2d_gpucanvascontext_imagebitmaprenderingcontext_webgl2renderingcontext_webglrenderingcontext.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_gpucanvascontext_imagebitmaprenderingcontext_offscreencanvasrenderingcontext2d_webgl2renderingcontext_webglrenderingcontext.h"
+#include "third_party/blink/renderer/core/html/canvas/predefined_color_space.h"
 #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
 #include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
 #include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
@@ -19,6 +20,7 @@
 #include "third_party/blink/renderer/modules/webgpu/gpu_queue.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_texture.h"
 #include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
+#include "third_party/blink/renderer/platform/graphics/canvas_color_params.h"
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h"
@@ -79,7 +81,7 @@
                      alpha_mode_ == V8GPUCanvasAlphaMode::Enum::kOpaque
                          ? kOpaque_SkAlphaType
                          : kPremul_SkAlphaType,
-                     SkColorSpace::MakeSRGB());
+                     PredefinedColorSpaceToSkColorSpace(color_space_));
 }
 
 void GPUCanvasContext::Stop() {
@@ -365,20 +367,15 @@
     return;
   }
 
-  // TODO(crbug.com/1241375): Support additional color spaces for external
-  // textures.
-  if (descriptor->colorSpace().AsEnum() !=
-      V8PredefinedColorSpace::Enum::kSRGB) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kOperationError,
-        "colorSpace !== 'srgb' isn't supported yet.");
+  if (!ValidateAndConvertColorSpace(descriptor->colorSpace(), color_space_,
+                                    exception_state)) {
     return;
   }
 
   swap_buffers_ = base::AdoptRef(new WebGPUSwapBufferProvider(
       this, device_->GetDawnControlClient(), device_->GetHandle(),
       static_cast<WGPUTextureUsage>(texture_descriptor_.usage),
-      texture_descriptor_.format));
+      texture_descriptor_.format, color_space_));
   swap_buffers_->SetFilterQuality(filter_quality_);
 
   // Note: SetContentsOpaque is only an optimization hint. It doesn't
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
index 225a39d4..2e8e8787 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context_factory.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h"
+#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
 
 namespace blink {
@@ -137,6 +138,7 @@
       cc::PaintFlags::FilterQuality::kLow;
   Member<GPUDevice> device_;
   Member<GPUTexture> texture_;
+  PredefinedColorSpace color_space_ = PredefinedColorSpace::kSRGB;
   V8GPUCanvasAlphaMode::Enum alpha_mode_;
   scoped_refptr<WebGPUTextureAlphaClearer> alpha_clearer_;
   scoped_refptr<WebGPUSwapBufferProvider> swap_buffers_;
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc
index 3adcfa0..0ab7036 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc
@@ -36,12 +36,14 @@
     scoped_refptr<DawnControlClientHolder> dawn_control_client,
     WGPUDevice device,
     WGPUTextureUsage usage,
-    WGPUTextureFormat format)
+    WGPUTextureFormat format,
+    PredefinedColorSpace color_space)
     : dawn_control_client_(dawn_control_client),
       client_(client),
       device_(device),
       format_(WGPUFormatToViz(format)),
-      usage_(usage) {
+      usage_(usage),
+      color_space_(color_space) {
   // Create a layer that will be used by the canvas and will ask for a
   // SharedImage each frame.
   layer_ = cc::TextureLayer::CreateForMailbox(this);
@@ -164,8 +166,8 @@
 
   if (unused_swap_buffers_.empty()) {
     gpu::Mailbox mailbox = sii->CreateSharedImage(
-        Format(), size, gfx::ColorSpace::CreateSRGB(), kTopLeft_GrSurfaceOrigin,
-        alpha_mode,
+        Format(), size, PredefinedColorSpaceToGfxColorSpace(color_space_),
+        kTopLeft_GrSurfaceOrigin, alpha_mode,
         gpu::SHARED_IMAGE_USAGE_WEBGPU |
             gpu::SHARED_IMAGE_USAGE_WEBGPU_SWAP_CHAIN_TEXTURE |
             gpu::SHARED_IMAGE_USAGE_DISPLAY_READ,
@@ -283,7 +285,7 @@
       current_swap_buffer_->mailbox, GL_LINEAR, GetTextureTarget(),
       current_swap_buffer_->access_finished_token, current_swap_buffer_->size,
       Format(), IsOverlayCandidate());
-  out_resource->color_space = gfx::ColorSpace::CreateSRGB();
+  out_resource->color_space = PredefinedColorSpaceToGfxColorSpace(color_space_);
 
   // This holds a ref on the SwapBuffers that will keep it alive until the
   // mailbox is released (and while the release callback is running).
@@ -323,7 +325,8 @@
                                     GetTextureTarget());
 
   auto success = frame_pool->CopyRGBATextureToVideoFrame(
-      Format(), current_swap_buffer_->size, gfx::ColorSpace::CreateSRGB(),
+      Format(), current_swap_buffer_->size,
+      PredefinedColorSpaceToGfxColorSpace(color_space_),
       kTopLeft_GrSurfaceOrigin, mailbox_holder, dst_color_space,
       std::move(callback));
 
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h
index 64be91b..51150a4 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h
@@ -40,7 +40,8 @@
       scoped_refptr<DawnControlClientHolder> dawn_control_client,
       WGPUDevice device,
       WGPUTextureUsage usage,
-      WGPUTextureFormat format);
+      WGPUTextureFormat format,
+      PredefinedColorSpace color_space);
   ~WebGPUSwapBufferProvider() override;
 
   viz::ResourceFormat Format() const;
@@ -161,8 +162,9 @@
 
   WTF::Vector<scoped_refptr<SwapBuffer>> unused_swap_buffers_;
   scoped_refptr<SwapBuffer> last_swap_buffer_;
-  viz::ResourceFormat format_;
-  WGPUTextureUsage usage_;
+  const viz::ResourceFormat format_;
+  const WGPUTextureUsage usage_;
+  const PredefinedColorSpace color_space_;
 
   scoped_refptr<SwapBuffer> current_swap_buffer_;
 };
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
index f09f6f3..f1f10a0 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
@@ -100,12 +100,14 @@
       WGPUDevice device,
       scoped_refptr<DawnControlClientHolder> dawn_control_client,
       WGPUTextureUsage usage,
-      WGPUTextureFormat format)
+      WGPUTextureFormat format,
+      PredefinedColorSpace color_space)
       : WebGPUSwapBufferProvider(client,
                                  dawn_control_client,
                                  device,
                                  usage,
-                                 format),
+                                 format,
+                                 color_space),
         alive_(alive),
         client_(client) {
     texture_desc_.nextInChain = nullptr;
@@ -157,7 +159,7 @@
 
     provider_ = base::MakeRefCounted<WebGPUSwapBufferProviderForTests>(
         &provider_alive_, &client_, fake_device_, dawn_control_client_, kUsage,
-        kFormat);
+        kFormat, PredefinedColorSpace::kSRGB);
   }
 
   void TearDown() override { Platform::UnsetMainThreadTaskRunnerForTesting(); }