From 5d0236ad51e36388a6c2676c226b11f8304a23b2 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sun, 9 Nov 2025 11:10:29 -0500
Subject: [PATCH] wayland: Don't leave un-acked configurations when throttling
resize events
Compositors that send multiple resize events per frame could result in the most recent configuration requests being left un-acked when the resize ends. Ack the most recent resized state on frame callbacks, so as to always ack the most recent configuration within a reasonable timeframe.
---
src/video/wayland/SDL_waylandwindow.c | 49 ++++++++++++++++-----------
src/video/wayland/SDL_waylandwindow.h | 4 ++-
2 files changed, 33 insertions(+), 20 deletions(-)
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 4cfc8121cbedc..7eb6d104afded 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -301,7 +301,7 @@ static void SetSurfaceOpaqueRegion(SDL_WindowData *wind, bool is_opaque)
}
}
-static bool ConfigureWindowGeometry(SDL_Window *window)
+static void ConfigureWindowGeometry(SDL_Window *window)
{
SDL_WindowData *data = window->internal;
const double scale_factor = GetWindowScale(window);
@@ -310,17 +310,6 @@ static bool ConfigureWindowGeometry(SDL_Window *window)
int window_width, window_height;
bool window_size_changed;
- // Throttle interactive resize events to once per refresh cycle to prevent lag.
- if (data->resizing) {
- data->resizing = false;
-
- if (data->drop_interactive_resizes) {
- return false;
- } else {
- data->drop_interactive_resizes = true;
- }
- }
-
// Set the drawable backbuffer size.
GetBufferSize(window, &data->current.pixel_width, &data->current.pixel_height);
const bool buffer_size_changed = data->current.pixel_width != old_pixel_width ||
@@ -469,8 +458,6 @@ static bool ConfigureWindowGeometry(SDL_Window *window)
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0);
}
}
-
- return true;
}
static void CommitLibdecorFrame(SDL_Window *window)
@@ -690,7 +677,15 @@ static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time
wl_surface_damage(wind->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
}
- wind->drop_interactive_resizes = false;
+ if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
+ if (wind->pending_config_ack) {
+ wind->pending_config_ack = false;
+ ConfigureWindowGeometry(wind->sdlwindow);
+ xdg_surface_ack_configure(wind->shell_surface.xdg.surface, wind->shell_surface.xdg.serial);
+ }
+ } else {
+ wind->resizing = false;
+ }
if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME) {
wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOWN;
@@ -746,8 +741,17 @@ static void handle_xdg_surface_configure(void *data, struct xdg_surface *xdg, ui
SDL_WindowData *wind = (SDL_WindowData *)data;
SDL_Window *window = wind->sdlwindow;
- if (ConfigureWindowGeometry(window)) {
+ /* Interactive resizes are throttled by acking and committing only the most recent configuration at
+ * the next frame callback, or certain combinations of clients and compositors can exhibit severe lag
+ * when resizing.
+ */
+ wind->shell_surface.xdg.serial = serial;
+ if (!wind->resizing) {
+ wind->pending_config_ack = false;
+ ConfigureWindowGeometry(window);
xdg_surface_ack_configure(xdg, serial);
+ } else {
+ wind->pending_config_ack = true;
}
if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) {
@@ -1404,6 +1408,7 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
}
// Store the new state.
+ const bool started_resize = !wind->resizing && resizing;
wind->last_configure.width = width;
wind->last_configure.height = height;
wind->floating = floating;
@@ -1430,9 +1435,15 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
}
#endif
- // Calculate the new window geometry
- if (ConfigureWindowGeometry(window)) {
- // ... then commit the changes on the libdecor side.
+ if (!wind->resizing || started_resize) {
+ /* Calculate the new window geometry and commit the changes on the libdecor side.
+ *
+ * XXX: This will potentially leave un-acked configurations, but libdecor invalidates the
+ * configuration upon returning from the frame event, so there is nothing that can be
+ * done, unless libdecor adds the ability to copy or refcount the configuration state
+ * to apply later.
+ */
+ ConfigureWindowGeometry(window);
struct libdecor_state *state = libdecor_state_new(wind->current.logical_width, wind->current.logical_height);
libdecor_frame_commit(frame, state, configuration);
libdecor_state_free(state);
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index 8c7b4a2cd8919..eeec232b7a9cc 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -63,6 +63,8 @@ struct SDL_WindowData
struct xdg_positioner *xdg_positioner;
} popup;
};
+
+ Uint32 serial;
} xdg;
} shell_surface;
enum
@@ -203,7 +205,7 @@ struct SDL_WindowData
bool suspended;
bool resizing;
bool active;
- bool drop_interactive_resizes;
+ bool pending_config_ack;
bool is_fullscreen;
bool fullscreen_exclusive;
bool drop_fullscreen_requests;