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

commita1ed4a201cb1a4758295bb97c963b4f7be57622e
parent399a80c70f
authorLucas Galante <[email protected]>
date2026-09-04 10:33
fix(pan): keep non-cce windows blurred through camera motion — no more blur pop at the end of a pan

render_viewport_update destroyed every non-cce window's blur nodes on
the first motion frame and rebuilt them 120ms after the gesture ended,
a visible pop on every pan; cce apps were exempt to avoid the flicker.
The exemption existed because per-frame blur during motion was thought
too expensive. With the scene's optimized-blur caches frozen for the
duration of the motion, a blurred window costs one cached-texture
sample per frame while moving, so the motion path now issues the same
enable_blur call for every window that the settled path does.

Headless shadow, two decorated (blurred) non-cce windows plus cce-fonts,
four 60-notch pans: render mean 748us / p95 1210us / max 1670us, versus
610 / 990 / 1325 with their blur torn down. No errors.

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

 src/server/window.rs | 39 ++++++++++++++++++++-------------------
 1 file changed, 20 insertions(+), 19 deletions(-)

diff --git a/src/server/window.rs b/src/server/window.rs
index 9f4d3c7..35220c5 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -3079,9 +3079,15 @@ impl Window {
             ffi::river_scene_node_set_position_if_changed(self.tree as *mut ffi::wlr_scene_node, self.box_geom.x, self.box_geom.y);
             ffi::river_scene_node_set_position_if_changed(self.popup_tree as *mut ffi::wlr_scene_node, self.box_geom.x, self.box_geom.y);
 
-            // Disable backdrop blur during active viewport zoom/pan for maximum performance,
-            // EXCEPT for cce-* apps, which we keep blurred during the pan so their translucent
-            // backgrounds don't flicker as blur toggles on/off across motion frames.
+            // Blur stays on through a pan for every window. Non-cce windows
+            // used to have their blur nodes DESTROYED on the first motion
+            // frame and rebuilt 120ms after the gesture — a visible pop at
+            // the end of every pan — on the theory that per-frame blur was
+            // too expensive to keep during motion. Since the scene freezes
+            // its optimized-blur caches for the duration of the motion
+            // (river_scene_set_blur_frozen), a blurred window costs one
+            // cached-texture sample per frame while moving, so the same
+            // treatment cce apps always had now applies to all.
             // The geometry below is computed for EVERY window regardless: the drop
             // shadow has to track the zoom even where live blur does not (see the
             // update_shadow call at the end of the block).
@@ -3151,22 +3157,17 @@ impl Window {
                 let radius = if requested.circular { radius } else { widen_corner_radius(radius, actual_w as i32, actual_h as i32) };
                 let width = (actual_w as f64 * self.scale) as i32;
                 let height = (actual_h as f64 * self.scale) as i32;
-                if app_id.starts_with("cce-") {
-                    ffi::river_scene_node_enable_blur(
-                        self.tree as *mut ffi::wlr_scene_node,
-                        blur_enabled,
-                        use_optimized,
-                        ignore_transparent,
-                        0,
-                        0,
-                        width,
-                        height,
-                        (radius as f64 * self.scale) as i32,
-                    );
-                } else {
-                    // Tearing the blur down: radius is irrelevant, the nodes are destroyed.
-                    ffi::river_scene_node_enable_blur(self.tree as *mut ffi::wlr_scene_node, false, (*self.server).wm.layout.scenefx_optimized_blur, true, 0, 0, 0, 0, 0);
-                }
+                ffi::river_scene_node_enable_blur(
+                    self.tree as *mut ffi::wlr_scene_node,
+                    blur_enabled,
+                    use_optimized,
+                    ignore_transparent,
+                    0,
+                    0,
+                    width,
+                    height,
+                    (radius as f64 * self.scale) as i32,
+                );
                 // Every window, blurred or not: the shadow's size, blur sigma,
                 // offset and — critically — the clipped region that punches the
                 // window out of it are all scale-dependent, and nothing else on