Wayland: Fix race with buffer scaling

We now pass the buffer scale along the window size to the display server
and set everything from there.

This should avoid races where the buffer scale changes while we handle
window rect changes.
This commit is contained in:
Dery Almas
2026-02-23 23:26:52 +01:00
parent c71c2bfc5e
commit 2d74e56caf
3 changed files with 41 additions and 20 deletions
@@ -1901,7 +1901,23 @@ void DisplayServerWayland::process_events() {
Ref<WaylandThread::WindowRectMessage> winrect_msg = msg;
if (winrect_msg.is_valid()) {
int scale = winrect_msg->buffer_scale;
WaylandThread::WindowState *ws = wayland_thread.window_get_state(winrect_msg->id);
ERR_CONTINUE(ws == nullptr);
if (scale == 0) {
// This should *never* happen. We fallback to the latest scale but it might
// have changed in the meantime to something invalid (non-integer divisor),
// leading to a protocol error.
ERR_PRINT("Wayland Thread did not report buffer scale at the time of resize.");
scale = wayland_thread.window_state_get_preferred_buffer_scale(ws);
}
wayland_thread.window_state_set_buffer_scale(ws, scale);
_update_window_rect(winrect_msg->rect, winrect_msg->id);
continue;
}
+14 -12
View File
@@ -1314,17 +1314,6 @@ void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *w
ws->frame_callback = wl_surface_frame(ws->wl_surface);
wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws);
if (ws->wl_surface && ws->buffer_scale_changed) {
// NOTE: We're only now setting the buffer scale as the idea is to get this
// data committed together with the new frame, all by the rendering driver.
// This is important because we might otherwise set an invalid combination of
// buffer size and scale (e.g. odd size and 2x scale). We're pretty much
// guaranteed to get a proper buffer in the next render loop as the rescaling
// method also informs the engine of a "window rect change", triggering
// rendering if needed.
wl_surface_set_buffer_scale(ws->wl_surface, window_state_get_preferred_buffer_scale(ws));
}
}
void WaylandThread::_wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {
@@ -1597,6 +1586,7 @@ void WaylandThread::_xdg_popup_on_configure(void *data, struct xdg_popup *xdg_po
rect_msg->id = ws->id;
rect_msg->rect.position = scale_vector2i(ws->rect.position, parent_scale);
rect_msg->rect.size = scale_vector2i(ws->rect.size, parent_scale);
rect_msg->buffer_scale = window_state_get_preferred_buffer_scale(ws);
ws->wayland_thread->push_message(rect_msg);
}
@@ -3788,7 +3778,6 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int
if (p_ws->buffer_scale != preferred_buffer_scale) {
// The buffer scale is always important, even if we use frac scaling.
p_ws->buffer_scale = preferred_buffer_scale;
p_ws->buffer_scale_changed = true;
if (!using_fractional) {
// We don't bother updating everything else if it's turned on though.
@@ -3833,6 +3822,7 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int
rect_msg->id = p_ws->id;
rect_msg->rect.position = scale_vector2i(p_ws->rect.position, win_scale);
rect_msg->rect.size = scaled_size;
rect_msg->buffer_scale = preferred_buffer_scale;
p_ws->wayland_thread->push_message(rect_msg);
}
@@ -3845,6 +3835,18 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int
}
}
void WaylandThread::window_state_set_buffer_scale(WindowState *p_ws, int p_buffer_scale) {
ERR_FAIL_NULL(p_ws);
ERR_FAIL_COND(p_buffer_scale <= 0);
ERR_FAIL_NULL(p_ws->wl_surface);
if (p_ws->buffer_scale != p_buffer_scale) {
p_ws->buffer_scale = p_buffer_scale;
wl_surface_set_buffer_scale(p_ws->wl_surface, p_buffer_scale);
}
}
// Scales a vector according to wp_fractional_scale's rules, where coordinates
// must be scaled with away from zero half-rounding.
Vector2i WaylandThread::scale_vector2i(const Vector2i &p_vector, double p_amount) {
+11 -8
View File
@@ -133,9 +133,17 @@ public:
GDSOFTCLASS(WindowRectMessage, WindowMessage);
public:
// NOTE: This is in "scaled" terms. For example, if there's a 1920x1080 rect
// with a scale factor of 2, the actual value of `rect` will be 3840x2160.
// The window size in "absolute units" (pre-scaled). Basically the desired
// resolution of the underlying buffer.
Rect2i rect;
// Used to synchronize buffer size and scale together, otherwise we risk a
// protocol error. Note that this doesn't control scaling in general, only the
// concept of "surface buffer scale". See the protocol documentation of
// `wl_surface::set_buffer_scale` for more details.
//
// Defaults to 0 so that we can catch malformed messages.
int buffer_scale = 0;
};
class WindowEventMessage : public WindowMessage {
@@ -333,12 +341,6 @@ public:
// Currently applied buffer scale.
int buffer_scale = 1;
// Buffer scale must be applied right before rendering but _after_ committing
// everything else or otherwise we might have an inconsistent state (e.g.
// double scale and odd resolution). This flag assists with that; when set,
// on the next frame, we'll commit whatever is set in `buffer_scale`.
bool buffer_scale_changed = false;
// NOTE: The preferred buffer scale is currently only dynamically calculated.
// It can be accessed by calling `window_state_get_preferred_buffer_scale`.
@@ -1206,6 +1208,7 @@ public:
static int window_state_get_preferred_buffer_scale(WindowState *p_ws);
static double window_state_get_scale_factor(const WindowState *p_ws);
static void window_state_update_size(WindowState *p_ws, int p_width, int p_height);
static void window_state_set_buffer_scale(WindowState *p_ws, int p_buffer_scale);
static Vector2i scale_vector2i(const Vector2i &p_vector, double p_amount);