Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
grid: a commit damages what the client damaged, not the whole screen
wlroots' scene surface helper rewrites a buffer's dest size and opaque
region on every commit, to the natural size and the unscaled region.
For a surface drawn scaled the compositor puts its own values back, and
each rewrite damages the whole node. The grid is always drawn scaled
(its patch carries the output scale) and covers the screen, so every
commit of it repainted the entire output: an image dragged across the
desktop cost a full-screen repaint per frame, whatever damage cce-grid
sent.
scenefx scene buffers can now be pinned
(wlr_scene_buffer_set_geometry_pinned): the plain setters leave a
pinned buffer alone and only the _pinned ones, which the river shims
use, move it. render_finish pins the grid's buffers and nothing else.
Measured in a shadow at 3840x2400 scale 2 with the matching cce-grid
change, per 1.6 s of dragging: about 670 ms of compositor GPU time
before, 63-109 ms after.
Also drops two debug lines that logged at info on every commit of any
window titled "Ubisoft" -- over a hundred lines a second while Ubisoft
Connect was open.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
scenefx/include/scenefx/types/wlr_scene.h | 30 ++++++++++++++++++++
scenefx/types/scene/wlr_scene.c | 21 ++++++++++++++
src/server/window.rs | 47 +++++++++----------------------
src/server/wlroots_log_wrapper.c | 11 ++++++--
wrapper.h | 1 +
5 files changed, 74 insertions(+), 36 deletions(-)
diff --git a/scenefx/include/scenefx/types/wlr_scene.h b/scenefx/include/scenefx/types/wlr_scene.h
index ca392ef4..7057b2d9 100644
--- a/scenefx/include/scenefx/types/wlr_scene.h
+++ b/scenefx/include/scenefx/types/wlr_scene.h
@@ -395,8 +395,38 @@ struct wlr_scene_buffer {
struct fx_corner_radii corners;
struct linked_node blur;
+
+ // The compositor owns this buffer's dest size and opaque region:
+ // wlr_scene_buffer_set_dest_size() and
+ // wlr_scene_buffer_set_opaque_region() leave them alone, and only the
+ // _pinned setters move them. See wlr_scene_buffer_set_geometry_pinned().
+ bool geometry_pinned;
};
+/**
+ * Hand a buffer's dest size and opaque region to the compositor.
+ *
+ * wlroots' scene surface helper rewrites both on EVERY commit, to the
+ * surface's natural size and its unscaled opaque region. A compositor that
+ * draws the surface at another size then has to put its own values back,
+ * and each of the two rewrites damages the whole node: a surface shown
+ * scaled repaints in full on every commit, whatever damage the client sent.
+ * For one that covers the screen, that is the screen.
+ *
+ * While pinned, the plain setters are ignored for this buffer and
+ * wlr_scene_buffer_set_dest_size_pinned() /
+ * wlr_scene_buffer_set_opaque_region_pinned() are the only way to move the
+ * values, so a commit that changes neither damages only what the client
+ * damaged. The compositor must then set both itself whenever the surface's
+ * size or opaque region changes.
+ */
+void wlr_scene_buffer_set_geometry_pinned(struct wlr_scene_buffer *scene_buffer,
+ bool pinned);
+void wlr_scene_buffer_set_dest_size_pinned(struct wlr_scene_buffer *scene_buffer,
+ int width, int height);
+void wlr_scene_buffer_set_opaque_region_pinned(struct wlr_scene_buffer *scene_buffer,
+ const pixman_region32_t *region);
+
/** A viewport for an output in the scene-graph */
struct wlr_scene_output {
struct wlr_output *output;
diff --git a/scenefx/types/scene/wlr_scene.c b/scenefx/types/scene/wlr_scene.c
index 9f9bb0f2..ce9a4bd0 100644
--- a/scenefx/types/scene/wlr_scene.c
+++ b/scenefx/types/scene/wlr_scene.c
@@ -1959,8 +1959,21 @@ void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer,
wlr_scene_buffer_set_buffer_with_options(scene_buffer, buffer, NULL);
}
+void wlr_scene_buffer_set_geometry_pinned(struct wlr_scene_buffer *scene_buffer,
+ bool pinned) {
+ scene_buffer->geometry_pinned = pinned;
+}
+
void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer,
const pixman_region32_t *region) {
+ if (scene_buffer->geometry_pinned) {
+ return;
+ }
+ wlr_scene_buffer_set_opaque_region_pinned(scene_buffer, region);
+}
+
+void wlr_scene_buffer_set_opaque_region_pinned(struct wlr_scene_buffer *scene_buffer,
+ const pixman_region32_t *region) {
if (pixman_region32_equal(&scene_buffer->opaque_region, region)) {
return;
}
@@ -1997,6 +2010,14 @@ void wlr_scene_buffer_set_source_box(struct wlr_scene_buffer *scene_buffer,
void wlr_scene_buffer_set_dest_size(struct wlr_scene_buffer *scene_buffer,
int width, int height) {
+ if (scene_buffer->geometry_pinned) {
+ return;
+ }
+ wlr_scene_buffer_set_dest_size_pinned(scene_buffer, width, height);
+}
+
+void wlr_scene_buffer_set_dest_size_pinned(struct wlr_scene_buffer *scene_buffer,
+ int width, int height) {
if (scene_buffer->dst_width == width && scene_buffer->dst_height == height) {
return;
}
diff --git a/src/server/window.rs b/src/server/window.rs
index f2301728..a3d5fb1e 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -3056,17 +3056,6 @@ impl Window {
let requested = &self.rendering_requested;
let enabled = !requested.hidden && (matches!(self.state, WindowState::Mapped) || matches!(self.state, WindowState::Closing));
- let title_ptr = match self.impl_type {
- WindowImpl::Xwayland(xwindow) => {
- if xwindow.is_null() { std::ptr::null() } else { (*(*xwindow).xsurface).title }
- }
- _ => std::ptr::null(),
- };
- let title = if title_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(title_ptr).to_str().unwrap_or("") };
- if title.contains("Ubisoft") {
- log::info!("render_finish for '{}' (addr={:p}): enabled={} hidden={} state={:?}", title, self as *const Window, enabled, requested.hidden, self.state);
- }
-
ffi::wlr_scene_node_set_enabled(self.tree as *mut ffi::wlr_scene_node, enabled);
ffi::wlr_scene_node_set_enabled(self.popup_tree as *mut ffi::wlr_scene_node, enabled);
if !enabled {
@@ -3188,6 +3177,13 @@ impl Window {
scale_x: f64,
scale_y: f64,
ancestor: *mut ffi::wlr_scene_node,
+ /// The grid's buffers are pinned (see
+ /// `wlr_scene_buffer_set_geometry_pinned`). Its surface is
+ /// always shown scaled and covers the screen, so the scene
+ /// helper resetting its dest size and opaque region on each
+ /// commit, and this pass putting them back, repainted the
+ /// whole output for every frame of an image being dragged.
+ pin: bool,
}
unsafe extern "C" fn set_overview_scale_iterator(
@@ -3201,6 +3197,9 @@ impl Window {
let surface = ffi::river_scene_node_get_surface(node);
if !surface.is_null() {
+ if data.pin {
+ ffi::river_scene_buffer_set_geometry_pinned(buffer, true);
+ }
let (w, h, ox, oy) = surface_buffer_extent(buffer, surface);
if data.scale_x == 1.0 && data.scale_y == 1.0 {
ffi::river_scene_buffer_set_dest_size_if_changed(buffer, w, h);
@@ -3235,7 +3234,7 @@ impl Window {
// leaves it briefly at the old zoom, which restore corrects.
}
- let scale_data_surfaces = ScaleData { scale_x, scale_y, ancestor: self.surfaces.tree as *mut ffi::wlr_scene_node };
+ let scale_data_surfaces = ScaleData { scale_x, scale_y, ancestor: self.surfaces.tree as *mut ffi::wlr_scene_node, pin: self.is_grid() };
ffi::wlr_scene_node_for_each_buffer(
self.surfaces.tree as *mut ffi::wlr_scene_node,
Some(set_overview_scale_iterator),
@@ -3243,7 +3242,7 @@ impl Window {
);
if self.surfaces.saved {
- let scale_data_saved = ScaleData { scale_x, scale_y, ancestor: self.surfaces.saved_tree as *mut ffi::wlr_scene_node };
+ let scale_data_saved = ScaleData { scale_x, scale_y, ancestor: self.surfaces.saved_tree as *mut ffi::wlr_scene_node, pin: self.is_grid() };
ffi::wlr_scene_node_for_each_buffer(
self.surfaces.saved_tree as *mut ffi::wlr_scene_node,
Some(set_overview_scale_iterator),
@@ -3251,7 +3250,7 @@ impl Window {
);
}
- let scale_data_popup = ScaleData { scale_x, scale_y, ancestor: self.popup_tree as *mut ffi::wlr_scene_node };
+ let scale_data_popup = ScaleData { scale_x, scale_y, ancestor: self.popup_tree as *mut ffi::wlr_scene_node, pin: self.is_grid() };
ffi::wlr_scene_node_for_each_buffer(
self.popup_tree as *mut ffi::wlr_scene_node,
Some(set_overview_scale_iterator),
@@ -4632,26 +4631,6 @@ impl Window {
surface_clip.y += y;
}
}
- WindowImpl::Xwayland(xwindow) => {
- if !xwindow.is_null() {
- let title_ptr = (*(*xwindow).xsurface).title;
- let title = if title_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(title_ptr).to_str().unwrap_or("") };
- if title.contains("Ubisoft") {
- log::info!(
- "XWayland window clip check: title='{}' box_geom=({}, {}, {}, {}) xsurface=({}, {}, {}, {})",
- title,
- self.box_geom.x,
- self.box_geom.y,
- self.box_geom.width,
- self.box_geom.height,
- (*(*xwindow).xsurface).x,
- (*(*xwindow).xsurface).y,
- (*(*xwindow).xsurface).width,
- (*(*xwindow).xsurface).height,
- );
- }
- }
- }
_ => {}
}
diff --git a/src/server/wlroots_log_wrapper.c b/src/server/wlroots_log_wrapper.c
index 05e9c360..6b102c24 100644
--- a/src/server/wlroots_log_wrapper.c
+++ b/src/server/wlroots_log_wrapper.c
@@ -909,9 +909,16 @@ void river_scene_node_set_corner_radius(struct wlr_scene_node *node, int radius)
}
}
+/* See wlr_scene_buffer_set_geometry_pinned(). The two setters below write
+ * through the pin, so every caller of theirs keeps working on a pinned
+ * buffer. */
+void river_scene_buffer_set_geometry_pinned(struct wlr_scene_buffer *scene_buffer, bool pinned) {
+ wlr_scene_buffer_set_geometry_pinned(scene_buffer, pinned);
+}
+
void river_scene_buffer_set_dest_size_if_changed(struct wlr_scene_buffer *scene_buffer, int width, int height) {
if (scene_buffer->dst_width != width || scene_buffer->dst_height != height) {
- wlr_scene_buffer_set_dest_size(scene_buffer, width, height);
+ wlr_scene_buffer_set_dest_size_pinned(scene_buffer, width, height);
}
}
@@ -987,7 +994,7 @@ void river_scene_buffer_set_scaled_opaque_region(struct wlr_scene_buffer *scene_
pixman_region32_intersect_rect(&scaled, &scaled, 0, 0, clip.width, clip.height);
}
wlr_region_scale(&scaled, &scaled, (float)scale);
- wlr_scene_buffer_set_opaque_region(scene_buffer, &scaled);
+ wlr_scene_buffer_set_opaque_region_pinned(scene_buffer, &scaled);
pixman_region32_fini(&scaled);
}
diff --git a/wrapper.h b/wrapper.h
index 384b0b70..a5e09c23 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -281,6 +281,7 @@ void river_scene_node_set_opacity(struct wlr_scene_node *node, float opacity);
void river_scene_node_set_corner_radius(struct wlr_scene_node *node, int radius);
+void river_scene_buffer_set_geometry_pinned(struct wlr_scene_buffer *scene_buffer, bool pinned);
void river_scene_buffer_set_dest_size_if_changed(struct wlr_scene_buffer *scene_buffer, int width, int height);
int river_scene_buffer_get_width(struct wlr_scene_buffer *scene_buffer);
int river_scene_buffer_get_height(struct wlr_scene_buffer *scene_buffer);