vk_native: honour the premultiplied flag on window-space layers (#1786) - #1787
Merged
Merged
Conversation
This was referenced 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
force-pushed
the
fix/vk-window-space-premul-1786
branch
from
October 2, 2026 07:32
2f46f08 to
603d422
Compare
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>
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.
Fixes #1786.
Problem
vk_native stamps window-space layers (
XrCompositionLayerWindowSpaceDXR) into the atlas throughvk_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_BITclear) came out asrgb·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 whenSOURCE_ALPHA_BITis clear, straight when it is set. They deliberately do not usecomp_layer_blend_mode()(#1599).Fix
vk_hud_blendbuilds a second pipeline,pipeline_premul, in the same instance. It is identical to the existing one except colour srcONE. Both share the render pass, the pipeline/descriptor layouts and the per-VkImageview/descriptor cache. The newvk_hud_blend_draw_no_layout_ex(..., bool premultiplied)picks the pipeline for each draw.premultiplied = !(flags & SOURCE_ALPHA_BIT).srcAlpha ONE,dstAlpha ONE_MINUS_SRC_ALPHA, A written whenwrite_alpha).vk_hud_blend_init(),vk_hud_blend_draw()andvk_hud_blend_draw_no_layout()still use the straight pipeline, so thecomp_multi_system.ccallers (hud_blend,chrome_blend,shared_chrome_blend) behave exactly as before. The only change for them is one extra pipeline object at init.comp_gl_compositor.cpp) had the same fixedglBlendFunc(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_blendinstance with two pipelines, not a second instance, so #1783'svk_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_exdeclaration sits beforevk_hud_blend_draw_no_layoutin the header, away from where #1783 addsvk_hud_blend_forget_image. I checked that this commit rebases cleanly onto the #1783 head.Who is affected (submitter audit)
displayxr-commonxr_window_space_hud.cpp(v2.24.0). Every in-tree test app's HUD (cube_handle_vk_macos,cube_zones_vk_macos, thewindowspace_handle_*probes, GL/Metal cube apps)SOURCE_ALPHA_BITdisplayxr-unitywindow-space UI (ps_fill_wsui_layer, shared by every graphics backend including Vulkan)SOURCE_ALPHA_BITlayerFlags == 0on vk_native or GLNo in-tree or Unity window-space submitter sends
flags == 0, so nothing that ships today changes appearance. ThelayerFlags = 0 // premultiplied bytespanels 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)
comp_metal_compositor.m, window-space pass ~L3993): draws withprojection_pipeline, which hasblendingEnabled = NO. The HUD overwrites its rect instead of blending, which is a different bug from this one.proj_premult_pipeline/proj_straight_pipelinealready exist, so a fix would be small, but it changes macOS HUD appearance and needs its own eyeball. Left for a follow-up.hud_renderer_macos.mmdraws into akCGImageAlphaPremultipliedLastbitmap butxr_window_space_hud.cppflags the layerSOURCE_ALPHA_BIT(straight). This is consistent across backends, so it is not a vk_native issue.Testing
cmake+ninja, same options asbuild_macos.sh, service off) compiles cleanly with no new warnings in the touched files. clang-format applied to the changed lines.cube_handle_vk_macosagainst 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.Not tested
flags == 0window-space submitter exists to A/B the premultiplied path visually, on any platform.🤖 Generated with Claude Code