Skip to content

Commit

Permalink
Platform: HiDPI support for SDL2 and GLFW on Windows.
Browse files Browse the repository at this point in the history
Co-authored-by: Guillaume Jacquemin <williamjcm@users.noreply.github.com>
  • Loading branch information
mosra and williamjcm committed Mar 16, 2019
1 parent d98efb2 commit 2253987
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 17 deletions.
4 changes: 4 additions & 0 deletions doc/changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
71 changes: 68 additions & 3 deletions doc/platforms-windows.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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}
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
true/pm
</dpiAware> <!-- legacy -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
permonitorv2,permonitor
</dpiAwareness> <!-- falls back to pm if pmv2 is not available -->
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
@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

Expand Down
31 changes: 28 additions & 3 deletions src/Magnum/Platform/GlfwApplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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";
Expand All @@ -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;
Expand Down
8 changes: 5 additions & 3 deletions src/Magnum/Platform/GlfwApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
43 changes: 43 additions & 0 deletions src/Magnum/Platform/Implementation/DpiScaling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@
#include <emscripten/emscripten.h>
#endif

#if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT)
#define WIN32_LEAN_AND_MEAN 1
#define VC_EXTRALEAN
#include <windows.h>
#ifdef __has_include
#if __has_include(<shellscalingapi.h>)
#include <shellscalingapi.h>
#endif
#endif
#include <Corrade/Utility/Assert.h>
#endif

namespace Magnum { namespace Platform { namespace Implementation {

Utility::Arguments windowScalingArguments() {
Expand Down Expand Up @@ -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<HRESULT(*)(HANDLE, PROCESS_DPI_AWARENESS*)>(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<BOOL(*)()>(GetProcAddress(user32, "IsProcessDPIAware"));
CORRADE_INTERNAL_ASSERT(isProcessDPIAware);
return isProcessDPIAware();
}
#endif

}}}

#endif
4 changes: 4 additions & 0 deletions src/Magnum/Platform/Implementation/DpiScaling.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Float emscriptenDpiScaling();
bool isAppleBundleHiDpiEnabled();
#endif

#if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT)
bool isWindowsAppDpiAware();
#endif

}}}

#endif
28 changes: 25 additions & 3 deletions src/Magnum/Platform/Sdl2Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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};
Expand Down
14 changes: 9 additions & 5 deletions src/Magnum/Platform/Sdl2Application.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/Magnum/Platform/Test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions src/Magnum/Platform/Test/WindowsHiDPI.manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
true/pm
</dpiAware> <!-- legacy -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
permonitorv2,permonitor
</dpiAwareness> <!-- falls back to pm if pmv2 is not available -->
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
<!-- kate: hl xml; -->
1 change: 1 addition & 0 deletions src/Magnum/Platform/Test/WindowsHiDPI.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1 RT_MANIFEST "WindowsHiDPI.manifest"

0 comments on commit 2253987

Please sign in to comment.