From fc13f6870bf8be2516efffe78ecfdc81a403c5b2 Mon Sep 17 00:00:00 2001
From: Mihai Parparita <mihai@persistent.info>
Date: Sun, 27 Oct 2024 11:02:47 -0700
Subject: [PATCH] Enable HiDPI mode for SDL and ensure that we use
 nearest-neighbor scaling

Avoids blurriness on macOS hosts with Retina displays.

Smooth scaling can be toggled back on via control-S
---
 core/hostevents.h             |  6 ++++++
 core/hostevents_sdl.cpp       | 14 ++++++++++++--
 devices/video/display_sdl.cpp | 28 +++++++++++++++++++++++-----
 3 files changed, 41 insertions(+), 7 deletions(-)

diff --git a/core/hostevents.h b/core/hostevents.h
index 1cf1157..03e6134 100644
--- a/core/hostevents.h
+++ b/core/hostevents.h
@@ -35,6 +35,12 @@ public:
     uint32_t    window_id;
 };
 
+enum : uint16_t {
+    // Standard SDL window event types are uint8_t's, we add our own custom
+    // events after that.
+    WINDOW_SCALE_QUALITY_TOGGLE = 1 << 8,
+};
+
 enum : uint32_t {
     MOUSE_EVENT_MOTION = 1 << 0,
     MOUSE_EVENT_BUTTON = 1 << 1,
diff --git a/core/hostevents_sdl.cpp b/core/hostevents_sdl.cpp
index 1c51221..0fe8f62 100644
--- a/core/hostevents_sdl.cpp
+++ b/core/hostevents_sdl.cpp
@@ -54,14 +54,24 @@ void EventManager::poll_events()
 
         case SDL_KEYDOWN:
         case SDL_KEYUP: {
-                // Internal shortcuts to trigger mouse grab, intentionally not
-                // sent to the host.
+                // Internal shortcuts, intentionally not sent to the host.
+                // Control-G: mouse grab
                 if (event.key.keysym.sym == SDLK_g && SDL_GetModState() & KMOD_LCTRL) {
                     if (event.type == SDL_KEYUP) {
                         toggle_mouse_grab(event.key);
                     }
                     return;
                 }
+                // Control-S: scale quality
+                if (event.key.keysym.sym == SDLK_s && SDL_GetModState() & KMOD_LCTRL) {
+                    if (event.type == SDL_KEYUP) {
+                        WindowEvent we;
+                        we.sub_type  = WINDOW_SCALE_QUALITY_TOGGLE;
+                        we.window_id = event.window.windowID;
+                        this->_window_signal.emit(we);
+                    }
+                    return;
+                }
                 int key_code = get_sdl_event_key_code(event.key);
                 if (key_code != -1) {
                     KeyboardEvent ke;
diff --git a/devices/video/display_sdl.cpp b/devices/video/display_sdl.cpp
index 65dccbf..0245b6c 100644
--- a/devices/video/display_sdl.cpp
+++ b/devices/video/display_sdl.cpp
@@ -29,12 +29,15 @@ public:
     uint32_t        disp_wnd_id = 0;
     SDL_Window*     display_wnd = 0;
     SDL_Renderer*   renderer = 0;
+    double          renderer_scale_x; // scaling factor from guest OS to host OS
+    double          renderer_scale_y;
     SDL_Texture*    disp_texture = 0;
     SDL_Texture*    cursor_texture = 0;
     SDL_Rect        cursor_rect; // destination rectangle for cursor drawing
 };
 
 Display::Display(): impl(std::make_unique<Impl>()) {
+    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
 }
 
 Display::~Display() {
@@ -61,7 +64,7 @@ bool Display::configure(int width, int height) {
             SDL_WINDOWPOS_UNDEFINED,
             SDL_WINDOWPOS_UNDEFINED,
             width, height,
-            SDL_WINDOW_OPENGL
+            SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI
         );
 
         impl->disp_wnd_id = SDL_GetWindowID(impl->display_wnd);
@@ -72,6 +75,11 @@ bool Display::configure(int width, int height) {
         if (impl->renderer == NULL)
             ABORT_F("Display: SDL_CreateRenderer failed with %s", SDL_GetError());
 
+        int drawable_width, drawable_height;
+        SDL_GetRendererOutputSize(impl->renderer, &drawable_width, &drawable_height);
+        impl->renderer_scale_x = static_cast<double>(drawable_width) / width;
+        impl->renderer_scale_y = static_cast<float>(drawable_height) / height;
+
         is_initialization = true;
     } else { // resize display window
         SDL_SetWindowSize(impl->display_wnd, width, height);
@@ -100,6 +108,16 @@ void Display::handle_events(const WindowEvent& wnd_event) {
     if (wnd_event.sub_type == SDL_WINDOWEVENT_EXPOSED &&
         wnd_event.window_id == impl->disp_wnd_id)
         SDL_RenderPresent(impl->renderer);
+    if (wnd_event.sub_type == WINDOW_SCALE_QUALITY_TOGGLE &&
+        wnd_event.window_id == impl->disp_wnd_id) {
+        auto current_quality = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY);
+        auto new_quality = current_quality == NULL || strcmp(current_quality, "nearest") == 0 ? "best" : "nearest";
+        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, new_quality);
+        // We need the window/texture to be recreated to pick up the hint change.
+        int width, height;
+        SDL_GetWindowSize(impl->display_wnd, &width, &height);
+        this->configure(width, height);
+    }
 }
 
 void Display::blank() {
@@ -133,8 +151,8 @@ void Display::update(std::function<void(uint8_t *dst_buf, int dst_pitch)> conver
 
     // draw HW cursor if enabled
     if (draw_hw_cursor) {
-        impl->cursor_rect.x = cursor_x;
-        impl->cursor_rect.y = cursor_y;
+        impl->cursor_rect.x = cursor_x * impl->renderer_scale_x;
+        impl->cursor_rect.y = cursor_y * impl->renderer_scale_y;
         SDL_RenderCopy(impl->renderer, impl->cursor_texture, NULL, &impl->cursor_rect);
     }
 
@@ -170,6 +188,6 @@ void Display::setup_hw_cursor(std::function<void(uint8_t *dst_buf, int dst_pitch
 
     impl->cursor_rect.x = 0;
     impl->cursor_rect.y = 0;
-    impl->cursor_rect.w = cursor_width;
-    impl->cursor_rect.h = cursor_height;
+    impl->cursor_rect.w = cursor_width * impl->renderer_scale_x;
+    impl->cursor_rect.h = cursor_height * impl->renderer_scale_y;
 }