Skip to content

fix(compositors): sRGB passthrough for in-process D3D11/D3D12/VK/Metal swapchains#408

Merged
dfattal merged 1 commit into
mainfrom
fix/inprocess-srgb-d3d-vk
Jun 4, 2026
Merged

fix(compositors): sRGB passthrough for in-process D3D11/D3D12/VK/Metal swapchains#408
dfattal merged 1 commit into
mainfrom
fix/inprocess-srgb-d3d-vk

Conversation

@dfattal
Copy link
Copy Markdown
Collaborator

@dfattal dfattal commented Jun 4, 2026

Extends the in-process GL sRGB/gamma fix (#407, 58d3f7c) to the other four native compositors.

Root cause (same as GL)

When an in-process app uses an sRGB swapchain, the compositor sampled it with the GPU's automatic sRGB→linear decode but wrote the result into a non-sRGB atlas with no re-encode, so the Leia display processor — which expects display-referred (sRGB-encoded) bytes — received ~2.2× too-dark content. Only the composited path is affected; the single-layer zero-copy handoff already passes the texture straight to the DP.

Fix (per API)

Make the runtime's sample a non-decoding/UNORM read so the app's display-referred bytes pass through unchanged. The app's own render target keeps the sRGB format, so any encoding it does is preserved.

Compositor Approach
D3D11 internal sampling SRV → UNORM sibling (texture already TYPELESS)
D3D12 promote 8-bit color resource to TYPELESS (D3D12 can't SRV-cast concrete sRGB) + UNORM SRV
Vulkan vkCmdBlitImage reads the image's own format → create image UNORM-base + MUTABLE_FORMAT + format list exposing the requested sRGB (app can still make an sRGB view to encode)
Metal sample via UNORM newTextureViewWithPixelFormat: (+ MTLTextureUsagePixelFormatView)

Shared sRGB→UNORM / TYPELESS→UNORM-sample DXGI helpers in d3d_dxgi_formats.h.

Verification

D3D11, D3D12, Vulkan validated on-device via the post-compose atlas capture (displayxr_atlas_trigger), with a cube temporarily flipped to an sRGB swapchain rendering raw display-referred bytes (UNORM RTV/framebuffer to avoid the app double-encoding) — exposure matches the UNORM baseline.

Metal compiles only on macOS — pending a macOS CI build + Mac runtime eyeball. Code mirrors the others.

Scope

Behavior change only for in-process apps that pick an sRGB swapchain (now correct instead of dark); UNORM apps untouched.

🤖 Generated with Claude Code

…l swapchains

Extends the in-process GL sRGB/gamma fix (58d3f7c) to the other four native
compositors. Same root cause: when an in-process app uses an sRGB swapchain,
the compositor sampled it with the GPU's automatic sRGB->linear decode but wrote
the result into a non-sRGB atlas with no re-encode, so the Leia display
processor — which expects display-referred (sRGB-encoded) bytes — received
~2.2x-too-dark content. Only the *composited* path is affected; the single-layer
zero-copy handoff already passes the texture through untouched.

Fix per API (make the runtime's sample a non-decoding/UNORM read so the app's
display-referred bytes pass through unchanged; the app's own render target keeps
the sRGB format, so any encoding it does is preserved):
- D3D11: build the internal sampling SRV with the UNORM sibling format
  (texture is already TYPELESS). comp_d3d11_swapchain.cpp.
- D3D12: promote the 8-bit color resource to TYPELESS (D3D12 can't SRV-cast a
  concrete sRGB resource) and sample it as UNORM. comp_d3d12_swapchain.cpp +
  comp_d3d12_renderer.cpp.
- Vulkan: vkCmdBlitImage reads the source in the image's own format (no view to
  retag), so create the color image in the UNORM sibling with MUTABLE_FORMAT + a
  format list exposing the requested sRGB (the app can still make an sRGB view to
  render with encode). comp_vk_native_swapchain.c.
- Metal: sample through a UNORM newTextureViewWithPixelFormat: (adds
  MTLTextureUsagePixelFormatView to the swapchain texture). comp_metal_compositor.m.
Shared sRGB->UNORM / TYPELESS->UNORM-sample DXGI helpers in d3d_dxgi_formats.h.

Verification: D3D11, D3D12, and Vulkan validated on-device via the post-compose
atlas capture (displayxr_atlas_trigger) with a cube temporarily flipped to an
sRGB swapchain rendering raw display-referred bytes — exposure matches the UNORM
baseline. Metal compiles only on macOS; pending a macOS/Mac runtime check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dfattal dfattal merged commit 70fad88 into main Jun 4, 2026
22 checks passed
@dfattal dfattal deleted the fix/inprocess-srgb-d3d-vk branch June 4, 2026 05:29
dfattal added a commit that referenced this pull request Jun 4, 2026
…#409 forward-ref

- Rollout note: as of #408 all five in-process native compositors
  (GL #407, D3D11/D3D12/VK/Metal #408) pass sRGB swapchains through
  correctly — drop the "being aligned / currently too dark" caveat.
- Linear/UNORM-swapchain bullet: add a "subject to #409" forward-reference
  noting this describes current Model-A behavior and would flip if the color
  model lands on linear-compose (Model B). The request-an-sRGB-swapchain
  recommendation is correct either way.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dfattal added a commit that referenced this pull request Jun 4, 2026
…409]

Establish a vendor-neutral color contract for the runtime, replacing the
three ad-hoc per-path colorspace strategies surfaced during the #407/#408
sRGB investigation.

- ADR-021: matched-pair invariant (decode..encode or passthrough, never
  half); compose space is DisplayXR-internal/vendor-neutral; DP-handoff
  encoding is DECLARED by the DP (LINEAR/ENCODED/EITHER) and the runtime
  converts compose->handoff to match (both directions). Production DPs
  with a configurable conversion control declare EITHER -- an instance of
  the contract, not its definition. Canonical TF is standard sRGB; no
  vendor curves in runtime shaders. Model A (passthrough) baseline;
  Model B (linear compose) sanctioned future, scoped to the workspace
  compose path. ADR kept vendor-neutral.
- compositor-pipeline.md: correct the wrong "DP expects linear input"
  assertion; document the latent sRGB-SRV half-conversion.
- vendors/leia/weaver.md: map the SDK's sRGB-conversion knob to the
  ADR-021 contract + record the plug-in wiring gaps (vendor specifics
  live here, not in the ADR).
- comp_d3d11_service.cpp: one-shot guard + corrected comment on the
  per-client sRGB-SRV branch (decode with no matching re-encode; dead in
  practice, fires for a real sRGB-swapchain client under the workspace).
- Fix inbound anchor links to the renamed color-space section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant