git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commita4ea755592118f7c0610c71966e32758767fc88d
parent05bff2e060
authorLucas Galante <[email protected]>
date2026-07-16 17:21
fix(scenefx): re-blur whole blur nodes and test real damage coverage

Status-module backdrops flickered while panning the desktop: grid-cell
movement produces damage with full-output extents that is mostly holes,
which passed the extents-based full_damage test and skipped blur
compensation entirely; each blur node then re-blurred only the damaged
slivers, sampling stale previous-frame composited pixels (including the
node's own content) within the blur's sample range.

- full_damage now tests actual region coverage of the output via
  pixman_region32_contains_rectangle instead of comparing extents.
- apply_blur_region now unions the blur node's entire visible region
  (and its sample-size-expanded padding) into the damage instead of just
  the damaged sliver, so the whole backdrop is re-blurred from the same
  freshly rendered below-content every frame.

Co-Authored-By: Claude Fable 5 <[email protected]>

 scenefx/types/scene/wlr_scene.c | 33 ++++++++++++++++++++++++---------
 1 file changed, 24 insertions(+), 9 deletions(-)

diff --git a/scenefx/types/scene/wlr_scene.c b/scenefx/types/scene/wlr_scene.c
index d56d64f..6aed74d 100644
--- a/scenefx/types/scene/wlr_scene.c
+++ b/scenefx/types/scene/wlr_scene.c
@@ -3007,16 +3007,24 @@ static bool apply_blur_region(struct wlr_scene_node *node, struct blur_data *blu
 	if (pixman_region32_intersect(&intersection, &expanded_damage, &node_visible_region)) {
 		should_compensate_blur = true;
 
-		// Expand the render damage to re-render surrounding blur nodes
-		pixman_region32_union(&render_data->damage, &render_data->damage, &intersection);
+		// Re-render the node's entire blur region, not just the damaged
+		// sliver: the blur samples the framebuffer up to sample_size beyond
+		// the damage, where pixels still hold the previous frame's final
+		// composited content — including this node's own content drawn above
+		// the blur. Re-blurring only the sliver bakes those stale samples
+		// into part of the backdrop while neighboring pixels keep last
+		// frame's blur, which shimmers whenever damage sweeps underneath
+		// (e.g. grid lines during a desktop pan). Re-blurring the whole node
+		// computes every pixel from the same freshly rendered below-content.
+		pixman_region32_union(&render_data->damage, &render_data->damage, &node_visible_region);
 		// Also make sure that the backend also knows about the new
 		// damage. Very important
 		output_state->committed |= WLR_OUTPUT_STATE_DAMAGE;
-		pixman_region32_union(&output_state->damage, &output_state->damage, &intersection);
+		pixman_region32_union(&output_state->damage, &output_state->damage, &node_visible_region);
 
 		// Expand it once more to get the blur padding region
 		// which is key for artifact removal :)
-		wlr_region_expand(&intersection, &intersection, sample_size);
+		wlr_region_expand(&intersection, &node_visible_region, sample_size);
 		// Don't re-add already added rectangles
 		pixman_region32_subtract(&intersection, &intersection, blur_padding_region);
 		pixman_region32_union(blur_padding_region, blur_padding_region, &intersection);
@@ -3439,11 +3447,18 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
 		pixman_region32_init(&original_damage);
 		pixman_region32_copy(&original_damage, &render_data.damage);
 
-		// Only compensate for blur artifacts when the damage doesn't span
-		// the whole output
-		const bool full_damage =
-			original_damage.extents.x2 - original_damage.extents.x1 >= output->width
-			&& original_damage.extents.y2 - original_damage.extents.y1 >= output->height;
+		// Only compensate for blur artifacts when the damage doesn't cover
+		// the whole output. Test actual coverage, not extents: scattered
+		// damage (e.g. every grid cell rect moving during a desktop pan)
+		// has full-output extents while being mostly holes, and skipping
+		// compensation then leaves each blur node re-blurring only slivers
+		// against stale framebuffer content.
+		pixman_box32_t full_output_box = {
+			.x1 = 0, .y1 = 0,
+			.x2 = output->width, .y2 = output->height,
+		};
+		const bool full_damage = pixman_region32_contains_rectangle(
+			&original_damage, &full_output_box) == PIXMAN_REGION_IN;
 
 		// The extra region we copy and paste onto the framebuffer after render
 		// for artifact removal