Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix(scenefx): gate optimized-blur re-bake on dirty to stop cache poisoning
The optimized-blur cache re-bakes by blurring pass->buffer over the
node's full box, which is only sound when the whole box was damaged
this frame — mark_dirty guarantees that by damaging the node's visible
region. Commit 092b91e dropped the `&& scene_blur->dirty` gate, so the
bake fired on any frame whose damage merely grazed the box and sampled
stale buffer content, including this very surface composited above the
node. Popups baked their own image into their backdrop cache: frozen
fade-outs, accumulated hover highlights, and ghost menus left on screen.
092b91e removed the gate to mask a real first-frame bug:
wlr_scene_optimized_blur_create initialized dirty = false, so a fresh
cache was never baked and backplate blur rendered empty. Fix both ends:
create the node dirty and restore the gate.
Co-Authored-By: Claude Fable 5 <[email protected]>
scenefx/types/scene/wlr_scene.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/scenefx/types/scene/wlr_scene.c b/scenefx/types/scene/wlr_scene.c
index 69a7e59..d56d64f 100644
--- a/scenefx/types/scene/wlr_scene.c
+++ b/scenefx/types/scene/wlr_scene.c
@@ -1371,7 +1371,10 @@ struct wlr_scene_optimized_blur *wlr_scene_optimized_blur_create(
scene_blur->width = width;
scene_blur->height = height;
- scene_blur->dirty = false;
+ // Start dirty so the first render pass bakes the cache; scene_entry_render
+ // only re-bakes when dirty (re-baking on undamaged frames samples stale
+ // pass->buffer content, ghosting whatever was composited above the node).
+ scene_blur->dirty = true;
scene_node_update(&scene_blur->node, NULL);
@@ -2246,8 +2249,14 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren
case WLR_SCENE_NODE_OPTIMIZED_BLUR:;
struct wlr_scene_optimized_blur *scene_blur = wlr_scene_optimized_blur_from_node(node);
// Re-render the optimized blur buffer when needed. Retry rendering
- // until there's a visible blur_node.
- if (fx_pass->has_blur && is_scene_blur_enabled(&scene->blur_data)) {
+ // until there's a visible blur_node. The dirty gate is load-bearing:
+ // mark_dirty damages the node's whole box, so pass->buffer holds fresh
+ // below-node content when the re-bake samples it. Without the gate the
+ // re-bake fires on frames whose damage only grazes the box and blurs
+ // stale pass->buffer pixels — including this very surface composited
+ // above — baking ghosts into the shared cache.
+ if (fx_pass->has_blur && is_scene_blur_enabled(&scene->blur_data)
+ && scene_blur->dirty) {
const float opacity = 1.0f;
enum wl_output_transform transform =
wlr_output_transform_invert(data->transform);