Skip to content

Commit

Permalink
Platform: initial HiDPI support in SDL2 app on Linux and Emscripten.
Browse files Browse the repository at this point in the history
This is quite complex, actually. The end goal is: when I request an
800x600 window, it should create a window of the same physical size as
an 800x600 window would have on a system default DPI. After that, the
actual window size (for events), framebuffer size and DPI scaling value
(to correctly scale the contents relative to window size) are
platform-dependent.

On macOS and iOS, the DPI scaling is done simply by having the
framebuffer twice the size while the window size (for events) remains
the same. Easy to support.

On Linux, a non-DPI-aware app is simply having a really tiny window. The
worst behavior of all systems. Next to that, SDL_GetDisplayDPI() returns
physical DPI, which is quite useless as the value is usually coming from
Xorg display autodetection and is usually just 96, unless one goes extra
lengths and supplies a correct value via an xorg.conf. The DE is using a
different, user-configurable value for scaling the visuals and this one
is available through a Xft.dpi property. To get it, we dlopen() self and
dlsym() X11 symbols to get this property. If this fails, it might mean
the app doesn't run on X11 (maybe Wayland, maybe something's just messed
up, who knows) and then we fall back to SDL_GetDisplayDPI(). Which is
usually very wrong, so this is also why I'm implementing two ways to
override this -- either via the app Configuration or via a command-line
/ environment variable.

On Emscripten / HTML5, all that's needed is querying device pixel ratio
and then requesting canvas size scaled by that. The event coordinates
are relative to this size, so there's not much more to handle. Physical
canvas size on the page is controlled via CSS, so no issues with stuff
being too big or too small apply -- in the worst case, things may
be blurry.

On Windows, the DPI scaling is something in-between -- if the app
presents itself as DPI-aware, window size is treated as real pixels (so
one gets really what is asked for, i.e. an 800x600 window on a system
with 240 DPI is maybe four centimeters wide). If not, the window is
upscaled (and blurried) by the compositor. In order to have correct
behavior, I first need to query if the app is DPI-aware and then either
scale the requested size or not (to avoid extra huge windows when the
app is not marked as DPI aware). That will be done in a later commit.
  • Loading branch information
mosra committed Aug 21, 2018
1 parent 51ec199 commit ae31c3c
Show file tree
Hide file tree
Showing 7 changed files with 564 additions and 21 deletions.
2 changes: 2 additions & 0 deletions doc/changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ See also:

@subsubsection changelog-latest-new-platform Platform libraries

- Initial HiDPI support for Linux and Emscripten in
@ref Platform::Sdl2Application
- Implemented @ref Platform::GlfwApplication::MouseMoveEvent::buttons() for
feature parity with @ref Platform::Sdl2Application
- Added @ref Platform::Sdl2Application::GLConfiguration::setColorBufferSize() "GLConfiguration::setColorBufferSize()",
Expand Down
5 changes: 5 additions & 0 deletions modules/FindMagnum.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,11 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
find_package(SDL2)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES SDL2::SDL2)
if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE)
# Needed for opt-in DPI queries
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES ${CMAKE_DL_LIBS})
endif()

# With GLVND (since CMake 3.11) we need to explicitly link to
# GLX/EGL because libOpenGL doesn't provide it. For EGL we have
Expand Down
6 changes: 5 additions & 1 deletion src/Magnum/Platform/AndroidApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,11 @@ class AndroidApplication {

/** @{ @name Screen handling */

/** @copydoc Sdl2Application::windowSize() */
/**
* @brief Window size
*
* Window size to which all input event coordinates can be related.
*/
Vector2i windowSize();

/**
Expand Down
11 changes: 11 additions & 0 deletions src/Magnum/Platform/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,17 @@ if(WITH_SDL2APPLICATION)
${MagnumSomeContext_LIBRARY})
endif()

# If there is X11, ask it for DPI
if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE)
find_package(X11)
if(X11_FOUND)
# Not linking to X11, we dlopen() instead
target_include_directories(MagnumSdl2Application PRIVATE ${X11_X11_INCLUDE_PATH})
target_link_libraries(MagnumSdl2Application PUBLIC ${CMAKE_DL_LIBS})
target_compile_definitions(MagnumSdl2Application PRIVATE "_MAGNUM_PLATFORM_USE_X11")
endif()
endif()

install(FILES ${MagnumSdl2Application_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Platform)
install(TARGETS MagnumSdl2Application
RUNTIME DESTINATION ${MAGNUM_BINARY_INSTALL_DIR}
Expand Down
131 changes: 131 additions & 0 deletions src/Magnum/Platform/Implementation/dpiScaling.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#ifndef Magnum_Platform_Implementation_dpiScaling_hpp
#define Magnum_Platform_Implementation_dpiScaling_hpp
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

#include <Corrade/Utility/Arguments.h>

#include "Magnum/Magnum.h"

#ifdef _MAGNUM_PLATFORM_USE_X11
#include <dlfcn.h>
#include <X11/Xresource.h>
#include <Corrade/Containers/ScopedExit.h>
#undef None
#endif

#ifdef CORRADE_TARGET_EMSCRIPTEN
#include <emscripten/html5.h>
#endif

namespace Magnum { namespace Platform { namespace Implementation { namespace {

inline Utility::Arguments windowScalingArguments() {
Utility::Arguments args{"magnum"};
args.addOption("dpi-scaling", "virtual")
.setFromEnvironment("dpi-scaling", "default")
#ifdef CORRADE_TARGET_APPLE
.setHelp("dpi-scaling", "\n window DPI scaling", "default|framebuffer|<d>|\"<h> <v>\"")
#elif !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID)
.setHelp("dpi-scaling", "\n window DPI scaling", "default|virtual|physical|<d>|\"<h> <v>\"")
#else
.setHelp("dpi-scaling", "\n window DPI scaling", "default|physical|<d>|\"<h> <v>\"")
#endif
;
return args;
}

#ifdef _MAGNUM_PLATFORM_USE_X11
/* Returns DPI scaling for current X11 instance. Because X11 (as opposed to
Wayland) doesn't have per-monitor scaling, it's fetched from the default
display. */
inline Float x11DpiScaling() {
/* If the end app links to X11, these symbols will be available in a global
scope and we can use that to query the DPI. If not, then those symbols
won't be and that's okay -- it may be using Wayland or something else. */
void* xlib = dlopen(nullptr, RTLD_NOW|RTLD_GLOBAL);
Containers::ScopedExit closeXlib{xlib, dlclose};
#ifdef __GNUC__ /* http://www.mr-edd.co.uk/blog/supressing_gcc_warnings */
__extension__
#endif
auto xOpenDisplay = reinterpret_cast<Display*(*)(char*)>(dlsym(xlib, "XOpenDisplay"));
#ifdef __GNUC__ /* http://www.mr-edd.co.uk/blog/supressing_gcc_warnings */
__extension__
#endif
auto xCloseDisplay = reinterpret_cast<int(*)(Display*)>(dlsym(xlib, "XCloseDisplay"));
#ifdef __GNUC__ /* http://www.mr-edd.co.uk/blog/supressing_gcc_warnings */
__extension__
#endif
auto xResourceManagerString = reinterpret_cast<char*(*)(Display*)>(dlsym(xlib, "XResourceManagerString"));
#ifdef __GNUC__ /* http://www.mr-edd.co.uk/blog/supressing_gcc_warnings */
__extension__
#endif
auto xrmGetStringDatabase = reinterpret_cast<XrmDatabase(*)(const char*)>(dlsym(xlib, "XrmGetStringDatabase"));
#ifdef __GNUC__ /* http://www.mr-edd.co.uk/blog/supressing_gcc_warnings */
__extension__
#endif
auto xrmGetResource = reinterpret_cast<int(*)(XrmDatabase, const char*, const char*, char**, XrmValue*)>(dlsym(xlib, "XrmGetResource"));
#ifdef __GNUC__ /* http://www.mr-edd.co.uk/blog/supressing_gcc_warnings */
__extension__
#endif
auto xrmDestroyDatabase = reinterpret_cast<void(*)(XrmDatabase)>(dlsym(xlib, "XrmDestroyDatabase"));
if(!xOpenDisplay || !xCloseDisplay || !xResourceManagerString || !xrmGetStringDatabase || !xrmGetResource || !xrmDestroyDatabase) {
Warning{} << "Platform: can't load X11 symbols for getting virtual DPI scaling, falling back to physical DPI";
return {};
}

Display* display = xOpenDisplay(nullptr);
Containers::ScopedExit closeDisplay{display, xCloseDisplay};

const char* rms = xResourceManagerString(display);
CORRADE_INTERNAL_ASSERT(rms);
XrmDatabase db = xrmGetStringDatabase(rms);
CORRADE_INTERNAL_ASSERT(db);
Containers::ScopedExit closeDb{db, xrmDestroyDatabase};

XrmValue value;
char* type{};
if(xrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) {
if(type && strcmp(type, "String") == 0) {
const float scaling = std::stof(value.addr)/96.0f;
CORRADE_INTERNAL_ASSERT(scaling);
return scaling;
}
}

Warning{} << "Platform: can't get Xft.dpi property for virtual DPI scaling, falling back to physical DPI";
return {};
}
#endif

#ifdef CORRADE_TARGET_EMSCRIPTEN
inline Float emscriptenDpiScaling() {
return Float(emscripten_get_device_pixel_ratio());
}
#endif

}}}}

#endif
Loading

0 comments on commit ae31c3c

Please sign in to comment.