Skip to content

feat(馃崗): honor GPUCanvasConfiguration.colorSpace on iOS - #586

Merged
wcandillon merged 1 commit into
wcandillon:mainfrom
camchis:feat/canvas-display-p3
Oct 22, 2026
Merged

wcandillon merged 1 commit into
wcandillon:mainfrom
camchis:feat/canvas-display-p3

Conversation

@camchis

@camchis camchis commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Problem

context.configure({ colorSpace }) is part of the WebGPU spec (PredefinedColorSpace: "srgb" | "display-p3"), but GPUCanvasConfiguration drops it, so a canvas is always sRGB. On iOS that matters wherever the canvas sits next to Display P3 content. react-native-skia's iOS canvas is Display P3, so a WebGPU canvas drawn beside it, or one that has to reproduce a Skia shader, needs a manual sRGB鈫擯3 conversion in its last pass, and an rgba16float canvas to carry the result.

Change (iOS only)

  • GPUCanvasConfiguration reads colorSpace ("srgb" default, "display-p3"); any other value throws a TypeError as in the browser. SurfaceInfo keeps it next to the configuration.

  • applyCAMetalLayerColorSpace takes it into account:

    format srgb (unchanged) display-p3
    8-bit no colorspace kCGColorSpaceDisplayP3
    rgba16float kCGColorSpaceExtendedSRGB kCGColorSpaceExtendedDisplayP3

    Values keep the sRGB transfer function, as the spec says, and both constants exist from iOS 14, so this is within the package's iOS floor.

  • Android is untouched: it parses and ignores the option. The docs say so. Happy to look at the SurfaceView data space as a follow-up if you want parity.

  • Docs: a "Color space" section in api/canvas.mdx and getting-started/canvas.mdx. Tests in CanvasContext.spec.ts configure both values on both format paths and check that an unknown value is rejected. They only check configuration, because the layer colorspace is not readable from JS.

Verification

Measured on an iPhone 12 (Release build, react-native-webgpu 0.13.0 with this diff applied). The same animated scenes were drawn two ways: a shader converting its Display P3 output to sRGB by hand into an rgba16float canvas (the workaround this makes unnecessary), and the raw Display P3 values into a canvas configured colorSpace: "display-p3". I compared screenshots of the same state over the pixels whose channel spread is above 60, where a wrong set of primaries would show most:

canvas saturated pixels compared within 1/255
rgba16float, display-p3 173,271 94% (the rest is moving content)
8-bit, display-p3 197,406 78% (max difference 11, on moving edges and 8-bit rounding)

Flat areas differ by at most 1/255.

  • Not run: the e2e suite (yarn test), an Android build, and a control with the layer left sRGB. I could only syntax-check the .mm against the Dawn 154 headers I had installed.
  • The new tests cover configuration only, because the layer's colorspace is not readable from JS. If you would like a pixel check in the repo, I can add a fixed-color screen to the example app.

Notes

The Skia side of the same problem, a Skia image wrapping a WebGPU texture being converted sRGB鈫扨3 when sampled, is a react-native-skia matter and not touched here.

configure() ignored colorSpace, so a canvas was always sRGB. "display-p3" now tags the CAMetalLayer Display P3 (extended Display P3 on rgba16float). Android still ignores it.
@wcandillon
wcandillon self-requested a review October 22, 2026 06:13

@wcandillon wcandillon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this :)

@wcandillon
wcandillon merged commit ba22e8f into wcandillon:main Oct 22, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants