Skip to content

vk_native: honour the premultiplied flag on window-space layers (#1786) - #1787

Merged
dfattal merged 2 commits into
mainfrom
fix/vk-window-space-premul-1786
Oct 2, 2026
Merged

dfattal merged 2 commits into
mainfrom
fix/vk-window-space-premul-1786

Conversation

@dfattal

@dfattal dfattal commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1786.

Problem

vk_native stamps window-space layers (XrCompositionLayerWindowSpaceDXR) into the atlas through vk_hud_blend. Its colour blend was always straight alpha (SRC_ALPHA / ONE_MINUS_SRC_ALPHA), and it ignored the layer flags. A layer submitted with premultiplied bytes (SOURCE_ALPHA_BIT clear) came out as rgb·a², so translucent pixels and antialiased edges were too dark. #1784 now composites the HUD's alpha as well, so in a transparent session those panels show over the desktop, darkened.

D3D11 (comp_d3d11_renderer.cpp) and D3D12 (render_window_space_layer) already choose the blend from the flags: premultiplied when SOURCE_ALPHA_BIT is clear, straight when it is set. They deliberately do not use comp_layer_blend_mode() (#1599).

Fix

  • vk_hud_blend builds a second pipeline, pipeline_premul, in the same instance. It is identical to the existing one except colour src ONE. Both share the render pass, the pipeline/descriptor layouts and the per-VkImage view/descriptor cache. The new vk_hud_blend_draw_no_layout_ex(..., bool premultiplied) picks the pipeline for each draw.
  • vk_native's window-space pass uses the D3D11/D3D12 rule: premultiplied = !(flags & SOURCE_ALPHA_BIT).
  • The alpha blend from vk_native: composite window-space alpha into the atlas (#1780) #1784 is unchanged in both pipelines (srcAlpha ONE, dstAlpha ONE_MINUS_SRC_ALPHA, A written when write_alpha).
  • vk_hud_blend_init(), vk_hud_blend_draw() and vk_hud_blend_draw_no_layout() still use the straight pipeline, so the comp_multi_system.c callers (hud_blend, chrome_blend, shared_chrome_blend) behave exactly as before. The only change for them is one extra pipeline object at init.
  • GL (comp_gl_compositor.cpp) had the same fixed glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) on its window-space pass. It now uses the same rule (one line).

Composes with #1783

This is designed to land next to #1783 (forget a destroyed image's cached HUD view). It uses one vk_hud_blend instance with two pipelines, not a second instance, so #1783's vk_compositor_forget_dead_window_space_images() still drains the only cache. Blend mode belongs to the layer, not the image, so the cache keys stay valid. The new _ex declaration sits before vk_hud_blend_draw_no_layout in the header, away from where #1783 adds vk_hud_blend_forget_image. I checked that this commit rebases cleanly onto the #1783 head.

Who is affected (submitter audit)

Submitter Window-space flags Effect of this PR
displayxr-common xr_window_space_hud.cpp (v2.24.0). Every in-tree test app's HUD (cube_handle_vk_macos, cube_zones_vk_macos, the windowspace_handle_* probes, GL/Metal cube apps) SOURCE_ALPHA_BIT none: still straight
displayxr-unity window-space UI (ps_fill_wsui_layer, shared by every graphics backend including Vulkan) SOURCE_ALPHA_BIT none: still straight
Any app submitting layerFlags == 0 on vk_native or GL 0 now premultiplied (matches D3D11/D3D12)

No in-tree or Unity window-space submitter sends flags == 0, so nothing that ships today changes appearance. The layerFlags = 0 // premultiplied bytes panels in the cube apps are Local2D layers (XRT_LAYER_LOCAL_2D), not window-space. This PR doesn't touch that path.

Parity notes (not changed here)

  • Metal (comp_metal_compositor.m, window-space pass ~L3993): draws with projection_pipeline, which has blendingEnabled = NO. The HUD overwrites its rect instead of blending, which is a different bug from this one. proj_premult_pipeline/proj_straight_pipeline already exist, so a fix would be small, but it changes macOS HUD appearance and needs its own eyeball. Left for a follow-up.
  • D3D12: already matches D3D11.
  • Possible submitter mismatch, also not changed: hud_renderer_macos.mm draws into a kCGImageAlphaPremultipliedLast bitmap but xr_window_space_hud.cpp flags the layer SOURCE_ALPHA_BIT (straight). This is consistent across backends, so it is not a vk_native issue.

Testing

  • macOS (Apple Silicon, MoltenVK): a Debug runtime build (cmake + ninja, same options as build_macos.sh, service off) compiles cleanly with no new warnings in the touched files. clang-format applied to the changed lines.
  • Ran cube_handle_vk_macos against this build with the vk_native file-trigger capture ($TMPDIR/displayxr_atlas_trigger). The session started, rendered and captured normally. But the app's HUD is hidden by default and only toggles with Shift+Tab, which I could not send from the harness, so that run never submitted a window-space layer. It does not exercise this change.
  • Confirmed this commit rebases cleanly onto vk_native: forget a destroyed window-space image's cached HUD view (#1782) #1783's head.

Not tested

  • No run reached the window-space stamp, so neither pipeline has been seen drawing, and premultiplied pipeline creation on MoltenVK hasn't been run (it is lazy, on the first window-space layer).
  • No flags == 0 window-space submitter exists to A/B the premultiplied path visually, on any platform.
  • Linux and Windows Vulkan not run. GL change compiled only, not run.

🤖 Generated with Claude Code

vk_hud_blend stamped every window-space layer with a straight-alpha colour
blend (SRC_ALPHA / ONE_MINUS_SRC_ALPHA) and ignored the layer flags, so a
layer submitted with premultiplied bytes (SOURCE_ALPHA_BIT clear) came out
as rgb*a^2: translucent pixels and antialiased edges too dark. Since #1784
composites the HUD's alpha too, those panels now show over the desktop in
a transparent session, darkened.

vk_hud_blend now builds a second pipeline in the same instance, identical
except for colour src ONE, and vk_hud_blend_draw_no_layout_ex() picks one
per draw. Both share the render pass, layouts and the per-image cache, so
cache eviction keeps working on the one instance. vk_native picks it with
the D3D11/D3D12 window-space rule: premultiplied when SOURCE_ALPHA_BIT is
clear, straight when set. Deliberately not comp_layer_blend_mode() (#1599).
The alpha blend from #1784 is unchanged.

The GL compositor's window-space pass had the same fixed straight blend;
it gets the same one-line rule.

vk_hud_blend_init(), vk_hud_blend_draw() and vk_hud_blend_draw_no_layout()
keep the straight pipeline, so the comp_multi_system.c callers are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dfattal
dfattal force-pushed the fix/vk-window-space-premul-1786 branch from 2f46f08 to 603d422 Compare October 2, 2026 07:32
The #1621 guard pins each backend's direct reads of the source-alpha
bit. #1786 gives GL's window-space pass the same premul/straight
decision D3D11 and D3D12 already have an allowance for (#1581), so GL
moves from 0 to 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dfattal
dfattal merged commit 77ca37a into main Oct 2, 2026
40 checks passed
@dfattal
dfattal deleted the fix/vk-window-space-premul-1786 branch October 2, 2026 07:59
dfattal added a commit that referenced this pull request Oct 2, 2026
The Metal window-space pass (XrCompositionLayerWindowSpaceDXR) drew with
projection_pipeline, which has blending disabled, so a HUD overwrote its
whole rectangle in the atlas, colour AND alpha, instead of compositing
over the 3D content.

Pick the blended projection pipeline per layer, matching the rule that
D3D11/D3D12, vk_native and GL use (#1786/#1787): SOURCE_ALPHA_BIT clear
means premultiplied (colour src ONE), set means straight (colour src
SOURCE_ALPHA); colour dst and the alpha "over" (ONE /
ONE_MINUS_SOURCE_ALPHA) are shared, so atlas alpha survives for
transparent sessions (#225/#1784). proj_premult_pipeline and
proj_straight_pipeline already carry exactly those factors with the
same shaders and depth attachment, so no new pipeline is needed.
projection_pipeline's REPLACE for the base blit is untouched.

Verified on macOS (cube_handle_metal_macos, sim_display anaglyph,
atlas capture): before, the HUD rect is an opaque grey panel hiding the
grid and writes atlas alpha 128 over opaque content; after, the grid
shows through and atlas alpha stays 255. Premultiplied (flags 0) case
checked with a temporary local flag flip: translucent, no halo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal added a commit that referenced this pull request Oct 2, 2026
…1789)

* fix(metal): blend window-space layers instead of overwriting (#1788)

The Metal window-space pass (XrCompositionLayerWindowSpaceDXR) drew with
projection_pipeline, which has blending disabled, so a HUD overwrote its
whole rectangle in the atlas, colour AND alpha, instead of compositing
over the 3D content.

Pick the blended projection pipeline per layer, matching the rule that
D3D11/D3D12, vk_native and GL use (#1786/#1787): SOURCE_ALPHA_BIT clear
means premultiplied (colour src ONE), set means straight (colour src
SOURCE_ALPHA); colour dst and the alpha "over" (ONE /
ONE_MINUS_SOURCE_ALPHA) are shared, so atlas alpha survives for
transparent sessions (#225/#1784). proj_premult_pipeline and
proj_straight_pipeline already carry exactly those factors with the
same shaders and depth attachment, so no new pipeline is needed.
projection_pipeline's REPLACE for the base blit is untouched.

Verified on macOS (cube_handle_metal_macos, sim_display anaglyph,
atlas capture): before, the HUD rect is an opaque grey panel hiding the
grid and writes atlas alpha 128 over opaque content; after, the grid
shows through and atlas alpha stays 255. Premultiplied (flags 0) case
checked with a temporary local flag flip: translucent, no halo.

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

* tests: allow Metal's window-space SOURCE_ALPHA_BIT read (#1788)

Same allowance D3D11/D3D12/GL have for the Local2D / window-space
channel; the #1621 guard moves Metal from 0 to 1.

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

---------

Co-authored-by: Claude Opus 5.5 <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.

vk_native: window-space layers always blend as straight alpha (premultiplied HUDs darken; D3D11 honours the flag)

1 participant