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

commitdd4b71a704229467ffecd7387fb2b792038aa4c7
parent4e7c5a0975
authorLucas Galante <[email protected]>
date2026-08-13 14:41
debug: file-gated scene-buffer state dump (/tmp/cce-ovdbg) for the off-screen reveal delay

While /tmp/cce-ovdbg exists (contents = comma-separated app_id
substrings), every rendered frame dumps matching windows' window-manager
state plus, per scene buffer, the scene-side ground truth: enabled,
absolute coords, dest size, buffer dimensions, wlr_buffer/texture
pointers, primary output, and the computed visibility region extents.

Investigating: windows fully off-screen before a camera zoom-out (manual
or overview) render nothing at their new on-screen position until their
client commits again; partially-visible windows reveal instantly. The
window-manager side is verified correct per frame (positions, scale,
scene_enabled, full-output damage), so the dump targets the scene/buffer
layer, where the leading suspect is the early client-buffer release for
surfaces not visible on any output.

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

 src/server/output.rs             | 41 ++++++++++++++++++++++++++++++++++++++++
 src/server/wlroots_log_wrapper.c | 31 ++++++++++++++++++++++++++++++
 wrapper.h                        |  1 +
 3 files changed, 73 insertions(+)

diff --git a/src/server/output.rs b/src/server/output.rs
index ef62046..53267ca 100644
--- a/src/server/output.rs
+++ b/src/server/output.rs
@@ -527,6 +527,47 @@ impl Output {
             }
         }
 
+        // Overview-delay debugging: while /tmp/cce-ovdbg exists (contents =
+        // comma-separated app_id substrings), dump the scene-side truth for
+        // matching windows every rendered frame. Toggle live with
+        // `echo firefox,cce-calendar > /tmp/cce-ovdbg`; `rm` to stop.
+        if let Ok(filter) = std::fs::read_to_string("/tmp/cce-ovdbg") {
+            let pats: Vec<&str> = filter.trim().split(',').filter(|p| !p.is_empty()).collect();
+            for &window in wm.windows.iter() {
+                if window.is_null() || (*window).closed {
+                    continue;
+                }
+                let app = (*window).get_app_id_string().unwrap_or_default();
+                if !pats.iter().any(|p| app.contains(p)) {
+                    continue;
+                }
+                let now = std::time::SystemTime::now()
+                    .duration_since(std::time::UNIX_EPOCH)
+                    .unwrap_or_default();
+                eprintln!(
+                    "[ovdbg] t={}.{:03} win {} scale={} req=({},{}) box=({},{}) state={:?} hidden={} saved={} tree_en={}",
+                    now.as_secs(),
+                    now.subsec_millis(),
+                    app,
+                    (*window).scale,
+                    (*window).rendering_requested.x,
+                    (*window).rendering_requested.y,
+                    (*window).box_geom.x,
+                    (*window).box_geom.y,
+                    (*window).state,
+                    (*window).rendering_requested.hidden,
+                    (*window).surfaces.saved,
+                    ffi::river_scene_node_get_enabled((*window).tree as *mut ffi::wlr_scene_node),
+                );
+                if let Ok(tag) = std::ffi::CString::new(app) {
+                    ffi::river_scene_ovdbg_dump(
+                        (*window).tree as *mut ffi::wlr_scene_node,
+                        tag.as_ptr(),
+                    );
+                }
+            }
+        }
+
         let mut state = std::mem::zeroed();
         ffi::wlr_output_state_init(&mut state);
         
diff --git a/src/server/wlroots_log_wrapper.c b/src/server/wlroots_log_wrapper.c
index ed4068e..c1fc9bb 100644
--- a/src/server/wlroots_log_wrapper.c
+++ b/src/server/wlroots_log_wrapper.c
@@ -929,3 +929,34 @@ int river_scene_buffer_get_dest_height(struct wlr_scene_buffer *scene_buffer) {
 bool river_scene_node_get_enabled(struct wlr_scene_node *node) {
 	return node->enabled;
 }
+
+/* Overview-delay debugging: dump the true scene-side state of every buffer
+ * under a node — enabled flags, absolute position, dest size, whether a
+ * wlr_buffer/texture is attached, the primary output, and the computed
+ * visibility region. The visibility extents are the ground truth for "will
+ * this buffer be drawn": an empty region means the scene considers it
+ * invisible regardless of what the window-manager state says. */
+static void river_ovdbg_buffer_iter(struct wlr_scene_buffer *buffer,
+		int sx, int sy, void *data) {
+	const char *tag = data;
+	struct wlr_scene_node *node = &buffer->node;
+	int lx = 0, ly = 0;
+	bool coords_en = wlr_scene_node_coords(node, &lx, &ly);
+	pixman_box32_t *ext = pixman_region32_extents(&node->WLR_PRIVATE.visible);
+	struct timespec ts;
+	clock_gettime(CLOCK_REALTIME, &ts);
+	fprintf(stderr,
+		"[ovdbg] t=%ld.%03ld %s buf=%p en=%d coords_en=%d abs=(%d,%d) "
+		"dst=%dx%d bufwh=%dx%d wlrbuf=%p tex=%p primary=%p vis=(%d,%d %dx%d)\n",
+		(long)ts.tv_sec, ts.tv_nsec / 1000000, tag, (void *)buffer,
+		node->enabled, coords_en, lx, ly,
+		buffer->dst_width, buffer->dst_height,
+		buffer->WLR_PRIVATE.buffer_width, buffer->WLR_PRIVATE.buffer_height,
+		(void *)buffer->buffer, (void *)buffer->WLR_PRIVATE.texture,
+		(void *)buffer->primary_output,
+		ext->x1, ext->y1, ext->x2 - ext->x1, ext->y2 - ext->y1);
+}
+
+void river_scene_ovdbg_dump(struct wlr_scene_node *node, const char *tag) {
+	wlr_scene_node_for_each_buffer(node, river_ovdbg_buffer_iter, (void *)tag);
+}
diff --git a/wrapper.h b/wrapper.h
index 3d7f448..7f02cf3 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -281,3 +281,4 @@ void river_scene_rect_set_size_if_changed(struct wlr_scene_rect *rect, int width
 void river_scene_rect_set_corner_radius(struct wlr_scene_rect *rect, int radius);
 
 #endif // WRAPPER_H
+void river_scene_ovdbg_dump(struct wlr_scene_node *node, const char *tag);