From c5641f6de56a1e5125c69dd3be5cd5ff104f8c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 21 Mar 2026 08:45:48 +0000 Subject: [PATCH] output: limit rate of hardware cursor texture change A client that generates lots of cursor image changes could cause a high render load, or deplete the output's cursor swapchain on some backends. Limit to two renders per frame, and catch up after output commit if necessary. --- include/types/wlr_output.h | 1 + include/wlr/types/wlr_output.h | 2 ++ types/output/cursor.c | 14 +++++++++++++- types/output/output.c | 6 ++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/types/wlr_output.h b/include/types/wlr_output.h index fdbffaf9d..a7db7047d 100644 --- a/include/types/wlr_output.h +++ b/include/types/wlr_output.h @@ -17,6 +17,7 @@ bool output_pick_format(struct wlr_output *output, bool output_ensure_buffer(struct wlr_output *output, struct wlr_output_state *state, bool *new_back_buffer); +bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor); bool output_cursor_set_texture(struct wlr_output_cursor *cursor, struct wlr_texture *texture, bool own_texture, const struct wlr_fbox *src_box, int dst_width, int dst_height, enum wl_output_transform transform, diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index c8e44b0e6..1e06bad40 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -278,6 +278,8 @@ struct wlr_output { struct wlr_output_image_description image_description_value; struct wlr_color_transform *color_transform; struct wlr_color_primaries default_primaries_value; + int cursor_uploaded_this_frame; + struct wlr_output_cursor *cursor_pending_upload; } WLR_PRIVATE; }; diff --git a/types/output/cursor.c b/types/output/cursor.c index 4a823dab1..67d0ab913 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -14,6 +14,11 @@ #include "types/wlr_buffer.h" #include "types/wlr_output.h" +// Maximum of hardware cursor renders per frame. +// Many clients don't regroup the buffer commit and the hotspot change in a +// single message : typical burst of 2 must still be processed without delay +#define MAX_UPLOADS_PER_FRAME 2 + static bool output_set_hardware_cursor(struct wlr_output *output, struct wlr_buffer *buffer, int hotspot_x, int hotspot_y) { if (!output->impl->set_cursor) { @@ -55,6 +60,7 @@ static void output_disable_hardware_cursor(struct wlr_output *output) { output_set_hardware_cursor(output, NULL, 0, 0); output_cursor_damage_whole(output->hardware_cursor); output->hardware_cursor = NULL; + output->cursor_pending_upload = NULL; } void wlr_output_lock_software_cursors(struct wlr_output *output, bool lock) { @@ -288,7 +294,7 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) return buffer; } -static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { +bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { struct wlr_output *output = cursor->output; if (!output->impl->set_cursor || output->software_cursor_locks > 0) { @@ -297,6 +303,11 @@ static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { struct wlr_texture *texture = cursor->texture; + if (texture != NULL && output->cursor_uploaded_this_frame >= MAX_UPLOADS_PER_FRAME) { + output->cursor_pending_upload = cursor; + return true; + } + // If the cursor was hidden or was a software cursor, the hardware // cursor position is outdated output_move_hardware_cursor(cursor->output, (int)cursor->x, (int)cursor->y); @@ -308,6 +319,7 @@ static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { wlr_log(WLR_DEBUG, "Failed to render cursor buffer"); return false; } + output->cursor_uploaded_this_frame++; } struct wlr_box hotspot = { diff --git a/types/output/output.c b/types/output/output.c index 46da1f425..cb26353b0 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -841,6 +841,12 @@ bool wlr_output_commit_state(struct wlr_output *output, wlr_buffer_unlock(pending.buffer); } + output->cursor_uploaded_this_frame = 0; + if (output->cursor_pending_upload) { + output_cursor_attempt_hardware(output->cursor_pending_upload); + output->cursor_pending_upload = NULL; + } + return true; }