Browse Source

Platform: HiDPI support for SDL2 and GLFW on Windows.

Co-authored-by: Guillaume Jacquemin <williamjcm@users.noreply.github.com>
pull/331/head
Vladimír Vondruš 7 years ago
parent
commit
2253987531
  1. 4
      doc/changelog.dox
  2. 71
      doc/platforms-windows.dox
  3. 31
      src/Magnum/Platform/GlfwApplication.cpp
  4. 8
      src/Magnum/Platform/GlfwApplication.h
  5. 43
      src/Magnum/Platform/Implementation/DpiScaling.cpp
  6. 4
      src/Magnum/Platform/Implementation/DpiScaling.h
  7. 28
      src/Magnum/Platform/Sdl2Application.cpp
  8. 14
      src/Magnum/Platform/Sdl2Application.h
  9. 8
      src/Magnum/Platform/Test/CMakeLists.txt
  10. 14
      src/Magnum/Platform/Test/WindowsHiDPI.manifest
  11. 1
      src/Magnum/Platform/Test/WindowsHiDPI.rc

4
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))

71
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}
<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

31
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;

8
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:

43
src/Magnum/Platform/Implementation/DpiScaling.cpp

@ -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() {
@ -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
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

28
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};

14
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

8
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)

14
src/Magnum/Platform/Test/WindowsHiDPI.manifest

@ -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
src/Magnum/Platform/Test/WindowsHiDPI.rc

@ -0,0 +1 @@
1 RT_MANIFEST "WindowsHiDPI.manifest"
Loading…
Cancel
Save