Skip to content

Commit 5328e7e

Browse files
kode54claude
andcommitted
blur: bridge SDR/PQ in blend shader and FP16-back saved_pixels on HDR
Two HDR-output regressions surfaced after the zero-copy bypass and luminance multiplier work, both rooted in blur's two-input pipeline. 1. blend shader was super bright on HDR. wp comes from inner_content (EXT_LINEAR / SDR-relative linear, 1.0 == SDR reference white). bp comes from sampling the blurred copy of target_fb's bound FBO, which on HDR is PQ-linear (1.0 == 10000 nits). The blend then wrote into target_fb in PQ-linear without bridging the wp side, so SDR window content landed at 1.0 PQ-linear and showed as 10000-nit white. Add a luminance_multiplier uniform to the blur_blend fragment shader, scale wp.rgb by it, and have wf_blur_base::render compute the factor with wf::compute_luminance_multiplier(EXT_LINEAR, target_fb.get_output_transfer_function()) — 1.0 on SDR, 0.0203 on HDR. 2. White rectangles on the padded regions around blurred windows. schedule_instructions glBlitFramebuffers a snippet of target's bound FBO into saved_pixels->pixels, which was allocated as 8-bit RGBA. With the linear two-pass pipeline target's FBO is FP16, and on HDR it carries PQ-linear values that can exceed 1.0 for HDR sources. Blitting to RGBA8 clamped those to 1.0 == peak HDR white; on the way back into target's FBO they painted as solid bright white over the padding. Allocate saved_pixels with hdr_linear hint when target's transfer function is PQ so the snapshot is FP16 and round-trips losslessly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b57b921 commit 5328e7e

2 files changed

Lines changed: 17 additions & 1 deletion

File tree

plugins/blur/blur-base.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ precision highp float;
3535
3636
@builtin@
3737
uniform float sat;
38+
uniform float luminance_multiplier;
3839
uniform sampler2D bg_texture;
3940
4041
varying highp vec2 uvpos[2];
@@ -52,6 +53,9 @@ void main()
5253
vec4 bp = texture2D(bg_texture, uvpos[1]);
5354
bp = vec4(saturation(bp.rgb, sat), bp.a);
5455
vec4 wp = get_pixel(uvpos[0]);
56+
// wp comes from inner_content in EXT_LINEAR (SDR-relative) space; bp comes from the target FBO
57+
// which is PQ-linear on HDR outputs. Bring wp into the same domain as bp before blending.
58+
wp.rgb *= luminance_multiplier;
5559
vec4 c = clamp(4.0 * wp.a, 0.0, 1.0) * bp;
5660
gl_FragColor = wp + (1.0 - wp.a) * c;
5761
})";
@@ -277,6 +281,10 @@ void wf_blur_base::render(wf::gles_texture_t src_tex, wlr_box src_box, const wf:
277281
/* XXX: core should give us the number of texture units used */
278282
blend_program.uniform1i("bg_texture", 1);
279283
blend_program.uniform1f("sat", saturation_opt);
284+
// Bridge SDR-relative linear (src_tex / inner_content) into the target's domain. On HDR outputs
285+
// the bound FBO is PQ-linear; on SDR the multiplier is 1.0 so this is a no-op.
286+
blend_program.uniform1f("luminance_multiplier", wf::compute_luminance_multiplier(
287+
WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR, target_fb.get_output_transfer_function()));
280288

281289
blend_program.set_active_texture(src_tex);
282290
GL_CALL(glActiveTexture(GL_TEXTURE0 + 1));

plugins/blur/blur.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,15 @@ class blur_render_instance_t : public transformer_render_instance_t<blur_node_t>
174174
// Nodes below should re-render the padded areas so that we can sample from them
175175
damage |= padded_region;
176176

177-
saved_pixels->pixels.allocate(target.get_size());
177+
// saved_pixels is filled by glBlitFramebuffer'ing target's bound FBO. With wlroots GLES2's
178+
// linear two-pass pipeline that source FBO is FP16 — PQ-linear on HDR outputs, where HDR
179+
// window content reaches values > 1.0. An 8-bit RGBA backing would clamp those to 1.0 (peak
180+
// HDR white) and then blit-back would deposit stark white over the padded regions. Mirror
181+
// the source format on HDR by hinting hdr_linear so the saved_pixels buffer is FP16 too.
182+
const bool target_is_hdr = target.get_output_transfer_function() ==
183+
WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ;
184+
saved_pixels->pixels.allocate(target.get_size(), 1.0f,
185+
wf::buffer_allocation_hints_t{.hdr_linear = target_is_hdr});
178186

179187
wf::gles::run_in_context_if_gles([&]
180188
{

0 commit comments

Comments
 (0)