Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
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);