fix(🍏): set sRGB color space on WebGPUMetalView CAMetalLayer#3767
fix(🍏): set sRGB color space on WebGPUMetalView CAMetalLayer#3767kbrandwijk wants to merge 5 commits intoShopify:mainfrom
Conversation
Without this, RGBA16Float surface values are interpreted in linear/EDR color space, making colors appear too bright. Setting sRGB ensures correct tone mapping for standard content. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wcandillon
left a comment
There was a problem hiding this comment.
Any chance you could add a small example that uses RGBA16Float? I think it would be fun, we could also display it next to a Skia Canvas/Native views to make some comparison. Now is that something that works on Android already? A small example would allow to test these things quickly.
Replace the hardcoded sRGB colorspace with a configurable colorSpace prop that supports "srgb", "display-p3", "bt2020-hlg", and "bt2020-pq". The colorSpace determines how rendered pixel values are interpreted for display — independent of the texture format. This is needed when rendering camera frames in different color spaces (e.g. P3, Apple Log) via RGBA16Float textures. iOS: Sets CAMetalLayer.colorspace based on the prop value. Android: Calls ANativeWindow_setBuffersDataSpace (API 28+). The prop is reactive — changing it at runtime (e.g. when switching camera formats) updates the display colorspace without recreating the surface. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Rethought this — hardcoding to sRGB wasn't the right approach. The colorspace on the display surface should be configurable because it depends on the content being rendered, not just the texture format. What changed: Replaced the hardcoded <WebGPUCanvas colorSpace="display-p3" />
<WebGPUCanvas colorSpace="srgb" />
<WebGPUCanvas colorSpace="bt2020-hlg" />Both platforms:
The prop is reactive — changing camera format at runtime (e.g., switching from P3 to sRGB) updates the display colorspace without recreating the surface. Re: example — This makes a side-by-side comparison straightforward: two Note: The existing (non-WebGPU) |
|
isn't colorSpace as well as the texture format set in .configure?
I understand then that on Android and iOS there might be limitations
to that spec (this is the case with the alphaMode property from
configure)
…On Wed, Mar 25, 2026 at 4:46 PM Kim Brandwijk ***@***.***> wrote:
kbrandwijk left a comment (Shopify/react-native-skia#3767)
Rethought this — hardcoding to sRGB wasn't the right approach. The colorspace on the display surface should be configurable because it depends on the content being rendered, not just the texture format.
What changed: Replaced the hardcoded CAMetalLayer.colorspace = sRGB with a colorSpace prop on WebGPUCanvas:
<WebGPUCanvas colorSpace="display-p3" />
<WebGPUCanvas colorSpace="srgb" />
<WebGPUCanvas colorSpace="bt2020-hlg" />
Both platforms:
iOS: Sets CAMetalLayer.colorspace based on prop value (sRGB, Display P3, ITU-R 2100 HLG/PQ)
Android: Calls ANativeWindow_setBuffersDataSpace (API 28+) with the corresponding data space
The prop is reactive — changing camera format at runtime (e.g., switching from P3 to sRGB) updates the display colorspace without recreating the surface.
Re: example — This makes a side-by-side comparison straightforward: two WebGPUCanvas instances with different colorSpace props rendering the same frame. Happy to put a quick example together.
Note: The existing (non-WebGPU) Canvas already has a colorSpace prop ("p3" | "srgb") that works on iOS but is a no-op stub on Android. Same gap, separate code path.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because your review was requested.Message ID: ***@***.***>
|
|
You're right — Dawn has However... the WebGPU spec only defines two color spaces — our camera pipeline also outputs BT.2020 (HLG/PQ) when using Apple Log or HDR formats, which aren't covered by So the approach could be: use There's a larger conversation going on around proper support through the WebGPU spec (gpuweb/gpuweb#4919 and https://github.com/ccameron-chromium/webgpu-hdr/blob/main/EXPLAINER.md) so for now I personally think it's fine to go through native directly, but if you prefer the fallback option then let me know. |
|
One thing worth noting — the Chrome team's HDR proposal uses |
When colorSpace is bt2020-hlg or bt2020-pq: - Enable wantsExtendedDynamicRangeContent on CAMetalLayer (iOS) - Override surface format to RGBA16Float via SurfaceInfo so the full HDR range is preserved through to display The format override is applied both as a stored preference (for when the native prop is set before JS configure) and checked during GPUCanvasContext::configure() to handle race conditions between native prop delivery and JS-side surface configuration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Follow-up: added EDR support and surface format override for HDR color spaces. When testing with HLG BT.2020 camera output, we discovered that setting the colorspace alone isn't enough for HDR — the surface also needs:
The format override is implemented via SurfaceInfo::setFormatOverride() which stores the desired format and applies it when GPUCanvasContext::configure() runs — handling the race between native prop delivery and JS-side surface configuration. This confirms the link between texture format and colorspace that your raised @wcandillon; for SDR (sRGB, Display P3) the existing BGRA8Unorm surface works fine, but HDR color spaces require the format and colorspace to change together. A future configure()-level API could couple these, but for now the native prop approach gives us the flexibility to support BT.2020 variants that aren't in the WebGPU spec yet. |
|
Here's a video showing the working relation between selected camera formats (sRGB, P3, 2020) and the selected surface format, for accurate display and underlying texture format usage (BGRA8unorm vs RGBA16float). You clearly see that sRGB canvas is washed out for P3 camera format, while P3 is accurate and the same for 2020 (it looks off, but that's HDR rendering) ScreenRecording_03-25-2026.12-13-09.PM_1.MP4 |
wcandillon
left a comment
There was a problem hiding this comment.
This looks very compelling, can you add a small example in https://github.com/Shopify/react-native-skia/tree/main/apps/example/src/Examples/WebGPU so we can play with it.
Btw I will clean up the existing ones and add some other more relevant ones, this was just some dirty testing.
|
it looks like there is a conflict, it shouldn't be something big |
Summary
CGColorSpaceSRGBon theCAMetalLayerbacking theWebGPUMetalViewRGBA16Floatsurface values are interpreted in linear/EDR color space, making colors appear too brightWebGPUCanvasTest plan
WebGPUCanvaswithRGBA16Floatsurface formatBGRA8Unormsurfaces