diff --git a/doc/changelog.dox b/doc/changelog.dox index c7a9308339..0103d1d1f2 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -79,6 +79,10 @@ See also: @subsubsection changelog-latest-new-platform Platform libraries +- @ref Platform::Sdl2Application and @ref Platform::GlfwApplication are now + DPI-aware on Windows as well. See their documentation, + @ref platforms-windows-hidpi and [mosra/magnum#243](https://github.com/mosra/magnum/issues/243) + for more information. - Added @ref Platform::Sdl2Application::glContext() to access the underlying `SDL_GLContext` (see [mosra/magnum#325](https://github.com/mosra/magnum/pull/325)) diff --git a/doc/platforms-windows.dox b/doc/platforms-windows.dox index a295b79d1c..7afcaa4496 100644 --- a/doc/platforms-windows.dox +++ b/doc/platforms-windows.dox @@ -34,9 +34,74 @@ namespace Magnum { @section platforms-windows-hidpi HiDPI support -Windows supports two approaches to advertising HiDPI support --- either via the -manifest file or through the [SetProcessDpiAwareness()](https://docs.microsoft.com/en-us/windows/desktop/api/shellscalingapi/nf-shellscalingapi-setprocessdpiawareness) -API. See the API documentation for more information. +Windows supports two approaches to advertising HiDPI support. The recommended +way is via a so-called manifest file added to an executable, but it's also +possible to it programatically through the `SetProcessDpiAwareness()` family of +APIs. Note there's three different levels of DPI awareness setup for Windows +Vista and newer, Windows 8.1 and newer and Windows 10, and for best support may +want to support all three. + +When using MSVC, the manifest file can be added directly via CMake. Advertising application-wide per-monitor support can look like in the following snippet, +together with fallbacks for older systems: + +@code{.xml} + + + + + true/pm + + + permonitorv2,permonitor + + + + +@endcode + +Then, the manifest file can be supplied directly in the sources list for +@cmake add_executable() @ce, via a variable, or you can add it conditionally +later using @cmake target_sources() @ce. For example: + +@code{.cmake} +add_executable(my-application MyApplication.cpp) +if(CORRADE_TARGET_WINDOWS) + target_sources(my-application PRIVATE WindowsHiDPI.manifest) +endif() +@endcode + +Some toolkits (such as GLFW in @ref Platform-GlfwApplication-dpi "Platform::GlfwApplication") +are advertising HiDPI support implicitly programatically. In that case the +manifest file doesn't need to be supplied, but there may be some disadvantages +compared to supplying the manifest. See the +[MSDN documentation about DPI awareness](https://msdn.microsoft.com/en-us/library/windows/desktop/mt846517(v=vs.85).aspx) +for more information. + +@m_class{m-block m-info} + +@par Supplying manifests with MinGW + With MinGw the operation is slightly more involved, as you need to pass it + through a `*.rc` file. A downside is that MinGW is not able to merge + information from multiple manifests like the MSVC toolchain can. +@par + @code{.txt} + 1 RT_MANIFEST "WindowsHiDPI.manifest" + @endcode +@par + Then you add the `*.rc` file using @cb{.cmake} target_sources @ce like + above. Here's a CMake snippet that will work for both: +@par + @code{.cmake} + add_executable(my-application MyApplication.cpp) + if(CORRADE_TARGET_WINDOWS) + if(MSVC) + target_sources(my-application PRIVATE WindowsHiDPI.manifest) + elif(MINGW) + target_sources(my-application PRIVATE WindowsHiDPI.rc) + endif() + endif() + @endcode @section platforms-windows-rt Windows RT diff --git a/src/Magnum/Platform/GlfwApplication.cpp b/src/Magnum/Platform/GlfwApplication.cpp index ec75ea040f..05d3824d23 100644 --- a/src/Magnum/Platform/GlfwApplication.cpp +++ b/src/Magnum/Platform/GlfwApplication.cpp @@ -149,6 +149,7 @@ Vector2 GlfwApplication::dpiScaling(const Configuration& configuration) const { /* Otherwise there's a choice between virtual and physical DPI scaling */ #else /* Try to get virtual DPI scaling first, if supported and requested */ + /** @todo Revisit this for GLFW 3.3 -- https://github.com/glfw/glfw/issues/677 */ if(dpiScalingPolicy == Implementation::GlfwDpiScalingPolicy::Virtual) { /* Use Xft.dpi on X11 */ #ifdef _MAGNUM_PLATFORM_USE_X11 @@ -158,6 +159,28 @@ Vector2 GlfwApplication::dpiScaling(const Configuration& configuration) const { return dpiScaling; } + /* Check for DPI awareness on non-RT Windows and then ask for DPI. GLFW + is advertising the application to be DPI-aware on its own even + without supplying an explicit manifest -- + https://github.com/glfw/glfw/blob/089ea9af227fdffdf872348923e1c12682e63029/src/win32_init.c#L564-L569 + If, for some reason, the app is still not DPI-aware, tell that to + the user explicitly and don't even attempt to query the value if the + app is not DPI aware. If it's desired to get the DPI value + unconditionally, the user should use physical DPI scaling instead. */ + #elif defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) + if(!Implementation::isWindowsAppDpiAware()) { + Warning{verbose} << "Platform::GlfwApplication: your application is not set as DPI-aware, DPI scaling won't be used"; + return Vector2{1.0f}; + } + GLFWmonitor* const monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode* const mode = glfwGetVideoMode(monitor); + Vector2i monitorSize; + glfwGetMonitorPhysicalSize(monitor, &monitorSize.x(), &monitorSize.y()); + auto dpi = Vector2{Vector2i{mode->width, mode->height}*25.4f/Vector2{monitorSize}}; + const Vector2 dpiScaling{dpi/96.0f}; + Debug{verbose} << "Platform::GlfwApplication: virtual DPI scaling" << dpiScaling; + return dpiScaling; + /* Otherwise ¯\_(ツ)_/¯ */ #else Debug{verbose} << "Platform::GlfwApplication: sorry, virtual DPI scaling not implemented on this platform yet, falling back to physical DPI scaling"; @@ -168,9 +191,11 @@ Vector2 GlfwApplication::dpiScaling(const Configuration& configuration) const { scaling is requested */ CORRADE_INTERNAL_ASSERT(dpiScalingPolicy == Implementation::GlfwDpiScalingPolicy::Virtual || dpiScalingPolicy == Implementation::GlfwDpiScalingPolicy::Physical); - /* Take display DPI. Enable only on Linux for now, I need to test this - properly on Windows first. */ - #ifdef CORRADE_TARGET_UNIX + /* Take display DPI elsewhere. Enable only on Linux (where it gets the + usually very-off value from X11) and on non-RT Windows (where it takes + the UI scale value like with virtual DPI scaling, but without checking + for DPI awareness first). */ + #if defined(CORRADE_TARGET_UNIX) || (defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT)) GLFWmonitor* const monitor = glfwGetPrimaryMonitor(); const GLFWvidmode* const mode = glfwGetVideoMode(monitor); Vector2i monitorSize; diff --git a/src/Magnum/Platform/GlfwApplication.h b/src/Magnum/Platform/GlfwApplication.h index cc46930f0a..c2fb60f496 100644 --- a/src/Magnum/Platform/GlfwApplication.h +++ b/src/Magnum/Platform/GlfwApplication.h @@ -130,9 +130,11 @@ If no other application header is included, this class is also aliased to @section Platform-GlfwApplication-dpi DPI awareness DPI awareness behavior is consistent with @ref Sdl2Application except that iOS -or Emscripten specifics don't apply here. See -@ref Platform-Sdl2Application-dpi "its DPI awareness documentation" for more -information. +or Emscripten specifics don't apply here. In addition, on Windows, GLFW is +implicitly advertising DPI awareness, so the manifest file described in +@ref platforms-windows-hidpi doesn't necessarily need to be supplied. See +@ref Platform-Sdl2Application-dpi "Sdl2Application DPI awareness documentation" +for more information. */ class GlfwApplication { public: diff --git a/src/Magnum/Platform/Implementation/DpiScaling.cpp b/src/Magnum/Platform/Implementation/DpiScaling.cpp index 91f8d56612..3f971b70d7 100644 --- a/src/Magnum/Platform/Implementation/DpiScaling.cpp +++ b/src/Magnum/Platform/Implementation/DpiScaling.cpp @@ -45,6 +45,18 @@ #include #endif +#if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) +#define WIN32_LEAN_AND_MEAN 1 +#define VC_EXTRALEAN +#include +#ifdef __has_include +#if __has_include() +#include +#endif +#endif +#include +#endif + namespace Magnum { namespace Platform { namespace Implementation { Utility::Arguments windowScalingArguments() { @@ -129,6 +141,37 @@ Float emscriptenDpiScaling() { } #endif +#if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) +bool isWindowsAppDpiAware() { + /** @todo use GetWindowDpiAwarenessContext() (since Windows 10)? I think + it's not needed for a simple boolean return value. */ + + #ifdef DPI_ENUMS_DECLARED + /* The GetProcessDpiAwareness() function is available only since Windows + 8.1, so load it manually to avoid a link-time error when building for + Windows 7. Also, the shellscalingapi.h include might not be available + on older MinGW, so it's guarded by __has_include(). Here, if the + DPI_ENUMS_DECLARED define is present, the header exists and has what we + need. */ + HMODULE const shcore = GetModuleHandleA("Shcore.dll"); + if(shcore) { + auto* const getProcessDpiAwareness = reinterpret_cast(GetProcAddress(shcore, "GetProcessDpiAwareness")); + PROCESS_DPI_AWARENESS result{}; + return getProcessDpiAwareness && getProcessDpiAwareness(nullptr, &result) == S_OK && result != PROCESS_DPI_UNAWARE; + } + #endif + + /* IsProcessDPIAware() is available since Windows Vista. At this point we + can require it (XP support? haha no), so assert that everything works + correctly. */ + HMODULE const user32 = GetModuleHandleA("User32.dll"); + CORRADE_INTERNAL_ASSERT(user32); + auto const isProcessDPIAware = reinterpret_cast(GetProcAddress(user32, "IsProcessDPIAware")); + CORRADE_INTERNAL_ASSERT(isProcessDPIAware); + return isProcessDPIAware(); +} +#endif + }}} #endif diff --git a/src/Magnum/Platform/Implementation/DpiScaling.h b/src/Magnum/Platform/Implementation/DpiScaling.h index 57ef511b7e..8bdfdd1340 100644 --- a/src/Magnum/Platform/Implementation/DpiScaling.h +++ b/src/Magnum/Platform/Implementation/DpiScaling.h @@ -48,6 +48,10 @@ Float emscriptenDpiScaling(); bool isAppleBundleHiDpiEnabled(); #endif +#if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) +bool isWindowsAppDpiAware(); +#endif + }}} #endif diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index 97f817a51d..ff62f70ef1 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -199,6 +199,26 @@ Vector2 Sdl2Application::dpiScaling(const Configuration& configuration) const { return dpiScaling; } + /* Check for DPI awareness on (non-RT) Windows and then ask for DPI. + SDL_GetDisplayDPI() is querying GetDpiForMonitor() -- + https://github.com/spurious/SDL-mirror/blob/17af4584cb28cdb3c2feba17e7d989a806007d9f/src/video/windows/SDL_windowsmodes.c#L266 + and GetDpiForMonitor() returns 96 if the application is DPI unaware. + So we instead check for DPI awareness first (and tell the user if + not), and only if the app is, then we use SDL_GetDisplayDPI(). If + it's for some reason desired to get the DPI value unconditionally, + the user should use physical DPI scaling instead. */ + #elif defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) + if(!Implementation::isWindowsAppDpiAware()) { + Warning{verbose} << "Platform::Sdl2Application: your application is not set as DPI-aware, DPI scaling won't be used"; + return Vector2{1.0f}; + } + Vector2 dpi; + if(SDL_GetDisplayDPI(0, nullptr, &dpi.x(), &dpi.y()) == 0) { + const Vector2 dpiScaling{dpi/96.0f}; + Debug{verbose} << "Platform::Sdl2Application: virtual DPI scaling" << dpiScaling; + return dpiScaling; + } + /* Otherwise ¯\_(ツ)_/¯ */ #else Debug{verbose} << "Platform::Sdl2Application: sorry, virtual DPI scaling not implemented on this platform yet, falling back to physical DPI scaling"; @@ -220,9 +240,11 @@ Vector2 Sdl2Application::dpiScaling(const Configuration& configuration) const { Debug{verbose} << "Platform::Sdl2Application: physical DPI scaling" << dpiScaling.x(); return dpiScaling; - /* Take display DPI elsewhere. Enable only on Linux for now, I need to - test this properly on Windows first. Also only since SDL 2.0.4. */ - #elif defined(CORRADE_TARGET_UNIX) && SDL_VERSION_ATLEAST(2, 0, 4) + /* Take display DPI elsewhere. Enable only on Linux (where it gets the + usually very-off value from X11) and on non-RT Windows (where it takes + the UI scale value like with virtual DPI scaling, but without checking + for DPI awareness first). Also only since SDL 2.0.4. */ + #elif (defined(CORRADE_TARGET_UNIX) || (defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT))) && SDL_VERSION_ATLEAST(2, 0, 4) Vector2 dpi; if(SDL_GetDisplayDPI(0, nullptr, &dpi.x(), &dpi.y()) == 0) { const Vector2 dpiScaling{dpi/96.0f}; diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index 28993d3def..d2e7ef39ff 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -339,8 +339,11 @@ variable). system. For example if a 800x600 window is requested and DPI scaling is set to 200%, the resulting window will have 1600x1200 pixels. The backing framebuffer will have the same size. This is supported on Linux and - Windows. Equivalent to passing @ref Configuration::DpiScalingPolicy::Virtual - to @ref Configuration::setSize() or `virtual` on command line. + Windows; on Windows the application is first checked for DPI awareness + as described in @ref platforms-windows-hidpi and if the application is not + DPI-aware, 1:1 scaling is used. Equivalent to passing + @ref Configuration::DpiScalingPolicy::Virtual to + @ref Configuration::setSize() or `virtual` on command line. - Physical DPI scaling. Takes the requested window size as a physical size that a window would have on platform's default DPI and scales it to have the same physical size on given display physical DPI. So, for example on a @@ -349,9 +352,10 @@ variable). DPI display. On platforms that don't have a concept of a window (such as mobile platforms or @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten"), it causes the framebuffer to match display pixels 1:1 without any scaling. - This is supported on Linux, Windows, all mobile platforms except iOS and - Emscripten. Equivalent to passing - @ref Configuration::DpiScalingPolicy::Physical to + This is supported on Linux and all mobile platforms (except iOS) and + Emscripten. On Windows this is equivalent to virtual DPI scaling but + without doing an explicit check for DPI awareness first. Equivalent to + passing @ref Configuration::DpiScalingPolicy::Physical to @ref Configuration::setSize() or `physical` via command line / environment. Besides the above, it's possible to supply a custom DPI scaling value to diff --git a/src/Magnum/Platform/Test/CMakeLists.txt b/src/Magnum/Platform/Test/CMakeLists.txt index 2bd6eb0c57..c17eb2314c 100644 --- a/src/Magnum/Platform/Test/CMakeLists.txt +++ b/src/Magnum/Platform/Test/CMakeLists.txt @@ -34,6 +34,7 @@ endif() if(WITH_GLFWAPPLICATION) add_executable(PlatformGlfwApplicationTest GlfwApplicationTest.cpp) + # HiDPi.manifest not needed, as GLFW sets that on its own target_link_libraries(PlatformGlfwApplicationTest PRIVATE MagnumGlfwApplication) set_target_properties(PlatformGlfwApplicationTest PROPERTIES FOLDER "Magnum/Platform/Test") endif() @@ -52,6 +53,13 @@ endif() if(WITH_SDL2APPLICATION) add_executable(PlatformSdl2ApplicationTest Sdl2ApplicationTest.cpp) + if(CORRADE_TARGET_WINDOWS AND NOT CORRADE_TARGET_WINDOWS_RT) + if(MSVC) + target_sources(PlatformSdl2ApplicationTest PRIVATE WindowsHiDPI.manifest) + elseif(MINGW) + target_sources(PlatformSdl2ApplicationTest PRIVATE WindowsHiDPI.rc) + endif() + endif() target_link_libraries(PlatformSdl2ApplicationTest PRIVATE MagnumSdl2Application) set_target_properties(PlatformSdl2ApplicationTest PROPERTIES FOLDER "Magnum/Platform/Test") if(CORRADE_TARGET_EMSCRIPTEN) diff --git a/src/Magnum/Platform/Test/WindowsHiDPI.manifest b/src/Magnum/Platform/Test/WindowsHiDPI.manifest new file mode 100644 index 0000000000..54a0888847 --- /dev/null +++ b/src/Magnum/Platform/Test/WindowsHiDPI.manifest @@ -0,0 +1,14 @@ + + + + + true/pm + + + permonitorv2,permonitor + + + + + diff --git a/src/Magnum/Platform/Test/WindowsHiDPI.rc b/src/Magnum/Platform/Test/WindowsHiDPI.rc new file mode 100644 index 0000000000..cf21a63ec6 --- /dev/null +++ b/src/Magnum/Platform/Test/WindowsHiDPI.rc @@ -0,0 +1 @@ +1 RT_MANIFEST "WindowsHiDPI.manifest"