Skip to content

#1795: Local2D layers honour their declared format (all backends + service zones) - #1809

Merged
dfattal merged 8 commits into
mainfrom
fix/1795-local2d-flatten-honest
Oct 3, 2026
Merged

dfattal merged 8 commits into
mainfrom
fix/1795-local2d-flatten-honest

Conversation

@dfattal

@dfattal dfattal commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

The deferred half of #1795 (ADR-044 §7): Local2D layers honour their declared format on every in-process backend and on the D3D11 service's pure-zones commits.

Blocked on apps declaring Local2D _SRGB (cleared, see Measured):

  • displayxr-unity-samples v2.3.0 (pending David's OK). Released v2.2.0 predates displayxr-unity#348, so its Local2D is UNORM.
  • the two Lenovo Unity builds (plugin v2.19.0 / v2.20.1, both UNORM speech bubbles).

Each of these would wash out on this runtime until it is rebuilt.

What changes

backend change
D3D11 in-process Local2D + 2D-under scratches are TYPELESS: UNORM SRV for every reader and the bridge plane, _SRGB RTV for the flatten. The source view follows the target.
D3D12 Same shape. TYPELESS scratches, explicit _SRGB RTV, _SRGB flatten PSOs. The masked composite views the TYPELESS scratch typed.
GL A shared private GL_SRGB8_ALPHA8 flatten target with GL_FRAMEBUFFER_SRGB on for the flatten only, then glCopyImageSubData into the unchanged RGBA8 scratches.
vk_native MUTABLE scratches {UNORM, _SRGB} with opt-in _SRGB flatten pipelines. Under the output-device split (always engaged on hybrid Windows boxes, even on one adapter), the flatten goes to the scratch and is then vkCmdCopyImaged into the B8G8R8A8 bridge plane.
D3D11 service A pure-zones commit opens the existing _SRGB-view compose target only when some zone/Local2D source is not _SRGB. All-_SRGB commits stay byte-identical (passthrough).

The legacy hatch DXR_COLOR_LEGACY_UNORM_ENCODED=1 keeps every old path.

Measured (win box)

Test app: cube_handle_*_win, DXR_LOCAL2D_PANEL=1, opaque checker authored 40/235. Captured with a screen grab / PrintWindow, because Local2D is flattened after the atlas.

backend _SRGB panel UNORM panel UNORM + hatch
D3D11 40/235 110/246 (one encode) 40/235
D3D12 40/235 110/246 40/235
GL 40/235 110/246 40/235
vk_native (split path) 40/235 110/246 40/235
  • vk_native without the split: not measured. On this box the split always engages, so that path didn't run.
  • D3D11 service pure-zones path: verified by eye by David on the panel, 2026-10-03. The installed shell v2.3.2 ran on the dev service, with cube_zones_d3d11_win under DISPLAYXR_ZONES=1:
  • Apps first: cleared.
    • displayxr-unity-samples v2.3.0 is published.
    • dfattal/lenovo-unity-avatar#17 is merged at upm/v2.21.0, and LeiaInc/lenovo-avatar main is fast-forwarded to it.

The test apps gain a DXR_SWAPCHAIN_ENCODING=unorm override for the panel/strip, the same switch displayxr-common uses.

🤖 Generated with Claude Code

dfattal and others added 8 commits October 2, 2026 18:20
…ed (#1795)

The over-flatten and the 2D-under backdrop flatten sampled every Local2D
layer through the non-decoding SRV into a UNORM scratch, so the declared
format was ignored (a true-linear UNORM layer rendered too dark).

Both scratches are now TYPELESS with a UNORM SRV (every reader, and the
bridge plane, still sees ENCODED bytes) and an _SRGB RTV for the flatten.
The flatten samples through the view matching its target: format-honest
into an _SRGB RTV, non-decoding under the legacy hatch.

Measured (cube_handle_d3d11_win, DXR_LOCAL2D_PANEL=1, opaque checker
authored 40/235): _SRGB panel 40/235; UNORM panel 110/246 (one encode);
UNORM under DXR_COLOR_LEGACY_UNORM_ENCODED=1 40/235.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l UNORM

A/B knob for the Local2D colour contract (#1795), the same switch
displayxr-common uses for its swapchains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1795)

The over-flatten and the 2D-under backdrop flatten sampled every Local2D
layer through the non-decoding bind (GL_SKIP_DECODE_EXT) into a GL_RGBA8
scratch, so the declared format was ignored (a true-linear UNORM layer
rendered too dark).

Both flattens now draw into a private GL_SRGB8_ALPHA8 target with
GL_FRAMEBUFFER_SRGB on for the flatten pass only (previous enable
restored), sampling each layer format-honestly (gl_bind_layer_source
compose=true: an _SRGB source decodes, GL_RGBA8 is read as linear), and
publish into the unchanged GL_RGBA8 scratches with glCopyImageSubData --
the compose target's #1610 pattern. Every reader (post-weave masked
composite, #885 diag dump, DP set_background_2d) still sees ENCODED bytes.
Legacy hatch, no glCopyImageSubData, or an incomplete sRGB attachment
keep today's direct non-decoding flatten. A test pins the new publish as
a raw copy, never a draw.

Measured (cube_handle_gl_win, DXR_LOCAL2D_PANEL=1, opaque checker
authored 40/235): (hardware A/B pending).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed (#1795)

The over-flatten and the 2D-under backdrop flatten sampled every Local2D
layer through the non-decoding view into a UNORM scratch, so the declared
format was ignored (a true-linear UNORM layer rendered too dark).

Both scratches are now TYPELESS with an _SRGB RTV for the flatten; every
reader still sees ENCODED bytes through the UNORM member (the masked
composite resolves the typeless SRV to UNORM, the DP already views the
backdrop as R8G8B8A8_UNORM, and the bridge / deposit planes stay UNORM
chains fed by a same-family copy, #1663). The flatten samples through the
view matching its target: format-honest into an _SRGB RTV (with _SRGB
PSO twins, since D3D12 bakes the RTV format), non-decoding under the
legacy hatch.

Measured (cube_handle_d3d12_win, DXR_LOCAL2D_PANEL=1): (hardware A/B pending)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…clared (#1795)

The over-flatten and the 2D-under backdrop flatten sampled every Local2D
layer through the non-decoding view into a UNORM scratch, so the declared
format was ignored (a true-linear UNORM layer rendered too dark).

Both scratches are now B8G8R8A8_UNORM + MUTABLE_FORMAT with a
{UNORM,_SRGB} format list (the #1799 compose-target shape): the UNORM
view is what every reader samples, so they still see ENCODED bytes, and
the flatten renders through an _SRGB view with a second flatten render
pass + pipeline pair (vk_local2d_composite_init_srgb_flatten, opt-in, so
the Linux/macOS/Android multi-compositor callers are unchanged). The
source view follows the target: format-honest (true view) into an _SRGB
attachment, non-decoding into a UNORM one. The legacy hatch never builds
the _SRGB pipelines and keeps the old path exactly; so does the
weave-on-scanout split, whose bridge planes are typed-UNORM D3D11
textures.

Measured (hardware A/B pending): cube_handle_vk_win, DXR_LOCAL2D_PANEL=1,
opaque checker authored 40/235 -- expect _SRGB panel 40/235; UNORM panel
110/246 (one encode); UNORM under DXR_COLOR_LEGACY_UNORM_ENCODED=1 40/235.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…SRGB (#1795)

A pure-zones / Local2D commit never reached the compose decision (it lives
on the projection write path), so every one was a passthrough: a UNORM
zone or Local2D source was sampled raw into the encoded atlas and its
declared format ignored (a true-linear UNORM panel came out too dark).

service_composite_zones_frame() now decides for a commit with no
projection layer: when some sampled source is not _SRGB and the legacy
hatch is off, it opens the same private _SRGB-view compose target as a
composing projection commit (client_ensure_compose_target, the same
!compose_was_active || !compose_mirrors_atlas seed, under the
immediate_ctx_mutex the pass already holds), so zones_resolve_src_srv()
takes its format-honest branch, the blends run in linear, and the
existing end-of-commit whole-atlas publish copies the result. An
all-_SRGB commit stays the byte-identical passthrough (no compose target,
no extra copy). compose_was_active is now recorded on every painting
pure-zones commit, so a passthrough one forces the next re-seed, and a
composing Local2D-only commit stamps atlas_holds_srgb_bytes (encoded).

ADR-044 §7. Mixed projection + zones / Local2D commits already composed
(has_local_2d counts as an extra layer); unchanged.

Measured: (hardware A/B pending)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Local2D strip UNORM

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1795)

Windows VK always runs the weave-on-scanout split on the hybrid box
(ADR-039 same-adapter engage), where the over-flatten and the 2D-under
backdrop render straight into the bridge planes. Those are typed-UNORM
D3D11 textures with no _SRGB view, so 8b00d0283 left that path
byte-passthrough and a UNORM panel still read 40/235.

The split now flattens into the private MUTABLE scratch through its
_SRGB view, exactly as the non-split path does, and vkCmdCopyImage's
the region into the cleared plane: a raw texel copy between two
B8G8R8A8 images (never a blit), so the plane carries the same ENCODED
bytes. The copy is recorded in the plane pass's own command buffer, so
it sits inside the existing plane-write window (timeline signal/wait,
keyed-mutex timing-only ordering #1274) and, like the direct flatten,
runs on app frames only (#868/#875). The plane's VkFormat is now
exposed by comp_vk_deposit_plane_get; anything but B8G8R8A8, the legacy
hatch, or a missing scratch falls back to the direct plane flatten.

Measured (hardware A/B pending): cube_handle_vk_win, DXR_LOCAL2D_PANEL=1,
opaque checker authored 40/235 -- expect _SRGB panel 40/235; UNORM panel
110/246; UNORM under DXR_COLOR_LEGACY_UNORM_ENCODED=1 40/235.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dfattal

dfattal commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

Numeric follow-up (with the #1810 capture fix): the D3D11 service pure-zones path is now measured, not just eyeballed. In cube_zones_d3d11_win's per-client atlas over forced IPC, the UNORM-strip leg reads exactly one encode above the _SRGB leg: greys 60→133 and 210→234, red (38,8,8)→(108,50,50).

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