Skip to content

vk_native: composite window-space alpha into the atlas (#1780) - #1784

Merged
dfattal merged 1 commit into
DisplayXR:mainfrom
byungjul:fix/vk-hud-blend-alpha
Oct 2, 2026
Merged

dfattal merged 1 commit into
DisplayXR:mainfrom
byungjul:fix/vk-hud-blend-alpha

Conversation

@byungjul

@byungjul byungjul commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1780.

Problem

In a transparent session on the Vulkan native compositor, a window-space layer (HUD) was visible only where 3D content already sat under it. On Linux the HUD showed inside the avatar's silhouette and was see-through everywhere else.

vk_hud_blend stamps the HUD into the atlas with RGB writes only (srcAlpha ZERO, dstAlpha ONE, no A in colorWriteMask). In a transparent session the atlas alpha decides where the window is see-through. Outside the silhouette it is 0, so the HUD's colour was written but stayed fully transparent.

Fix

Behaviour change: this applies to every vk_native session, not only transparent ones. Under "over" the alpha only grows (a = a_src + a_dst·(1 − a_src)), so an opaque atlas (alpha 1) is unchanged. I didn't key it on the session's transparency because that can switch on mid-session (lazy transparency) and this pipeline is built once. That affects Linux, and macOS through MoltenVK. Windows Vulkan apps use vk_native too. I only tested Linux.

Testing

Ubuntu 26.04, RTX 4090 Laptop, Leia DS1, Leia SR plug-in 2.7.6. App: the lenovo-avatar Unity player (transparent) with displayxr-unity window-space UI on Vulkan. Two HUD panels, opened with Shift+Tab.

Runtime Result
v2.21.11 HUDs visible only where they overlap the avatar
v2.21.11 + this change HUDs visible over the desktop too, and their clicks work; the background around the avatar stays transparent

The hardware test used v2.21.11 with this patch plus #1783 (the patches apply cleanly). The branch is on current main and builds there (build_linux.sh, Release, no new warnings).

🤖 Generated with Claude Code

vk_hud_blend stamped the HUD with RGB writes only (src alpha ZERO, dst
alpha ONE). In a transparent session the atlas alpha is the window's
transparency, so a window-space layer was see-through everywhere no 3D
content sat under it: on Linux the HUD showed only inside the avatar's
silhouette.

vk_hud_blend_init_ex(..., write_alpha) adds the option to composite alpha
"over" as well (src ONE, dst ONE_MINUS_SRC_ALPHA, all channels written),
the blend D3D11 already uses for window-space layers (DisplayXR#225). vk_native's
window-space blend turns it on. Alpha only grows under "over", so an opaque
atlas (alpha 1) is unchanged. It is not keyed on the session being
transparent: that can switch on mid-session and the pipeline is built once.

vk_hud_blend_init() keeps the RGB-only blend, so comp_multi_system's
hud/chrome blends are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@byungjul
byungjul requested a review from a team as a code owner October 2, 2026 05:55

@dfattal dfattal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — small, correct, well-scoped.

  • D3D11 parity confirmed: the new blend (colour SRC_ALPHA/INV_SRC_ALPHA, alpha ONE/INV_SRC_ALPHA, all channels written) matches comp_d3d11_renderer.cpp blend_alpha (#225) exactly.
  • Unconditional enable is safe: alpha only grows under "over". The lazy-transparency alpha probe runs after the window-space stamp (comp_vk_native_compositor.c stamp → vk_lazy_transparency_frame), so a HUD can never create false alpha<1, and a HUD over a transparent region is now correctly counted as content. Opaque sessions present opaque. window_space_blend has one call site; comp_multi_system.c keeps the RGB-only blend.
  • Output convention matches the present: a straight-alpha HUD source through this blend yields a valid premultiplied pixel, which is what the transparent target (COMPOSITE_ALPHA_PRE_MULTIPLIED) expects.

Non-blocking follow-up: vk_hud_blend always treats the source as straight alpha and ignores the layer's XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT (OpenXR default is premultiplied), so a premultiplied HUD gets rgb·a² — darker translucent pixels. D3D11 has the same behaviour and RGB already did this, but it's now more visible over the desktop. Could pick src factor from the layer flags like comp_vk_native_renderer.c does for projection layers.

Not tested by me: macOS (MoltenVK) / Windows Vulkan transparent sessions — the monotone-alpha argument covers opaque ones.

@dfattal
dfattal merged commit feec78a into DisplayXR:main Oct 2, 2026
34 checks passed
dfattal added a commit that referenced this pull request Oct 2, 2026
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 added a commit that referenced this pull request Oct 2, 2026
… (#1787)

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

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>

* tests: allow GL's window-space SOURCE_ALPHA_BIT read (#1786)

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>

---------

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

Vulkan native: window-space layers are invisible outside the 3D content in transparent sessions (HUD blend never writes alpha)

2 participants