sdl2-compat: Add SDL_GetClosestDisplayMode()

From 54171734b834ed4b873eb1a806a82a2305c37b19 Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Fri, 3 Feb 2023 09:45:50 +0100
Subject: [PATCH] Add SDL_GetClosestDisplayMode() Fix return code of
 SDL_GetDisplayMode

---
 src/sdl2_compat.c | 188 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 176 insertions(+), 12 deletions(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index b605b80..f392c1d 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -3387,7 +3387,7 @@ SDL_GetDisplayMode(int displayIndex, int modeIndex, SDL2_DisplayMode *mode)
         if (mode) {
             DisplayMode_3to2(list[modeIndex], mode);
         }
-        ret = 1;
+        ret = 0;
     }
     SDL_free(list);
     return ret;
@@ -3428,22 +3428,186 @@ SDL_GetWindowDisplayMode(SDL_Window *window, SDL2_DisplayMode *mode)
     return ret;
 }
 
+static void SDL_FinalizeDisplayMode(SDL_DisplayMode *mode)
+{
+    /* Make sure all the fields are set up correctly */
+    if (mode->display_scale <= 0.0f) {
+        if (mode->screen_w == 0 && mode->screen_h == 0) {
+            mode->screen_w = mode->pixel_w;
+            mode->screen_h = mode->pixel_h;
+        }
+        if (mode->pixel_w == 0 && mode->pixel_h == 0) {
+            mode->pixel_w = mode->screen_w;
+            mode->pixel_h = mode->screen_h;
+        }
+        if (mode->screen_w > 0) {
+            mode->display_scale = (float)mode->pixel_w / mode->screen_w;
+        }
+    } else {
+        if (mode->screen_w == 0 && mode->screen_h == 0) {
+            mode->screen_w = (int)SDL_floorf(mode->pixel_w / mode->display_scale);
+            mode->screen_h = (int)SDL_floorf(mode->pixel_h / mode->display_scale);
+        }
+        if (mode->pixel_w == 0 && mode->pixel_h == 0) {
+            mode->pixel_w = (int)SDL_ceilf(mode->screen_w * mode->display_scale);
+            mode->pixel_h = (int)SDL_ceilf(mode->screen_h * mode->display_scale);
+        }
+    }
+
+    /* Make sure the screen width, pixel width, and display scale all match */
+    if (mode->display_scale != 0.0f) {
+        SDL_assert(mode->display_scale > 0.0f);
+        SDL_assert(SDL_fabsf(mode->screen_w - (mode->pixel_w / mode->display_scale)) < 1.0f);
+        SDL_assert(SDL_fabsf(mode->screen_h - (mode->pixel_h / mode->display_scale)) < 1.0f);
+    }
+}
+
+static SDL_DisplayMode *SDL_GetClosestDisplayModeForDisplay(/* SDL_VideoDisplay *display, */
+                                                            SDL_DisplayID displayID,
+                                                            const SDL_DisplayMode *mode,
+                                                            SDL_DisplayMode *closest)
+{
+    Uint32 target_format;
+    float target_refresh_rate;
+    int i;
+    SDL_DisplayMode requested_mode;
+    SDL_DisplayMode *current, *match;
+    SDL_DisplayMode **list;
+    int count = 0;
+
+    /* Make sure all the fields are filled out in the requested mode */
+    requested_mode = *mode;
+    SDL_FinalizeDisplayMode(&requested_mode);
+    mode = &requested_mode;
+
+    /* Default to the desktop format */
+    if (mode->format) {
+        target_format = mode->format;
+    } else {
+// FIXME: Desktop mode ?
+//        target_format = display->desktop_mode.format;
+        target_format = mode->format;
+    }
+
+    /* Default to the desktop refresh rate */
+    if (mode->refresh_rate > 0.0f) {
+        target_refresh_rate = mode->refresh_rate;
+    } else {
+// FIXME: Desktop mode ?
+//        target_refresh_rate = display->desktop_mode.refresh_rate;
+        target_refresh_rate = mode->refresh_rate;
+    }
+
+    match = NULL;
+    list = SDL3_GetFullscreenDisplayModes(displayID, &count);
+    for (i = 0; i < count; ++i) {
+        current = list[i];
+
+        if (current->pixel_w && (current->pixel_w < mode->pixel_w)) {
+            /* Out of sorted modes large enough here */
+            break;
+        }
+        if (current->pixel_h && (current->pixel_h < mode->pixel_h)) {
+            if (current->pixel_w && (current->pixel_w == mode->pixel_w)) {
+                /* Out of sorted modes large enough here */
+                break;
+            }
+            /* Wider, but not tall enough, due to a different
+               aspect ratio. This mode must be skipped, but closer
+               modes may still follow. */
+            continue;
+        }
+        if (match == NULL || current->pixel_w < match->pixel_w || current->pixel_h < match->pixel_h) {
+            match = current;
+            continue;
+        }
+        if (current->format != match->format) {
+            /* Sorted highest depth to lowest */
+            if (current->format == target_format ||
+                (SDL_BITSPERPIXEL(current->format) >=
+                     SDL_BITSPERPIXEL(target_format) &&
+                 SDL_PIXELTYPE(current->format) ==
+                     SDL_PIXELTYPE(target_format))) {
+                match = current;
+            }
+            continue;
+        }
+        if (current->refresh_rate != match->refresh_rate) {
+            /* Sorted highest refresh to lowest */
+            if (current->refresh_rate >= target_refresh_rate) {
+                match = current;
+                continue;
+            }
+        }
+    }
+
+    if (match) {
+        SDL_zerop(closest);
+        if (match->format) {
+            closest->format = match->format;
+        } else {
+            closest->format = mode->format;
+        }
+        if (match->screen_w && match->screen_h) {
+            closest->screen_w = match->screen_w;
+            closest->screen_h = match->screen_h;
+        } else {
+            closest->screen_w = mode->screen_w;
+            closest->screen_h = mode->screen_h;
+        }
+        if (match->pixel_w && match->pixel_h) {
+            closest->pixel_w = match->pixel_w;
+            closest->pixel_h = match->pixel_h;
+        } else {
+            closest->pixel_w = mode->pixel_w;
+            closest->pixel_h = mode->pixel_h;
+        }
+        if (match->refresh_rate > 0.0f) {
+            closest->refresh_rate = match->refresh_rate;
+        } else {
+            closest->refresh_rate = mode->refresh_rate;
+        }
+        closest->driverdata = match->driverdata;
+
+        /* Pick some reasonable defaults if the app and driver don't care */
+        if (!closest->format) {
+            closest->format = SDL_PIXELFORMAT_RGB888;
+        }
+        if (!closest->screen_w) {
+            closest->screen_w = 640;
+        }
+        if (!closest->screen_h) {
+            closest->screen_h = 480;
+        }
+        SDL_FinalizeDisplayMode(closest);
+        SDL_free(list);
+        return closest;
+    }
+    SDL_free(list);
+    return NULL;
+}
+
 DECLSPEC SDL2_DisplayMode * SDLCALL
 SDL_GetClosestDisplayMode(int displayIndex, const SDL2_DisplayMode *mode, SDL2_DisplayMode *closest)
 {
-#if 0
-    SDL_DisplayMode dp;
+    SDL_DisplayMode mode3;
     SDL_DisplayMode closest3;
-    SDL_DisplayMode *ret;
-    static SDL2_DisplayMode ret2;  /* FIXME alloc ?? */
+    SDL_DisplayMode *ret3;
+
+    if (mode == NULL || closest == NULL) {
+        SDL_InvalidParamError("mode/closest");
+        return NULL;
+    }
+
     DisplayMode_2to3(closest, &closest3);
-    ret = SDL3_GetClosestDisplayMode(Display_IndexToID(displayIndex), mode ? &dp : NULL, closest ? &closest3 : NULL);
-    DisplayMode_3to2(ret, &ret2);
-    return &ret2;
-#endif
-    // FIXME
-    SDL_Log("FIXME missing SDL_GetClosestDisplayMode");
-    return NULL;
+    DisplayMode_2to3(mode, &mode3);
+    ret3 = SDL_GetClosestDisplayModeForDisplay(Display_IndexToID(displayIndex), &mode3, &closest3);
+    if (ret3 == NULL) {
+        return NULL;
+    } else {
+        DisplayMode_3to2(ret3, closest);
+        return closest;
+    }
 }
 
 DECLSPEC int SDLCALL