From a9cca5e76ca73e6e7ca335f2a2ad247604b903ae Mon Sep 17 00:00:00 2001 From: grorp Date: Sun, 16 Jun 2024 17:49:42 +0200 Subject: [PATCH] SDL2: Support highdpi (#14703) and handle DPI changes at runtime --- builtin/settingtypes.txt | 3 -- irr/include/IEventReceiver.h | 3 ++ irr/src/CIrrDeviceSDL.cpp | 83 +++++++++++++++++++++------------- irr/src/CIrrDeviceSDL.h | 5 +- irr/src/CMakeLists.txt | 2 + misc/minetest.exe.manifest | 2 + src/client/clientlauncher.cpp | 2 + src/client/fontengine.cpp | 2 +- src/client/hud.cpp | 2 + src/client/inputhandler.cpp | 9 ++++ src/client/renderingengine.cpp | 16 +++---- src/defaultsettings.cpp | 2 +- 12 files changed, 84 insertions(+), 47 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index aca960590..bb7ba7f7f 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2187,9 +2187,6 @@ curl_file_download_timeout (cURL file download timeout) int 300000 5000 21474836 [**Miscellaneous] -# Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens. -screen_dpi (DPI) int 72 1 - # Adjust the detected display density, used for scaling UI elements. display_density_factor (Display Density Scaling Factor) float 1 0.5 5.0 diff --git a/irr/include/IEventReceiver.h b/irr/include/IEventReceiver.h index cf7dee3ab..a484bfb84 100644 --- a/irr/include/IEventReceiver.h +++ b/irr/include/IEventReceiver.h @@ -205,6 +205,9 @@ enum EAPPLICATION_EVENT_TYPE //! The application received a memory warning. EAET_MEMORY_WARNING, + //! The display density changed (only works on SDL). + EAET_DPI_CHANGED, + //! No real event, but to get number of event types. EAET_COUNT }; diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index f5859372e..f8fb66da3 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -285,6 +285,11 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) : SDL_SetHint(SDL_HINT_TV_REMOTE_AS_JOYSTICK, "0"); #endif +#if SDL_VERSION_ATLEAST(2, 24, 0) + // highdpi support on Windows + SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1"); +#endif + // Minetest has its own code to synthesize mouse events from touch events, // so we prevent SDL from doing it. SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); @@ -475,6 +480,7 @@ bool CIrrDeviceSDL::createWindow() bool CIrrDeviceSDL::createWindowWithContext() { u32 SDL_Flags = 0; + SDL_Flags |= SDL_WINDOW_ALLOW_HIGHDPI; SDL_Flags |= getFullscreenFlag(CreationParams.Fullscreen); if (Resizable) @@ -589,13 +595,16 @@ bool CIrrDeviceSDL::createWindowWithContext() return false; } - // Update Width and Height to match the actual window size. - // In fullscreen mode, the window size specified in SIrrlichtCreationParameters - // is ignored, so we cannot rely on it. - int w = 0, h = 0; - SDL_GetWindowSize(Window, &w, &h); - Width = w; - Height = h; + updateSizeAndScale(); + if (ScaleX != 1.0f || ScaleY != 1.0f) { + // The given window size is in pixels, not in screen coordinates. + // We can only do the conversion now since we didn't know the scale before. + SDL_SetWindowSize(Window, CreationParams.WindowSize.Width / ScaleX, + CreationParams.WindowSize.Height / ScaleY); + // Re-center, otherwise large, non-maximized windows go offscreen. + SDL_SetWindowPosition(Window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + updateSizeAndScale(); + } return true; #endif // !_IRR_EMSCRIPTEN_PLATFORM_ @@ -659,10 +668,10 @@ bool CIrrDeviceSDL::run() irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED; - MouseX = irrevent.MouseInput.X = SDL_event.motion.x; - MouseY = irrevent.MouseInput.Y = SDL_event.motion.y; - MouseXRel = SDL_event.motion.xrel; - MouseYRel = SDL_event.motion.yrel; + MouseX = irrevent.MouseInput.X = SDL_event.motion.x * ScaleX; + MouseY = irrevent.MouseInput.Y = SDL_event.motion.y * ScaleY; + MouseXRel = SDL_event.motion.xrel * ScaleX; + MouseYRel = SDL_event.motion.yrel * ScaleY; irrevent.MouseInput.ButtonStates = MouseButtonStates; irrevent.MouseInput.Shift = (keymod & KMOD_SHIFT) != 0; irrevent.MouseInput.Control = (keymod & KMOD_CTRL) != 0; @@ -694,8 +703,8 @@ bool CIrrDeviceSDL::run() SDL_Keymod keymod = SDL_GetModState(); irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; - irrevent.MouseInput.X = SDL_event.button.x; - irrevent.MouseInput.Y = SDL_event.button.y; + irrevent.MouseInput.X = SDL_event.button.x * ScaleX; + irrevent.MouseInput.Y = SDL_event.button.y * ScaleY; irrevent.MouseInput.Shift = (keymod & KMOD_SHIFT) != 0; irrevent.MouseInput.Control = (keymod & KMOD_CTRL) != 0; @@ -827,14 +836,25 @@ bool CIrrDeviceSDL::run() case SDL_WINDOWEVENT: switch (SDL_event.window.event) { case SDL_WINDOWEVENT_RESIZED: - if ((SDL_event.window.data1 != (int)Width) || (SDL_event.window.data2 != (int)Height)) { - Width = SDL_event.window.data1; - Height = SDL_event.window.data2; + case SDL_WINDOWEVENT_SIZE_CHANGED: +#if SDL_VERSION_ATLEAST(2, 0, 18) + case SDL_WINDOWEVENT_DISPLAY_CHANGED: +#endif + u32 old_w = Width, old_h = Height; + f32 old_scale_x = ScaleX, old_scale_y = ScaleY; + updateSizeAndScale(); + if (old_w != Width || old_h != Height) { if (VideoDriver) VideoDriver->OnResize(core::dimension2d(Width, Height)); } + if (old_scale_x != ScaleX || old_scale_y != ScaleY) { + irrevent.EventType = EET_APPLICATION_EVENT; + irrevent.ApplicationEvent.EventType = EAET_DPI_CHANGED; + postEventFromUser(irrevent); + } break; } + break; case SDL_USEREVENT: irrevent.EventType = irr::EET_USER_EVENT; @@ -1030,25 +1050,26 @@ bool CIrrDeviceSDL::activateJoysticks(core::array &joystickInfo) return false; } +void CIrrDeviceSDL::updateSizeAndScale() +{ + int window_w, window_h; + SDL_GetWindowSize(Window, &window_w, &window_h); + + int drawable_w, drawable_h; + SDL_GL_GetDrawableSize(Window, &drawable_w, &drawable_h); + + ScaleX = (float)drawable_w / (float)window_w; + ScaleY = (float)drawable_h / (float)window_h; + + Width = drawable_w; + Height = drawable_h; +} + //! Get the display density in dots per inch. float CIrrDeviceSDL::getDisplayDensity() const { - if (!Window) - return 0.0f; - - int window_w; - int window_h; - SDL_GetWindowSize(Window, &window_w, &window_h); - - int drawable_w; - int drawable_h; - SDL_GL_GetDrawableSize(Window, &drawable_w, &drawable_h); - // assume 96 dpi - float dpi_w = (float)drawable_w / (float)window_w * 96.0f; - float dpi_h = (float)drawable_h / (float)window_h * 96.0f; - - return std::max(dpi_w, dpi_h); + return std::max(ScaleX * 96.0f, ScaleY * 96.0f); } void CIrrDeviceSDL::SwapWindow() diff --git a/irr/src/CIrrDeviceSDL.h b/irr/src/CIrrDeviceSDL.h index c536a8149..8c7c7c3e1 100644 --- a/irr/src/CIrrDeviceSDL.h +++ b/irr/src/CIrrDeviceSDL.h @@ -158,7 +158,8 @@ class CIrrDeviceSDL : public CIrrDeviceStub //! Sets the new position of the cursor. void setPosition(s32 x, s32 y) override { - SDL_WarpMouseInWindow(Device->Window, x, y); + SDL_WarpMouseInWindow(Device->Window, + x / Device->ScaleX, y / Device->ScaleY); if (SDL_GetRelativeMouseMode()) { // There won't be an event for this warp (details on libsdl-org/SDL/issues/6034) @@ -300,6 +301,8 @@ class CIrrDeviceSDL : public CIrrDeviceStub u32 MouseButtonStates; u32 Width, Height; + f32 ScaleX = 1.0f, ScaleY = 1.0f; + void updateSizeAndScale(); bool Resizable; diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index daff63bc2..d5e9d47e7 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -1,3 +1,5 @@ +# When enabling SDL2 by default on macOS, don't forget to change +# "NSHighResolutionCapable" to true in "Info.plist". if(NOT APPLE) set(DEFAULT_SDL2 ON) endif() diff --git a/misc/minetest.exe.manifest b/misc/minetest.exe.manifest index ff5469250..1b8c3ba7b 100644 --- a/misc/minetest.exe.manifest +++ b/misc/minetest.exe.manifest @@ -10,7 +10,9 @@ + true + PerMonitorV2 UTF-8 SegmentHeap diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 6379fc141..f64d02844 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -69,6 +69,7 @@ static void dump_start_data(const GameStartData &data) ClientLauncher::~ClientLauncher() { delete input; + g_settings->deregisterChangedCallback("dpi_change_notifier", setting_changed_callback, this); g_settings->deregisterChangedCallback("gui_scaling", setting_changed_callback, this); delete g_fontengine; @@ -129,6 +130,7 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) guienv = m_rendering_engine->get_gui_env(); config_guienv(); + g_settings->registerChangedCallback("dpi_change_notifier", setting_changed_callback, this); g_settings->registerChangedCallback("gui_scaling", setting_changed_callback, this); g_fontengine = new FontEngine(guienv); diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index 0b1b0928e..e0174e011 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -59,7 +59,7 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) : "mono_font_path", "mono_font_path_bold", "mono_font_path_italic", "mono_font_path_bold_italic", "fallback_font_path", - "screen_dpi", "gui_scaling", + "dpi_change_notifier", "gui_scaling", }; for (auto name : settings) diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 637a2f500..c5e71b853 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -60,6 +60,7 @@ Hud::Hud(Client *client, LocalPlayer *player, this->inventory = inventory; readScalingSetting(); + g_settings->registerChangedCallback("dpi_change_notifier", setting_changed_callback, this); g_settings->registerChangedCallback("hud_scaling", setting_changed_callback, this); for (auto &hbar_color : hbar_colors) @@ -153,6 +154,7 @@ void Hud::readScalingSetting() Hud::~Hud() { + g_settings->deregisterChangedCallback("dpi_change_notifier", setting_changed_callback, this); g_settings->deregisterChangedCallback("hud_scaling", setting_changed_callback, this); if (m_selection_mesh) diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index a30c818a7..4233916c2 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -113,6 +113,15 @@ bool MyEventReceiver::OnEvent(const SEvent &event) return true; } + if (event.EventType == EET_APPLICATION_EVENT && + event.ApplicationEvent.EventType == EAET_DPI_CHANGED) { + // This is a fake setting so that we can use (de)registerChangedCallback + // not only to listen for gui/hud_scaling changes, but also for DPI changes. + g_settings->setU16("dpi_change_notifier", + g_settings->getU16("dpi_change_notifier") + 1); + return true; + } + // This is separate from other keyboard handling so that it also works in menus. if (event.EventType == EET_KEY_INPUT_EVENT) { const KeyPress keyCode(event.KeyInput); diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index e7ba333d2..18b9ff158 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -462,18 +462,14 @@ const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_ float RenderingEngine::getDisplayDensity() { + float user_factor = g_settings->getFloat("display_density_factor", 0.5f, 5.0f); #ifndef __ANDROID__ - static float cached_display_density = [&] { - float dpi = get_raw_device()->getDisplayDensity(); - // fall back to manually specified dpi - if (dpi == 0.0f) - dpi = g_settings->getFloat("screen_dpi"); - return dpi / 96.0f; - }(); - return std::max(cached_display_density * g_settings->getFloat("display_density_factor"), 0.5f); - + float dpi = get_raw_device()->getDisplayDensity(); + if (dpi == 0.0f) + dpi = 96.0f; + return std::max(dpi / 96.0f * user_factor, 0.5f); #else // __ANDROID__ - return porting::getDisplayDensity(); + return porting::getDisplayDensity() * user_factor; #endif // __ANDROID__ } diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index f16c56db4..1bb4dbe97 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -536,8 +536,8 @@ void set_default_settings() settings->setDefault("server_description", ""); settings->setDefault("enable_console", "false"); - settings->setDefault("screen_dpi", "72"); settings->setDefault("display_density_factor", "1"); + settings->setDefault("dpi_change_notifier", "0"); // Altered settings for macOS #if defined(__MACH__) && defined(__APPLE__)