Skip to content

vk_native window-space + all-backend Local2D are not format-honest (ADR-044 §7) #1795

Closed

Description

@dfattal

Two composition paths ignore the swapchain's declared colour format. They show up as open rows in ADR-044 §7, added in #1794.

1. vk_native window-space layers are passthrough

vk_hud_blend (src/xrt/auxiliary/vk/vk_hud_blend.c:366) samples every window-space source through a hard-coded VK_FORMAT_R8G8B8A8_UNORM view and blends into the atlas in encoded space.

Measured with the windowspace_handle_*_win probes (opaque layers authored at byte 38):

backend _SRGB swapchain UNORM swapchain
D3D11 / D3D12 / GL 38 108 (one encode), honest
vk_native 38 38, format ignored

On the model viewer's common button bar, the atlas is byte-identical with swapchain 37 vs 43, and a 55 % black fill lands at exactly 0.45 × the background's encoded value. Linear-light blending would give a visibly lighter value.

Side bug: because the view is hard-coded R8G8B8A8, a B8G8R8A8_* window-space swapchain would be sampled with R and B swapped.

2. Local2D flatten is passthrough on every in-process backend

Each backend samples the Local2D layer through a non-decoding view into a UNORM scratch:

  • D3D11: comp_d3d11_swapchain_get_srv, not get_compose_srv
  • D3D12: comp_d3d12_swapchain_sample_format
  • GL: gl_bind_layer_source(…, compose=false)
  • vk_native: get_image_view, not get_true_image_view

UNORM and _SRGB Local2D therefore render identically. A true-linear UNORM Local2D renders too dark. Local2D is flattened after the atlas, so the atlas capture (ADR-044 §5) cannot verify it; a post-weave or plane capture is needed.

Ordering: apps first, then the runtime

Making either path honest washes out every app that still declares UNORM with encoded bytes. Known state:

  • Already _SRGB:
  • Still UNORM: displayxr-shell-pvt's macOS surfaces (hard-coded 37). Any third-party app is unknown.

Proposed fix

  1. vk_native window-space: sample through the swapchain's format-honest view (get_true_image_view, or the UNORM↔_SRGB sibling matching the declared format) and blend in the private _SRGB target, as the projection path does since compositor blends layers in sRGB-encoded space; OpenXR expects linear-space blending (CTS SourceAlphaBlending) #1610. That also removes the R/B swap.
  2. Local2D on all four backends: switch the flatten to the format-honest view, with an _SRGB scratch or a matched encode.
  3. Add a windowspace_handle_vk_win / Local2D row to the colour regression checks so the passthrough can't silently come back.

Verification

Re-run the probe A/B with and without DXR_SWAPCHAIN_ENCODING=unorm. After the fix vk_native should match D3D11 (38 / 108). Then confirm that displayxr-unity (already _SRGB) is a no-op.

Activity

  1. dfattal commented on Oct 2, 2026

    @dfattal
    CollaboratorAuthor

    Downstream dependency (shell, macOS). The macOS service composites the shell's chrome, cursor, focus ring, launcher, splash and taskbar through the same vk_hud_blend (comp_multi_system.c chrome_blend / shared_chrome_blend). All of those are UNORM 37 holding CoreGraphics (display-referred) bytes. They look right today only because vk_hud_blend ignores the format.

    So the release that fixes this issue washes out the macOS shell unless displayxr-shell-pvt#121 ships alongside it, with an _SRGB flip gated on this release's version, the same pattern as #119 and runtime v2.21.13 on Windows.

    Plan the fix as:

    1. Land the runtime change in a release.
    2. Ship the shell with the version-gated flip.
    3. Verify both on a Mac panel.

    Also, a UNORM view of an _SRGB image needs MUTABLE_FORMAT. Keep that in mind if the fix keeps any non-decoding view.

  2. dfattal commented on Oct 2, 2026

    @dfattal
    CollaboratorAuthor

    Window-space part landed in #1799 (vk_native in-process + macOS service decorations), shipping in v2.23.0 with displayxr-shell-pvt#122. Still open here: the Local2D flatten honesty (all four backends). It is deferred until the Unity samples and any other UNORM-Local2D apps are re-released on _SRGB. The macOS content-pass darkening is tracked separately in #1800.

  3. dfattal commented on Oct 3, 2026

    @dfattal
    CollaboratorAuthor

    Local2D half landed in #1809 (D3D11, D3D12, GL and vk_native in-process, all HW-measured; the D3D11 service pure-zones path was verified by eye). Apps-first was cleared beforehand: Unity samples v2.3.0 and both Lenovo Unity builds are on displayxr-unity v2.21.0. Ships with the next runtime release. The remaining ADR-044 §7 items are tracked in #1800 (macOS content pass) and #1810 (IPC capture).

  4. dfattal commented on Oct 3, 2026

    @dfattal
    CollaboratorAuthor

    Both halves have landed: window-space in #1799 (v2.23.0) and Local2D in #1809 (next release). Follow-ups: #1800 (macOS content pass) and #1810 (IPC capture).

  5. dfattal commented on Oct 6, 2026

    @dfattal
    CollaboratorAuthor

    Unity no-op confirmation for #1809: PASSED on runtime v2.26.0 (Windows Leia panel, desktop-avatar from displayxr-unity-samples v2.3.0, D3D12, standalone; judged by eye by David):

    • A, displayxr-unity v2.21.0 DLL (_SRGB primary: swapchain 29 with Unity rendering into 28 and raw-copying; Local2D _SRGB via the D3D12 bridge): the speech bubble and the whole frame look correct.
    • B, the same player with a pre-guard(abi): compile-time tripwire on the plug-in DP-vtable ABI + ADR-020 #348 plugin DLL (UNORM primary 28 holding encoded bytes, UNORM Local2D): the entire frame washes out, 3D and the 2D bubble alike, which is the expected double encode.

    So Unity apps on plugin ≥ v2.21.0 are unaffected by honest Local2D, and pre-v2.21 Unity builds now wash out everywhere: rebuild on ≥ v2.21.0. Released at that level: displayxr-unity v2.21.0, displayxr-unity-samples v2.3.0, and lenovo-avatar main (dfattal + LeiaInc, fbe2d04). Not covered: the under-shell (service) leg with a Unity app, because desktop-avatar isn't installed or registered on this box.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions