diff --git a/src/xrt/auxiliary/vk/vk_hud_blend.c b/src/xrt/auxiliary/vk/vk_hud_blend.c index bf5cf71e0..be369f2e9 100644 --- a/src/xrt/auxiliary/vk/vk_hud_blend.c +++ b/src/xrt/auxiliary/vk/vk_hud_blend.c @@ -324,6 +324,16 @@ vk_hud_blend_init_ex(struct vk_hud_blend *blend, return false; } + // #1786: premultiplied variant, same state but colour src ONE. Picked per + // draw by vk_hud_blend_draw_no_layout_ex(); everything else uses the + // straight pipeline above. + blend_att.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + ret = vk->vkCreateGraphicsPipelines(vk->device, VK_NULL_HANDLE, 1, &pipe_ci, NULL, &blend->pipeline_premul); + if (ret != VK_SUCCESS) { + U_LOG_E("[HUD blend] Failed to create premultiplied pipeline: %d", ret); + return false; + } + blend->initialized = true; return true; } @@ -527,6 +537,23 @@ vk_hud_blend_draw_no_layout(struct vk_hud_blend *blend, int32_t dst_y, uint32_t dst_w, uint32_t dst_h) +{ + vk_hud_blend_draw_no_layout_ex(blend, vk, cmd, fb, fb_w, fb_h, hud_image, dst_x, dst_y, dst_w, dst_h, false); +} + +void +vk_hud_blend_draw_no_layout_ex(struct vk_hud_blend *blend, + struct vk_bundle *vk, + VkCommandBuffer cmd, + VkFramebuffer fb, + uint32_t fb_w, + uint32_t fb_h, + VkImage hud_image, + int32_t dst_x, + int32_t dst_y, + uint32_t dst_w, + uint32_t dst_h, + bool premultiplied) { if (!blend->initialized || hud_image == VK_NULL_HANDLE || fb == VK_NULL_HANDLE || dst_w == 0 || dst_h == 0) { @@ -551,7 +578,8 @@ vk_hud_blend_draw_no_layout(struct vk_hud_blend *blend, VkRect2D scissor = {{dst_x, dst_y}, {dst_w, dst_h}}; vk->vkCmdSetScissor(cmd, 0, 1, &scissor); - vk->vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, blend->pipeline); + vk->vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, + premultiplied ? blend->pipeline_premul : blend->pipeline); vk->vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, blend->pipe_layout, 0, 1, &desc_set, 0, NULL); vk->vkCmdDraw(cmd, 3, 1, 0, 0); @@ -598,6 +626,9 @@ vk_hud_blend_fini(struct vk_hud_blend *blend, struct vk_bundle *vk) if (blend->pipeline != VK_NULL_HANDLE) { vk->vkDestroyPipeline(vk->device, blend->pipeline, NULL); } + if (blend->pipeline_premul != VK_NULL_HANDLE) { + vk->vkDestroyPipeline(vk->device, blend->pipeline_premul, NULL); + } if (blend->render_pass != VK_NULL_HANDLE) { vk->vkDestroyRenderPass(vk->device, blend->render_pass, NULL); } diff --git a/src/xrt/auxiliary/vk/vk_hud_blend.h b/src/xrt/auxiliary/vk/vk_hud_blend.h index 5bd97ac6b..cfcd94b0b 100644 --- a/src/xrt/auxiliary/vk/vk_hud_blend.h +++ b/src/xrt/auxiliary/vk/vk_hud_blend.h @@ -45,7 +45,8 @@ extern "C" { struct vk_hud_blend { VkRenderPass render_pass; - VkPipeline pipeline; + VkPipeline pipeline; //!< Straight-alpha colour blend (src SRC_ALPHA). + VkPipeline pipeline_premul; //!< Premultiplied colour blend (src ONE), #1786. VkPipelineLayout pipe_layout; VkDescriptorSetLayout desc_layout; VkDescriptorPool desc_pool; @@ -138,6 +139,30 @@ vk_hud_blend_draw(struct vk_hud_blend *blend, uint32_t dst_w, uint32_t dst_h); +/*! + * vk_hud_blend_draw_no_layout(), choosing the colour blend per draw (#1786). + * + * @p premultiplied false is the straight-alpha blend every other draw uses + * (colour src SRC_ALPHA, dst ONE_MINUS_SRC_ALPHA). True treats the source as + * premultiplied (colour src ONE, dst ONE_MINUS_SRC_ALPHA). The alpha blend is + * whatever the blend was initialized with, in both cases. + * + * @ingroup aux_vk + */ +void +vk_hud_blend_draw_no_layout_ex(struct vk_hud_blend *blend, + struct vk_bundle *vk, + VkCommandBuffer cmd, + VkFramebuffer fb, + uint32_t fb_w, + uint32_t fb_h, + VkImage hud_image, + int32_t dst_x, + int32_t dst_y, + uint32_t dst_w, + uint32_t dst_h, + bool premultiplied); + /*! * Draw without managing target image layout transitions or framebuffer * caching. Use this when blending into a non-swapchain target (e.g. an diff --git a/src/xrt/compositor/gl/comp_gl_compositor.cpp b/src/xrt/compositor/gl/comp_gl_compositor.cpp index d1e122beb..32b6411ef 100644 --- a/src/xrt/compositor/gl/comp_gl_compositor.cpp +++ b/src/xrt/compositor/gl/comp_gl_compositor.cpp @@ -6244,7 +6244,11 @@ gl_compositor_layer_commit_locked(struct xrt_compositor *xc, xrt_graphics_sync_h glUseProgram(c->program_window_space); glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + // #1786: same rule as D3D11/D3D12/vk_native's window-space pass — + // SOURCE_ALPHA_BIT clear means premultiplied bytes, set means + // straight. Deliberately not comp_layer_blend_mode() (#1599). + bool ws_premultiplied = (layer->data.flags & XRT_LAYER_COMPOSITION_BLEND_TEXTURE_SOURCE_ALPHA_BIT) == 0; + glBlendFunc(ws_premultiplied ? GL_ONE : GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLint loc_ws_rect = glGetUniformLocation(c->program_window_space, "u_rect"); GLint loc_ws_tex = glGetUniformLocation(c->program_window_space, "u_texture"); diff --git a/src/xrt/compositor/vk_native/comp_vk_native_compositor.c b/src/xrt/compositor/vk_native/comp_vk_native_compositor.c index b31bd481f..3a502d26c 100644 --- a/src/xrt/compositor/vk_native/comp_vk_native_compositor.c +++ b/src/xrt/compositor/vk_native/comp_vk_native_compositor.c @@ -2622,6 +2622,12 @@ vk_compositor_render_window_space_into_atlas(struct comp_vk_native_compositor *c const struct xrt_layer_window_space_data *ws = &layer->data.window_space; uint32_t sc_index = ws->sub.image_index; + // #1786: same rule as D3D11/D3D12's window-space pass — + // SOURCE_ALPHA_BIT clear means premultiplied bytes, set means + // straight. Deliberately not comp_layer_blend_mode() (#1599): see + // the comment in comp_d3d11_renderer.cpp's window-space draw. + bool ws_premultiplied = (layer->data.flags & XRT_LAYER_COMPOSITION_BLEND_TEXTURE_SOURCE_ALPHA_BIT) == 0; + VkImage src_image = (VkImage)(uintptr_t) comp_vk_native_swapchain_get_image(xsc, sc_index); if (src_image == VK_NULL_HANDLE) { @@ -2699,9 +2705,9 @@ vk_compositor_render_window_space_into_atlas(struct comp_vk_native_compositor *c } } - vk_hud_blend_draw_no_layout(&c->window_space_blend, vk, cmd, - c->atlas_ws_fb, atlas_w, atlas_h, - src_image, dx, dy, (uint32_t)dw_i, (uint32_t)dh_i); + vk_hud_blend_draw_no_layout_ex(&c->window_space_blend, vk, cmd, c->atlas_ws_fb, atlas_w, + atlas_h, src_image, dx, dy, (uint32_t)dw_i, (uint32_t)dh_i, + ws_premultiplied); } // Source back to COLOR_ATTACHMENT_OPTIMAL so the app can rerender. diff --git a/tests/tests_comp_layer_view_camera.cpp b/tests/tests_comp_layer_view_camera.cpp index 2d1204105..88c4f004f 100644 --- a/tests/tests_comp_layer_view_camera.cpp +++ b/tests/tests_comp_layer_view_camera.cpp @@ -1134,7 +1134,10 @@ TEST_CASE("comp_layer_blend_mode: no backend reimplements the blend rule (#1621) const char *rel; uint32_t allowed_flag_reads; } backends[] = { - {"gl/comp_gl_compositor.cpp", 0}, + // #1786: GL's one allowance is the SAME Local2D / window-space + // channel D3D11's is (its own premul/straight decision, deliberately + // not comp_layer_blend_mode() -- see comp_d3d11_renderer.cpp). + {"gl/comp_gl_compositor.cpp", 1}, {"metal/comp_metal_compositor.m", 0}, // The Local2D / window-space channel, above. {"d3d11/comp_d3d11_renderer.cpp", 1},