SDL: wayland: Support new high-DPI system

From b2cfcbdb64456168a5ecd7451ff37c8f67d77b80 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Wed, 25 Jan 2023 21:49:18 -0500
Subject: [PATCH] wayland: Support new high-DPI system

- Adds support for scaled fullscreen modes
- General cleanup of scale factor setting and usage
---
 src/video/wayland/SDL_waylandmouse.c  |   2 +-
 src/video/wayland/SDL_waylandvideo.c  |  45 +++++++--
 src/video/wayland/SDL_waylandwindow.c | 129 ++++++++++----------------
 src/video/wayland/SDL_waylandwindow.h |   2 +-
 4 files changed, 87 insertions(+), 91 deletions(-)

diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c
index 4e925999904f..d295c21ca242 100644
--- a/src/video/wayland/SDL_waylandmouse.c
+++ b/src/video/wayland/SDL_waylandmouse.c
@@ -194,7 +194,7 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa
     focusdata = focus->driverdata;
 
     /* Cursors use integer scaling. */
-    *scale = SDL_ceilf(focusdata->scale_factor);
+    *scale = SDL_ceilf(focusdata->windowed_scale_factor);
     size *= *scale;
     for (i = 0; i < vdata->num_cursor_themes; i += 1) {
         if (vdata->cursor_themes[i].size == size) {
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 65308674ccdd..d354d2c82bfb 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -419,6 +419,7 @@ static void AddEmulatedModes(SDL_VideoDisplay *dpy, SDL_bool rot_90)
 
     for (i = 0; i < SDL_arraysize(mode_list); ++i) {
         mode = *dpy->display_modes;
+        mode.display_scale = 1.0f;
 
         if (rot_90) {
             mode.w = mode_list[i].h;
@@ -570,7 +571,7 @@ static void display_handle_done(void *data,
         native_mode.w = driverdata->native_width;
         native_mode.h = driverdata->native_height;
     }
-    native_mode.display_scale = 1.0f; /* FIXME */
+    native_mode.display_scale = 1.0f;
     native_mode.refresh_rate = ((100 * driverdata->refresh) / 1000) / 100.0f; /* mHz to Hz */
     native_mode.driverdata = driverdata->output;
 
@@ -579,7 +580,9 @@ static void display_handle_done(void *data,
     desktop_mode.format = SDL_PIXELFORMAT_RGB888;
 
     if (driverdata->has_logical_size) { /* If xdg-output is present, calculate the true scale of the desktop */
-        driverdata->scale_factor = (float)native_mode.w / (float)driverdata->width;
+        if (video->viewporter) {
+            driverdata->scale_factor = (float)native_mode.w / (float)driverdata->width;
+        }
     } else { /* Scale the desktop coordinates, if xdg-output isn't present */
         driverdata->width /= driverdata->scale_factor;
         driverdata->height /= driverdata->scale_factor;
@@ -593,21 +596,43 @@ static void display_handle_done(void *data,
         desktop_mode.w = driverdata->height;
         desktop_mode.h = driverdata->width;
     }
+    desktop_mode.display_scale = driverdata->scale_factor;
     desktop_mode.refresh_rate = ((100 * driverdata->refresh) / 1000) / 100.0f; /* mHz to Hz */
     desktop_mode.driverdata = driverdata->output;
 
-    /*
-     * The native display mode is only exposed separately from the desktop size if the
-     * desktop is scaled and the wp_viewporter protocol is supported.
-     */
-    if (driverdata->scale_factor > 1.0f && video->viewporter != NULL) {
-        if (driverdata->index > -1) {
-            SDL_AddDisplayMode(SDL_GetDisplay(driverdata->index), &native_mode);
+    if (driverdata->index > -1) {
+        dpy = SDL_GetDisplay(driverdata->index);
+    } else {
+        dpy = &driverdata->placeholder;
+    }
+
+    /* Set the desktop display mode. */
+    SDL_AddDisplayMode(dpy, &desktop_mode);
+    SDL_SetCurrentDisplayMode(dpy, &desktop_mode);
+    SDL_SetDesktopDisplayMode(dpy, &desktop_mode);
+
+    /* If the desktop is scaled... */
+    if (driverdata->scale_factor > 1.0f) {
+        /* ...expose the native resolution if viewports are available... */
+        if (video->viewporter != NULL) {
+            SDL_AddDisplayMode(dpy, &native_mode);
         } else {
-            SDL_AddDisplayMode(&driverdata->placeholder, &native_mode);
+            /* ...if not, expose the integer scale desktop modes down to 1.0. */
+            int i;
+            for (i = (int)driverdata->scale_factor - 1; i > 0; --i) {
+                desktop_mode.display_scale = (float)i;
+                SDL_AddDisplayMode(dpy, &desktop_mode);
+            }
         }
     }
 
+    /* Add emulated modes if wp_viewporter is supported and mode emulation is enabled. */
+    if (video->viewporter && mode_emulation_enabled) {
+        const SDL_bool rot_90 = ((driverdata->transform & WL_OUTPUT_TRANSFORM_90) != 0) ||
+                                (driverdata->width < driverdata->height);
+        AddEmulatedModes(dpy, rot_90);
+    }
+
     /* Calculate the display DPI */
     if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) {
         driverdata->hdpi = driverdata->physical_height ? (((float)driverdata->height) * 25.4f / driverdata->physical_height) : 0.0f;
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index ddd6b310c0ff..b9afc46dde38 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -57,7 +57,8 @@ SDL_FORCE_INLINE SDL_bool FloatEqual(float a, float b)
 static void GetFullScreenDimensions(SDL_Window *window, int *width, int *height, int *drawable_width, int *drawable_height)
 {
     SDL_WindowData *wind = (SDL_WindowData *)window->driverdata;
-    SDL_WaylandOutputData *output = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata;
+    SDL_VideoDisplay *disp = SDL_GetDisplayForWindow(window);
+    SDL_WaylandOutputData *output = (SDL_WaylandOutputData *)disp->driverdata;
 
     int fs_width, fs_height;
     int buf_width, buf_height;
@@ -82,23 +83,18 @@ static void GetFullScreenDimensions(SDL_Window *window, int *width, int *height,
             buf_height = fs_height;
         }
     } else {
-        /*
-         * If a mode was set, use it, otherwise use the native resolution
-         * for DPI aware apps and the desktop size for legacy apps.
-         */
+        /* If a mode was set, use it, otherwise use the native resolution. */
         if (window->fullscreen_mode.w != 0 && window->fullscreen_mode.h != 0) {
             fs_width = window->fullscreen_mode.w;
             fs_height = window->fullscreen_mode.h;
-        } else if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
-            fs_width = output->native_width;
-            fs_height = output->native_height;
+            buf_width = (int)SDL_lroundf((float)fs_width * window->fullscreen_mode.display_scale);
+            buf_height = (int)SDL_lroundf((float)fs_height * window->fullscreen_mode.display_scale);
         } else {
-            fs_width = output_width;
-            fs_height = output_height;
+            fs_width = disp->display_modes[0].w;
+            fs_height = disp->display_modes[0].h;
+            buf_width = (int)SDL_lroundf((float)fs_width * disp->display_modes[0].display_scale);
+            buf_height = (int)SDL_lroundf((float)fs_width * disp->display_modes[0].display_scale);
         }
-
-        buf_width = fs_width;
-        buf_height = fs_height;
     }
 
     if (width) {
@@ -115,18 +111,19 @@ static void GetFullScreenDimensions(SDL_Window *window, int *width, int *height,
     }
 }
 
-SDL_FORCE_INLINE SDL_bool SurfaceScaleIsFractional(SDL_Window *window)
-{
-    SDL_WindowData *data = window->driverdata;
-    return !FloatEqual(SDL_roundf(data->scale_factor), data->scale_factor);
-}
-
 SDL_FORCE_INLINE SDL_bool FullscreenModeEmulation(SDL_Window *window)
 {
     return (window->flags & SDL_WINDOW_FULLSCREEN) &&
            ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP);
 }
 
+SDL_bool SurfaceScaleIsFractional(SDL_Window *window)
+{
+    SDL_WindowData *data = window->driverdata;
+    const float scale_value = !FullscreenModeEmulation(window) ? data->windowed_scale_factor : window->fullscreen_mode.display_scale;
+    return !FloatEqual(SDL_roundf(scale_value), scale_value);
+}
+
 static SDL_bool WindowNeedsViewport(SDL_Window *window)
 {
     SDL_WindowData *wind = window->driverdata;
@@ -163,19 +160,12 @@ static void GetBufferSize(SDL_Window *window, int *width, int *height)
         GetFullScreenDimensions(window, NULL, NULL, &buf_width, &buf_height);
     } else if (WindowNeedsViewport(window)) {
         /* Round fractional backbuffer sizes halfway away from zero. */
-        buf_width = (int)SDL_lroundf(window->w * data->scale_factor);
-        buf_height = (int)SDL_lroundf(window->h * data->scale_factor);
+        buf_width = (int)SDL_lroundf((float)window->w * data->windowed_scale_factor);
+        buf_height = (int)SDL_lroundf((float)window->h * data->windowed_scale_factor);
     } else {
-        /*
-         * Integer scaled windowed or fullscreen with no viewport
-         *
-         * Round the scale factor up in the unlikely scenario of a compositor
-         * that supports fractional scaling, but not viewports.
-         */
-        int scale_factor = (int)SDL_ceilf(data->scale_factor);
-
-        buf_width = window->w * scale_factor;
-        buf_height = window->h * scale_factor;
+        /* Integer scaled windowed or fullscreen with no viewport */
+        buf_width = window->w * (int)data->windowed_scale_factor;
+        buf_height = window->h * (int)data->windowed_scale_factor;
     }
 
     if (width) {
@@ -233,7 +223,7 @@ static void ConfigureWindowGeometry(SDL_Window *window)
                                      0, 0);
     }
 
-    if (FullscreenModeEmulation(window) && WindowNeedsViewport(window)) {
+    if (FullscreenModeEmulation(window)) {
         int fs_width, fs_height;
         const int output_width = data->fs_output_width ? data->fs_output_width : output->width;
         const int output_height = data->fs_output_height ? data->fs_output_height : output->height;
@@ -243,11 +233,14 @@ static void ConfigureWindowGeometry(SDL_Window *window)
         if (window_size_changed || drawable_size_changed) {
             GetFullScreenDimensions(window, &fs_width, &fs_height, NULL, NULL);
 
-            /* Set the buffer scale to 1 since a viewport will be used. */
-            wl_surface_set_buffer_scale(data->surface, 1);
-            SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height,
-                                   output_width, output_height);
-
+            if (WindowNeedsViewport(window)) {
+                /* Set the buffer scale to 1 since a viewport will be used. */
+                wl_surface_set_buffer_scale(data->surface, 1);
+                SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height,
+                                       output_width, output_height);
+            } else {
+                wl_surface_set_buffer_scale(data->surface, (int32_t)window->fullscreen_mode.display_scale);
+            }
             data->window_width = output_width;
             data->window_height = output_height;
 
@@ -263,9 +256,7 @@ static void ConfigureWindowGeometry(SDL_Window *window)
                 SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height, window->w, window->h);
             } else {
                 UnsetDrawSurfaceViewport(window);
-
-                /* Round to the next integer in case of a fractional value. */
-                wl_surface_set_buffer_scale(data->surface, (int32_t)SDL_ceilf(data->scale_factor));
+                wl_surface_set_buffer_scale(data->surface, (int32_t)data->windowed_scale_factor);
             }
 
             /* Clamp the physical window size to the system minimum required size. */
@@ -528,14 +519,14 @@ static const struct wl_callback_listener gles_swap_frame_listener = {
     gles_swap_frame_done
 };
 
-static void Wayland_HandleResize(SDL_Window *window, int width, int height, float scale);
+static void Wayland_HandleResize(SDL_Window *window, int width, int height);
 
 static void handle_configure_xdg_shell_surface(void *data, struct xdg_surface *xdg, uint32_t serial)
 {
     SDL_WindowData *wind = (SDL_WindowData *)data;
     SDL_Window *window = wind->sdlwindow;
 
-    Wayland_HandleResize(window, window->w, window->h, wind->scale_factor);
+    Wayland_HandleResize(window, window->w, window->h);
     xdg_surface_ack_configure(xdg, serial);
 
     wind->shell_surface.xdg.initial_configure_seen = SDL_TRUE;
@@ -553,7 +544,6 @@ static void handle_configure_xdg_toplevel(void *data,
 {
     SDL_WindowData *wind = (SDL_WindowData *)data;
     SDL_Window *window = wind->sdlwindow;
-    SDL_WaylandOutputData *driverdata;
 
     enum xdg_toplevel_state *state;
     SDL_bool fullscreen = SDL_FALSE;
@@ -580,12 +570,10 @@ static void handle_configure_xdg_toplevel(void *data,
         }
     }
 
-    driverdata = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata;
-
     UpdateWindowFullscreen(window, fullscreen);
 
     if (!fullscreen) {
-        if (width == 0 || height == 0) {
+        if ((floating && !wind->was_floating) || width == 0 || height == 0) {
             /* This usually happens when we're being restored from a
              * non-floating state, so use the cached floating size here.
              */
@@ -658,13 +646,9 @@ static void handle_configure_xdg_toplevel(void *data,
             window->h = height;
             wind->needs_resize_event = SDL_TRUE;
         }
-
-        /* This part is good though. */
-        if ((window->flags & SDL_WINDOW_ALLOW_HIGHDPI) && !FloatEqual(wind->scale_factor, driverdata->scale_factor)) {
-            wind->scale_factor = driverdata->scale_factor;
-            wind->needs_resize_event = SDL_TRUE;
-        }
     }
+
+    wind->was_floating = floating;
 }
 
 static void handle_close_xdg_toplevel(void *data, struct xdg_toplevel *xdg_toplevel)
@@ -813,12 +797,10 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
 {
     SDL_WindowData *wind = (SDL_WindowData *)user_data;
     SDL_Window *window = wind->sdlwindow;
-    SDL_WaylandOutputData *driverdata;
     struct libdecor_state *state;
 
     enum libdecor_window_state window_state;
     int width, height;
-    float scale_factor = wind->scale_factor;
 
     SDL_bool focused = SDL_FALSE;
     SDL_bool fullscreen = SDL_FALSE;
@@ -838,8 +820,6 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
     }
     floating = !(fullscreen || maximized || tiled);
 
-    driverdata = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata;
-
     UpdateWindowFullscreen(window, fullscreen);
 
     if (!fullscreen) {
@@ -881,11 +861,6 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
         if (FullscreenModeEmulation(window)) {
             GetFullScreenDimensions(window, &width, &height, NULL, NULL);
         }
-
-        /* This part is good though. */
-        if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
-            scale_factor = driverdata->scale_factor;
-        }
     } else if (!(window->flags & SDL_WINDOW_RESIZABLE) || (floating && wind->floating_resize_pending)) {
         width = window->windowed.w;
         height = window->windowed.h;
@@ -930,7 +905,7 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
     wind->was_floating = floating;
 
     /* Do the resize on the SDL side (this will set window->w/h)... */
-    Wayland_HandleResize(window, width, height, scale_factor);
+    Wayland_HandleResize(window, width, height);
 
     /* ... then commit the changes on the libdecor side. */
     state = libdecor_state_new(wind->window_width, wind->window_height);
@@ -997,7 +972,7 @@ static const struct qt_extended_surface_listener extended_surface_listener = {
 
 static void update_scale_factor(SDL_WindowData *window)
 {
-    float old_factor = window->scale_factor;
+    float old_factor = window->windowed_scale_factor;
     float new_factor;
     int i;
 
@@ -1006,25 +981,21 @@ static void update_scale_factor(SDL_WindowData *window)
         return;
     }
 
-    if (FULLSCREEN_VISIBLE(window->sdlwindow)) {
-        /* For fullscreen, use the active display's scale factor */
-        SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window->sdlwindow);
-        SDL_WaylandOutputData *driverdata = display->driverdata;
-        new_factor = driverdata->scale_factor;
-    } else if (window->num_outputs == 0) {
-        /* No monitor (somehow)? Just fall back. */
-        new_factor = old_factor;
-    } else {
+    if (window->num_outputs != 0) {
         /* Check every display's factor, use the highest */
         new_factor = 0.0f;
         for (i = 0; i < window->num_outputs; i++) {
             SDL_WaylandOutputData *driverdata = window->outputs[i];
             new_factor = SDL_max(new_factor, driverdata->scale_factor);
         }
+    } else {
+        /* No monitor (somehow)? Just fall back. */
+        new_factor = old_factor;
     }
 
     if (!FloatEqual(new_factor, old_factor)) {
-        Wayland_HandleResize(window->sdlwindow, window->sdlwindow->w, window->sdlwindow->h, new_factor);
+        window->windowed_scale_factor = new_factor;
+        Wayland_HandleResize(window->sdlwindow, window->sdlwindow->w, window->sdlwindow->h);
     }
 }
 
@@ -1576,7 +1547,7 @@ void handle_preferred_scale_changed(void *data,
                                     uint preferred_scale)
 {
     SDL_WindowData *window = data;
-    float old_factor = window->scale_factor;
+    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)) {
@@ -1585,7 +1556,8 @@ void handle_preferred_scale_changed(void *data,
     }
 
     if (!FloatEqual(new_factor, old_factor)) {
-        Wayland_HandleResize(window->sdlwindow, window->sdlwindow->w, window->sdlwindow->h, new_factor);
+        window->windowed_scale_factor = new_factor;
+        Wayland_HandleResize(window->sdlwindow, window->sdlwindow->w, window->sdlwindow->h);
     }
 }
 
@@ -1925,13 +1897,13 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
     data->waylandData = c;
     data->sdlwindow = window;
 
-    data->scale_factor = 1.0f;
+    data->windowed_scale_factor = 1.0f;
 
     if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
         int i;
         for (i = 0; i < SDL_GetVideoDevice()->num_displays; i++) {
             float scale = ((SDL_WaylandOutputData *)SDL_GetVideoDevice()->displays[i].driverdata)->scale_factor;
-            data->scale_factor = SDL_max(data->scale_factor, scale);
+            data->windowed_scale_factor = SDL_max(data->windowed_scale_factor, scale);
         }
     }
 
@@ -2033,7 +2005,7 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
     return 0;
 }
 
-static void Wayland_HandleResize(SDL_Window *window, int width, int height, float scale)
+static void Wayland_HandleResize(SDL_Window *window, int width, int height)
 {
     SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
     const int old_w = window->w, old_h = window->h;
@@ -2043,7 +2015,6 @@ static void Wayland_HandleResize(SDL_Window *window, int width, int height, floa
     /* Update the window geometry. */
     window->w = width;
     window->h = height;
-    data->scale_factor = scale;
     ConfigureWindowGeometry(window);
 
     if (data->needs_resize_event ||
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index 97debadbec9c..f17f8dd330a8 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -103,7 +103,7 @@ typedef struct
     SDL_WaylandOutputData **outputs;
     int num_outputs;
 
-    float scale_factor;
+    float windowed_scale_factor;
     float pointer_scale_x;
     float pointer_scale_y;
     int drawable_width, drawable_height;