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

commitf41dbdb1c718d63083ce5dffb46488533f15c4c1
parentc2472de0b7
authorLucas Galante <[email protected]>
date2026-07-15 12:37
fix(resize): only move the scene tree synchronously for live buffers

The previous jitter fix repositioned the scene tree on every resize
commit — but when a configure is in flight the displayed buffer is the
frozen (saved) one at the OLD size, so the early move shifted that old
buffer and showed as jitter itself. Now the synchronous move (plus
box_geom/border update) applies only when the buffer is live
(surfaces not saved); for frozen commits the position is left to the
render pass, which restores the new buffer and applies the anchored
position atomically — and dirty_rendering() makes sure that pass runs
promptly instead of waiting for the next relayout.

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

 src/server/xdg_toplevel.rs | 28 ++++++++++++++++++----------
 1 file changed, 18 insertions(+), 10 deletions(-)

diff --git a/src/server/xdg_toplevel.rs b/src/server/xdg_toplevel.rs
index d269d17..e0b1ca6 100644
--- a/src/server/xdg_toplevel.rs
+++ b/src/server/xdg_toplevel.rs
@@ -645,16 +645,24 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
         (*window).box_geom.x = final_x;
         (*window).box_geom.y = final_y;
 
-        // Apply the compensating position (and the borders) to the scene in
-        // this same commit: the resized buffer is already part of the scene,
-        // and waiting for the next render pass lets a frame composite the
-        // new size at the old position — visible as jitter on the anchored
-        // edges during left/top resizes.
-        ffi::river_scene_node_set_position_if_changed((*window).tree as *mut ffi::wlr_scene_node, final_x, final_y);
-        ffi::river_scene_node_set_position_if_changed((*window).popup_tree as *mut ffi::wlr_scene_node, final_x, final_y);
-        (*window).box_geom.width = geometry.width;
-        (*window).box_geom.height = geometry.height;
-        (*window).draw_borders();
+        // Keep the displayed buffer and the compensating position atomic.
+        // Live buffer (no configure in flight): the commit is already on
+        // screen, so move the scene tree in the same commit — waiting for
+        // the next render pass lets a frame composite the new size at the
+        // old position, jittering the anchored edges. Frozen buffer (saved
+        // for an in-flight configure): moving the tree now would shift the
+        // OLD-size buffer instead, so leave the position to the render pass
+        // (which restores the new buffer and applies it together) and make
+        // sure that pass runs promptly.
+        if !(*window).surfaces.saved {
+            ffi::river_scene_node_set_position_if_changed((*window).tree as *mut ffi::wlr_scene_node, final_x, final_y);
+            ffi::river_scene_node_set_position_if_changed((*window).popup_tree as *mut ffi::wlr_scene_node, final_x, final_y);
+            (*window).box_geom.width = geometry.width;
+            (*window).box_geom.height = geometry.height;
+            (*window).draw_borders();
+        } else {
+            (*server).wm.dirty_rendering();
+        }
 
         if !resize_active {
             (*window).resize_edges = None;