Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix: force a full repaint on camera-motion frames — stale-zoom slivers
Zooming left graphical artifacts around windows (reported as "shadow
glitches" on claude — which, ssd=false, has no compositor shadow at
all): slivers of the PREVIOUS zoom level's frame surviving next to the
rescaled windows, full-size text ghosts at the edges, and idle windows
rendering entirely at the old scale. Root cause is damage
under-reporting: a camera-motion frame re-lays-out the whole screen,
but repaint regions come only from individual node changes — an idle
window's old pixels are nobody's damage, and rounding gaps between
old/new node boxes leave seams. update_viewport_local motion frames
and the viewport settle now damage the entire output via the new
river_scene_output_damage_whole wrapper (the public-field replica of
scenefx's internal scene_output_damage_whole; pending_commit_damage
lives behind the WLR_PRIVATE member). Full-screen repaint during
camera motion costs nothing — the whole screen is changing anyway.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/server/window_manager.rs | 20 +++++++++++++++++---
src/server/wlroots_log_wrapper.c | 18 ++++++++++++++++++
wrapper.h | 1 +
3 files changed, 36 insertions(+), 3 deletions(-)
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 3ab35d8..3425edb 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -1671,14 +1671,22 @@ impl WindowManager {
}
}
- // Commit outputs or schedule frame updates
+ // Commit outputs or schedule frame updates. A camera-motion frame
+ // re-lays-out the whole screen but per-node damage under-reports at
+ // the seams (stale slivers of the previous zoom level survive — an
+ // idle window's old pixels are nobody's damage), so motion forces a
+ // full repaint.
let outputs_list = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
let mut curr = (*outputs_list).next;
while curr != outputs_list {
let next = (*curr).next;
let output = crate::container_of!(curr, crate::output::Output, link);
if (*output).sent.state == crate::output::OutputStateValue::Enabled {
- ffi::wlr_output_schedule_frame((*output).wlr_output);
+ if moved && !(*output).scene_output.is_null() {
+ ffi::river_scene_output_damage_whole((*output).scene_output);
+ } else {
+ ffi::wlr_output_schedule_frame((*output).wlr_output);
+ }
}
curr = next;
}
@@ -1725,13 +1733,19 @@ impl WindowManager {
(*window).render_finish();
}
}
+ // Settling re-enables blur and re-finishes every window; sweep any
+ // remaining motion-frame slivers with one full repaint.
let outputs_list = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
let mut curr = (*outputs_list).next;
while curr != outputs_list {
let next = (*curr).next;
let output = crate::container_of!(curr, crate::output::Output, link);
if (*output).sent.state == crate::output::OutputStateValue::Enabled {
- ffi::wlr_output_schedule_frame((*output).wlr_output);
+ if !(*output).scene_output.is_null() {
+ ffi::river_scene_output_damage_whole((*output).scene_output);
+ } else {
+ ffi::wlr_output_schedule_frame((*output).wlr_output);
+ }
}
curr = next;
}
diff --git a/src/server/wlroots_log_wrapper.c b/src/server/wlroots_log_wrapper.c
index 3f408e8..a39fe76 100644
--- a/src/server/wlroots_log_wrapper.c
+++ b/src/server/wlroots_log_wrapper.c
@@ -385,6 +385,24 @@ struct wlr_surface *river_wlr_seat_get_pointer_focused_surface(struct wlr_seat *
return seat->pointer_state.focused_surface;
}
+/* Damage the whole output and schedule a frame — the public-field replica of
+ * scenefx's internal scene_output_damage_whole(). Viewport zoom re-lays-out
+ * the entire screen, but per-node damage under-reports at the seams (stale
+ * slivers of the previous zoom level survive), so camera motion forces a
+ * full repaint. */
+void river_scene_output_damage_whole(struct wlr_scene_output *scene_output) {
+ struct wlr_output *output = scene_output->output;
+ pixman_region32_t damage;
+ pixman_region32_init_rect(&damage, 0, 0, output->width, output->height);
+ wlr_output_schedule_frame(output);
+ wlr_damage_ring_add(&scene_output->damage_ring, &damage);
+ /* pending_commit_damage lives in the WLR_PRIVATE member — reaching in is
+ * the same deal as the pointer_state accesses elsewhere in this file. */
+ pixman_region32_union(&scene_output->WLR_PRIVATE.pending_commit_damage,
+ &scene_output->WLR_PRIVATE.pending_commit_damage, &damage);
+ pixman_region32_fini(&damage);
+}
+
struct wl_client *river_wlr_seat_client_get_client(struct wlr_seat_client *client) {
return client->client;
}
diff --git a/wrapper.h b/wrapper.h
index 4c2d2db..384b290 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -187,6 +187,7 @@ struct wl_signal *river_wlr_seat_get_start_drag_signal(struct wlr_seat *seat);
struct wl_signal *river_wlr_seat_get_request_set_primary_selection_signal(struct wlr_seat *seat);
struct wlr_seat_client *river_wlr_seat_get_pointer_focused_client(struct wlr_seat *seat);
struct wlr_surface *river_wlr_seat_get_pointer_focused_surface(struct wlr_seat *seat);
+void river_scene_output_damage_whole(struct wlr_scene_output *scene_output);
struct wl_client *river_wlr_seat_client_get_client(struct wlr_seat_client *client);
struct wlr_keyboard *river_wlr_seat_get_keyboard(struct wlr_seat *seat);
struct wl_global *river_wlr_seat_get_global(struct wlr_seat *seat);