fix(compositors): sRGB passthrough for in-process D3D11/D3D12/VK/Metal swapchains#408
Merged
Conversation
…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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
vkCmdBlitImagereads 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)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