SDL: wayland: Use the integer buffer scale event when applicable

From 8162d6659af506ac4d65e090667c30c0617ca17e Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Fri, 7 Apr 2023 17:27:50 -0400
Subject: [PATCH] wayland: Use the integer buffer scale event when applicable

wl_compositor v6 introduces the preferred buffer scale event, which serves a similar function to the fractional scale protocol, but deals in integer scale factors. Listen to this event when the wl_compositor version is >= 6 and the fractional scale protocol is not present to set the scale factor for surfaces.
---
 src/video/wayland/SDL_waylandvideo.c  |   8 +-
 src/video/wayland/SDL_waylandwindow.c | 101 +++++++++++++++-----------
 2 files changed, 66 insertions(+), 43 deletions(-)

diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index c919e8314ed2..de63c6bcb8ba 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -62,6 +62,12 @@
 
 #define WAYLANDVID_DRIVER_NAME "wayland"
 
+#if SDL_WAYLAND_CHECK_VERSION(1, 22, 0)
+#define SDL_WL_COMPOSITOR_VERSION 6
+#else
+#define SDL_WL_COMPOSITOR_VERSION 4
+#endif
+
 #if SDL_WAYLAND_CHECK_VERSION(1, 20, 0)
 #define SDL_WL_OUTPUT_VERSION 4
 #else
@@ -792,7 +798,7 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
     /*printf("WAYLAND INTERFACE: %s\n", interface);*/
 
     if (SDL_strcmp(interface, "wl_compositor") == 0) {
-        d->compositor = wl_registry_bind(d->registry, id, &wl_compositor_interface, SDL_min(4, version));
+        d->compositor = wl_registry_bind(d->registry, id, &wl_compositor_interface, SDL_min(SDL_WL_COMPOSITOR_VERSION, version));
     } else if (SDL_strcmp(interface, "wl_output") == 0) {
         Wayland_add_display(d, id, SDL_min(version, SDL_WL_OUTPUT_VERSION));
     } else if (SDL_strcmp(interface, "wl_seat") == 0) {
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 0e7da693a6bc..aadbf1760e9f 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -968,33 +968,48 @@ static const struct qt_extended_surface_listener extended_surface_listener = {
 };
 #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
 
-static void update_scale_factor(SDL_WindowData *window)
+static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, float factor)
 {
-    float old_factor = window->windowed_scale_factor;
-    float new_factor;
-    int i;
+    const float old_factor = window_data->windowed_scale_factor;
 
-    if (!(window->sdlwindow->flags & SDL_WINDOW_ALLOW_HIGHDPI)) {
+    if (!(window_data->sdlwindow->flags & SDL_WINDOW_ALLOW_HIGHDPI)) {
         /* Scale will always be 1, just ignore this */
         return;
     }
 
+    if (!FloatEqual(factor, old_factor)) {
+        window_data->windowed_scale_factor = factor;
+        ConfigureWindowGeometry(window_data->sdlwindow);
+    }
+}
+
+static void Wayland_MaybeUpdateScaleFactor(SDL_WindowData *window)
+{
+    float factor;
+    int i;
+
+    /* If the fractional scale protocol is present or the core protocol supports the
+     * preferred buffer scale event, the compositor will tell explicitly the application
+     * what scale it wants via these events, so don't try to determine the scale factor
+     * from which displays the surface has entered.
+     */
+    if (window->fractional_scale || wl_surface_get_version(window->surface) >= WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) {
+        return;
+    }
+
     if (window->num_outputs != 0) {
         /* Check every display's factor, use the highest */
-        new_factor = 0.0f;
+        factor = 0.0f;
         for (i = 0; i < window->num_outputs; i++) {
             SDL_DisplayData *driverdata = window->outputs[i];
-            new_factor = SDL_max(new_factor, driverdata->scale_factor);
+            factor = SDL_max(factor, driverdata->scale_factor);
         }
     } else {
         /* No monitor (somehow)? Just fall back. */
-        new_factor = old_factor;
+        factor = window->windowed_scale_factor;
     }
 
-    if (!FloatEqual(new_factor, old_factor)) {
-        window->windowed_scale_factor = new_factor;
-        ConfigureWindowGeometry(window->sdlwindow);
-    }
+    Wayland_HandlePreferredScaleChanged(window, factor);
 }
 
 /* While we can't get window position from the compositor, we do at least know
@@ -1060,10 +1075,7 @@ static void handle_surface_enter(void *data, struct wl_surface *surface,
 
     /* Update the scale factor after the move so that fullscreen outputs are updated. */
     Wayland_move_window(window->sdlwindow, driverdata);
-
-    if (!window->fractional_scale) {
-        update_scale_factor(window);
-    }
+    Wayland_MaybeUpdateScaleFactor(window);
 }
 
 static void handle_surface_leave(void *data, struct wl_surface *surface,
@@ -1100,14 +1112,42 @@ static void handle_surface_leave(void *data, struct wl_surface *surface,
                             window->outputs[window->num_outputs - 1]);
     }
 
-    if (!window->fractional_scale) {
-        update_scale_factor(window);
+    Wayland_MaybeUpdateScaleFactor(window);
+}
+
+static void handle_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor)
+{
+    SDL_WindowData *wind = data;
+
+    /* The spec is unclear on how this interacts with the fractional scaling protocol,
+     * so, for now, assume that the fractional scaling protocol takes priority and
+     * only listen to this event if the fractional scaling protocol is not present.
+     */
+    if (!wind->fractional_scale) {
+        Wayland_HandlePreferredScaleChanged(data, (float)factor);
     }
 }
 
+static void handle_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform)
+{
+    /* Nothing to do here. */
+}
+
 static const struct wl_surface_listener surface_listener = {
     handle_surface_enter,
-    handle_surface_leave
+    handle_surface_leave,
+    handle_preferred_buffer_scale,
+    handle_preferred_buffer_transform
+};
+
+static void handle_preferred_fractional_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale)
+{
+    const float factor = scale / 120.; /* 120 is a magic number defined in the spec as a common denominator */
+    Wayland_HandlePreferredScaleChanged(data, factor);
+}
+
+static const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
+    handle_preferred_fractional_scale
 };
 
 static void SetKeyboardFocus(SDL_Window *window)
@@ -1605,29 +1645,6 @@ int Wayland_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation)
     return 0;
 }
 
-static void handle_preferred_scale_changed(void *data,
-                                    struct wp_fractional_scale_v1 *wp_fractional_scale_v1,
-                                    uint preferred_scale)
-{
-    SDL_WindowData *window = data;
-    float old_factor = window->windowed_scale_factor;
-    float new_factor = preferred_scale / 120.; /* 120 is a magic number defined in the spec as a common denominator*/
-
-    if (!(window->sdlwindow->flags & SDL_WINDOW_ALLOW_HIGHDPI)) {
-        /* Scale will always be 1, just ignore this */
-        return;
-    }
-
-    if (!FloatEqual(new_factor, old_factor)) {
-        window->windowed_scale_factor = new_factor;
-        ConfigureWindowGeometry(window->sdlwindow);
-    }
-}
-
-static const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
-    handle_preferred_scale_changed
-};
-
 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
 static void SDLCALL QtExtendedSurface_OnHintChanged(void *userdata, const char *name,
                                                     const char *oldValue, const char *newValue)