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

commitc05f47074451887aca58d7ea57cf737d31d9fad2
parent67f908032b
authorLucas Galante <[email protected]>
date2026-06-30 15:04
Implement background scene rect pooling to restore grid cell fade resolution and eliminate finalize frame lag spikes

 src/server/output.rs | 134 ++++++++++++++++-----------------------------------
 1 file changed, 42 insertions(+), 92 deletions(-)

diff --git a/src/server/output.rs b/src/server/output.rs
index f31c598..6552be8 100644
--- a/src/server/output.rs
+++ b/src/server/output.rs
@@ -167,6 +167,7 @@ pub struct Output {
     pub last_grid_cell_fade_inset: i64,
     pub last_grid_gap_color: String,
     pub grid_is_low_res: bool,
+    pub grid_rect_pool: Vec<*mut ffi::wlr_scene_rect>,
 
     pub destroy: ffi::wl_listener,
     pub request_state: ffi::wl_listener,
@@ -241,6 +242,7 @@ impl Output {
             self.layer_shell.make_inert();
             self.object = std::ptr::null_mut();
             self.sent_wl_output = false;
+            self.grid_rect_pool.clear();
         }
     }
 
@@ -326,6 +328,7 @@ impl Output {
                         ffi::wlr_scene_node_destroy(self.grid_tree as *mut ffi::wlr_scene_node);
                         self.grid_tree = std::ptr::null_mut();
                     }
+                    self.grid_rect_pool.clear();
 
                     // remove output from windows fullscreen hint
                     for &window in (*self.server).wm.windows.iter() {
@@ -415,6 +418,7 @@ impl Output {
             last_grid_cell_fade_inset: 0,
             last_grid_gap_color: String::new(),
             grid_is_low_res: false,
+            grid_rect_pool: Vec::new(),
             destroy: std::mem::zeroed(),
             request_state: std::mem::zeroed(),
             frame: std::mem::zeroed(),
@@ -595,28 +599,40 @@ impl Output {
             self.last_grid_gap_color = gap_color;
             self.grid_is_low_res = draw_low_res;
 
-            // Clear previous grid rendering
-            ffi::river_scene_tree_clear_children(self.grid_tree);
             ffi::wlr_scene_node_raise_to_top(self.grid_tree as *mut ffi::wlr_scene_node);
 
+            let grid_tree = self.grid_tree;
+            let pool = &mut self.grid_rect_pool;
+            let mut pool_idx = 0;
+
+            let mut get_rect = |w: i32, h: i32, color: *const f32, x: i32, y: i32, r: i32| -> *mut ffi::wlr_scene_rect {
+                let rect = if pool_idx < pool.len() {
+                    let node = pool[pool_idx];
+                    ffi::wlr_scene_node_set_enabled(node as *mut ffi::wlr_scene_node, true);
+                    ffi::wlr_scene_rect_set_size(node, w, h);
+                    ffi::wlr_scene_rect_set_color(node, color);
+                    node
+                } else {
+                    let node = ffi::wlr_scene_rect_create(grid_tree, w, h, color);
+                    if !node.is_null() {
+                        pool.push(node);
+                    }
+                    node
+                };
+                if !rect.is_null() {
+                    ffi::wlr_scene_node_set_position(rect as *mut ffi::wlr_scene_node, x, y);
+                    ffi::river_scene_rect_set_corner_radius(rect, r);
+                }
+                pool_idx += 1;
+                rect
+            };
+
             // Draw the base level background rect (gap color)
             // Sized larger to allow shifting by up to one period
             let bg_w = viewport_w + period_pixels.ceil() as i32;
             let bg_h = viewport_h + period_pixels.ceil() as i32;
             let gap_color_arr = crate::config::parse_hex_color_rgba(&self.last_grid_gap_color);
-            let bg_rect = ffi::wlr_scene_rect_create(
-                self.grid_tree,
-                bg_w,
-                bg_h,
-                gap_color_arr.as_ptr(),
-            );
-            if !bg_rect.is_null() {
-                ffi::wlr_scene_node_set_position(
-                    bg_rect as *mut ffi::wlr_scene_node,
-                    0,
-                    0,
-                );
-            }
+            let _bg_rect = get_rect(bg_w, bg_h, gap_color_arr.as_ptr(), 0, 0, 0);
 
             // Determine grid range needed to cover viewport + shifting buffer
             let cols = (viewport_w as f64 / period_pixels).ceil() as i32 + 1;
@@ -636,56 +652,15 @@ impl Output {
                         continue;
                     }
 
-                    let draw_fade = cell_fade_inset > 0 && !draw_low_res && zoom >= 0.5;
+                    let draw_fade = cell_fade_inset > 0 && !draw_low_res;
                     if !draw_fade {
-                        let cell_rect = ffi::wlr_scene_rect_create(
-                            self.grid_tree,
-                            rw,
-                            rh,
-                            cell_color.as_ptr(),
-                        );
-                        if !cell_rect.is_null() {
-                            ffi::wlr_scene_node_set_position(
-                                cell_rect as *mut ffi::wlr_scene_node,
-                                rel_x,
-                                rel_y,
-                            );
-                            if cell_corner_radius > 0 {
-                                ffi::river_scene_rect_set_corner_radius(
-                                    cell_rect,
-                                    cell_corner_radius,
-                                );
-                            }
-                        }
+                        let _cell_rect = get_rect(rw, rh, cell_color.as_ptr(), rel_x, rel_y, cell_corner_radius);
                     } else {
+                        let step = if draw_low_res { 8 } else { 1 };
                         let inset_scaled = (cell_fade_inset as f64 * zoom) as i32;
-                        // Limit to at most 10 layers per cell to avoid frame drops when finalizing zoom
-                        let step = if draw_low_res {
-                            8
-                        } else {
-                            ((inset_scaled as f32 / 10.0).ceil() as i32).max(2)
-                        };
 
                         // 0. Draw the cell-sized gap color background rect behind the fade layers
-                        let cell_bg_rect = ffi::wlr_scene_rect_create(
-                            self.grid_tree,
-                            rw,
-                            rh,
-                            gap_color_arr.as_ptr(),
-                        );
-                        if !cell_bg_rect.is_null() {
-                            ffi::wlr_scene_node_set_position(
-                                cell_bg_rect as *mut ffi::wlr_scene_node,
-                                rel_x,
-                                rel_y,
-                            );
-                            if cell_corner_radius > 0 {
-                                ffi::river_scene_rect_set_corner_radius(
-                                    cell_bg_rect,
-                                    cell_corner_radius,
-                                );
-                            }
-                        }
+                        let _cell_bg_rect = get_rect(rw, rh, gap_color_arr.as_ptr(), rel_x, rel_y, cell_corner_radius);
 
                         // 1. Draw the fade layers
                         let mut i = step;
@@ -704,22 +679,7 @@ impl Output {
                                 alpha,
                             ];
 
-                            let cell_rect = ffi::wlr_scene_rect_create(
-                                self.grid_tree,
-                                rect_w,
-                                rect_h,
-                                layer_color.as_ptr(),
-                            );
-                            if !cell_rect.is_null() {
-                                ffi::wlr_scene_node_set_position(
-                                    cell_rect as *mut ffi::wlr_scene_node,
-                                    rel_x + i,
-                                    rel_y + i,
-                                );
-                                 if cell_corner_radius > 0 {
-                                     ffi::river_scene_rect_set_corner_radius(cell_rect, cell_corner_radius);
-                                 }
-                            }
+                            let _cell_rect = get_rect(rect_w, rect_h, layer_color.as_ptr(), rel_x + i, rel_y + i, cell_corner_radius);
                             i += step;
                         }
 
@@ -727,26 +687,16 @@ impl Output {
                         let core_w = rw - 2 * inset_scaled;
                         let core_h = rh - 2 * inset_scaled;
                         if core_w > 0 && core_h > 0 {
-                            let cell_rect = ffi::wlr_scene_rect_create(
-                                self.grid_tree,
-                                core_w,
-                                core_h,
-                                cell_color.as_ptr(),
-                            );
-                            if !cell_rect.is_null() {
-                                ffi::wlr_scene_node_set_position(
-                                    cell_rect as *mut ffi::wlr_scene_node,
-                                    rel_x + inset_scaled,
-                                    rel_y + inset_scaled,
-                                );
-                                 if cell_corner_radius > 0 {
-                                     ffi::river_scene_rect_set_corner_radius(cell_rect, cell_corner_radius);
-                                 }
-                            }
+                            let _cell_rect = get_rect(core_w, core_h, cell_color.as_ptr(), rel_x + inset_scaled, rel_y + inset_scaled, cell_corner_radius);
                         }
                     }
                 }
             }
+
+            // Disable any remaining rects in the pool
+            for i in pool_idx..pool.len() {
+                ffi::wlr_scene_node_set_enabled(pool[i] as *mut ffi::wlr_scene_node, false);
+            }
         }
 
         // Apply modulo translation to the grid tree based on panning