Browse Source

Platform: initial HiDPI support in SDL2 app on Linux and Emscripten.

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.
pull/272/head
Vladimír Vondruš 8 years ago
parent
commit
ae31c3cd82
  1. 2
      doc/changelog.dox
  2. 5
      modules/FindMagnum.cmake
  3. 6
      src/Magnum/Platform/AndroidApplication.h
  4. 11
      src/Magnum/Platform/CMakeLists.txt
  5. 131
      src/Magnum/Platform/Implementation/dpiScaling.hpp
  6. 149
      src/Magnum/Platform/Sdl2Application.cpp
  7. 281
      src/Magnum/Platform/Sdl2Application.h

2
doc/changelog.dox

@ -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()",

5
modules/FindMagnum.cmake

@ -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

6
src/Magnum/Platform/AndroidApplication.h

@ -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();
/**

11
src/Magnum/Platform/CMakeLists.txt

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

131
src/Magnum/Platform/Implementation/dpiScaling.hpp

@ -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

149
src/Magnum/Platform/Sdl2Application.cpp

@ -34,6 +34,7 @@
#include "Magnum/Math/Range.h"
#include "Magnum/Platform/ScreenedApplication.hpp"
#include "Magnum/Platform/Implementation/dpiScaling.hpp"
#ifdef MAGNUM_TARGET_GL
#include "Magnum/GL/Version.h"
@ -78,18 +79,43 @@ Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT):
_minimalLoopPeriod{0},
#endif
#ifdef MAGNUM_TARGET_GL
_glContext{nullptr}, _context{new GLContext{NoCreate, arguments.argc, arguments.argv}},
_glContext{nullptr},
#endif
_flags{Flag::Redraw}
{
Utility::Arguments args{Implementation::windowScalingArguments()};
#ifdef MAGNUM_TARGET_GL
_context.reset(new GLContext{NoCreate, args, arguments.argc, arguments.argv});
#else
args.parse(arguments.argc, arguments.argv);
#endif
if(SDL_Init(SDL_INIT_VIDEO) < 0) {
Error() << "Cannot initialize SDL.";
std::exit(1);
}
#ifndef MAGNUM_TARGET_GL
static_cast<void>(arguments);
/* Save command-line arguments */
if(args.value("log") == "verbose") _verboseLog = true;
const std::string dpiScaling = args.value("dpi-scaling");
if(dpiScaling == "default")
_commandLineDpiScalingPolicy = Implementation::DpiScalingPolicy::Default;
#ifdef CORRADE_TARGET_APPLE
else if(dpiScaling == "framebuffer")
_commandLineDpiScalingPolicy = Implementation::DpiScalingPolicy::Framebuffer;
#endif
#ifndef CORRADE_TARGET_APPLE
#if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID)
else if(dpiScaling == "virtual")
_commandLineDpiScalingPolicy = Implementation::DpiScalingPolicy::Virtual;
#endif
else if(dpiScaling == "physical")
_commandLineDpiScalingPolicy = Implementation::DpiScalingPolicy::Physical;
#endif
else if(dpiScaling.find_first_of(" \t\n") != std::string::npos)
_commandLineDpiScaling = args.value<Vector2>("dpi-scaling");
else
_commandLineDpiScaling = Vector2{args.value<Float>("dpi-scaling")};
}
void Sdl2Application::create() {
@ -106,12 +132,96 @@ void Sdl2Application::create(const Configuration& configuration, const GLConfigu
}
#endif
Vector2 Sdl2Application::dpiScaling(const Configuration& configuration) const {
std::ostream* verbose = _verboseLog ? Debug::output() : nullptr;
/* Use values from the configuration only if not overriden on command line.
In any case explicit scaling has a precedence before the policy. */
Implementation::DpiScalingPolicy dpiScalingPolicy{};
if(!_commandLineDpiScaling.isZero()) {
Debug{verbose} << "Platform::Sdl2Application: user-defined DPI scaling" << _commandLineDpiScaling.x();
return _commandLineDpiScaling;
} else if(UnsignedByte(_commandLineDpiScalingPolicy)) {
dpiScalingPolicy = _commandLineDpiScalingPolicy;
} else if(!configuration.dpiScaling().isZero()) {
Debug{verbose} << "Platform::Sdl2Application: app-defined DPI scaling" << _commandLineDpiScaling.x();
return configuration.dpiScaling();
} else {
dpiScalingPolicy = configuration.dpiScalingPolicy();
}
/* There's no choice on Apple, it's all controlled by the plist file. So
unless someone specified custom scaling via config or command-line
above, return the default. */
#ifdef CORRADE_TARGET_APPLE
return Vector2{1.0f};
/* Otherwise there's a choice between virtual and physical DPI scaling */
#else
/* Try to get virtual DPI scaling first, if supported and requested */
#if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID)
if(dpiScalingPolicy == Implementation::DpiScalingPolicy::Virtual) {
/* Use Xft.dpi on X11 */
#ifdef _MAGNUM_PLATFORM_USE_X11
const Vector2 dpiScaling{Implementation::x11DpiScaling()};
if(!dpiScaling.isZero()) {
Debug{verbose} << "Platform::Sdl2Application: virtual DPI scaling" << dpiScaling.x();
return dpiScaling;
}
/* Otherwise ¯\_(ツ)_/¯ */
#else
Debug{verbose} << "Platform::Sdl2Application: sorry, virtual DPI scaling not implemented on this platform yet";
return Vector2{1.0f};
#endif
}
#endif
/* At this point, either the virtual DPI query failed or a physical DPI
scaling is requested */
#if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID)
CORRADE_INTERNAL_ASSERT(dpiScalingPolicy == Implementation::DpiScalingPolicy::Virtual || dpiScalingPolicy == Implementation::DpiScalingPolicy::Physical);
#else
CORRADE_INTERNAL_ASSERT(dpiScalingPolicy == Implementation::DpiScalingPolicy::Physical);
#endif
/* Take device pixel ratio on Emscripten */
#ifdef CORRADE_TARGET_EMSCRIPTEN
const Vector2 dpiScaling{Implementation::emscriptenDpiScaling()};
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)
Vector2 dpi;
if(SDL_GetDisplayDPI(0, nullptr, &dpi.x(), &dpi.y()) == 0) {
const Vector2 dpiScaling{dpi/96.0f};
Debug{verbose} << "Platform::Sdl2Application: physical DPI scaling" << dpiScaling;
return dpiScaling;
}
Warning{} << "Platform::Sdl2Application: can't get physical display DPI, falling back to no scaling:" << SDL_GetError();
return Vector2{1.0f};
/* Not implemented otherwise */
#else
Debug{verbose} << "Platform::Sdl2Application: sorry, physical DPI scaling not implemented on this platform yet";
return Vector2{1.0f};
#endif
#endif
}
bool Sdl2Application::tryCreate(const Configuration& configuration) {
#ifdef MAGNUM_TARGET_GL
if(!(configuration.windowFlags() & Configuration::WindowFlag::Contextless))
return tryCreate(configuration, GLConfiguration{});
#endif
/* Scale window based on DPI */
_dpiScaling = dpiScaling(configuration);
const Vector2i scaledWindowSize = configuration.size()*_dpiScaling;
#ifndef CORRADE_TARGET_EMSCRIPTEN
/* Create window */
if(!(_window = SDL_CreateWindow(
@ -121,7 +231,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration) {
nullptr,
#endif
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
configuration.size().x(), configuration.size().y(),
scaledWindowSize.x(), scaledWindowSize.y(),
Uint32(configuration.windowFlags()&~Configuration::WindowFlag::Contextless))))
{
Error() << "Platform::Sdl2Application::tryCreate(): cannot create window:" << SDL_GetError();
@ -129,7 +239,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration) {
}
#else
/* Emscripten-specific initialization */
if(!(_glContext = SDL_SetVideoMode(configuration.size().x(), configuration.size().y(), 24, SDL_OPENGL|SDL_HWSURFACE|SDL_DOUBLEBUF))) {
if(!(_glContext = SDL_SetVideoMode(scaledWindowSize.x(), scaledWindowSize.y(), 24, SDL_OPENGL|SDL_HWSURFACE|SDL_DOUBLEBUF))) {
Error() << "Platform::Sdl2Application::tryCreate(): cannot create window:" << SDL_GetError();
return false;
}
@ -184,6 +294,10 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf
SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, glConfiguration.isSRGBCapable());
#endif
/* Scale window based on DPI */
_dpiScaling = dpiScaling(configuration);
const Vector2i scaledWindowSize = configuration.size()*_dpiScaling;
/** @todo Remove when Emscripten has proper SDL2 support */
#ifndef CORRADE_TARGET_EMSCRIPTEN
/* Set context version, if user-specified */
@ -241,7 +355,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf
nullptr,
#endif
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
configuration.size().x(), configuration.size().y(),
scaledWindowSize.x(), scaledWindowSize.y(),
SDL_WINDOW_OPENGL|SDL_WINDOW_HIDDEN|Uint32(configuration.windowFlags()))))
{
Error() << "Platform::Sdl2Application::tryCreate(): cannot create window:" << SDL_GetError();
@ -294,7 +408,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf
if(!(_window = SDL_CreateWindow(configuration.title().data(),
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
configuration.size().x(), configuration.size().y(),
scaledWindowSize.x(), scaledWindowSize.y(),
SDL_WINDOW_OPENGL|SDL_WINDOW_HIDDEN|Uint32(configuration.windowFlags()&~Configuration::WindowFlag::Contextless))))
{
Error() << "Platform::Sdl2Application::tryCreate(): cannot create window:" << SDL_GetError();
@ -325,7 +439,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf
#endif
#else
/* Emscripten-specific initialization */
if(!(_glContext = SDL_SetVideoMode(configuration.size().x(), configuration.size().y(), 24, SDL_OPENGL|SDL_HWSURFACE|SDL_DOUBLEBUF))) {
if(!(_glContext = SDL_SetVideoMode(scaledWindowSize.x(), scaledWindowSize.y(), 24, SDL_OPENGL|SDL_HWSURFACE|SDL_DOUBLEBUF))) {
Error() << "Platform::Sdl2Application::tryCreate(): cannot create context:" << SDL_GetError();
return false;
}
@ -354,7 +468,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf
}
#endif
Vector2i Sdl2Application::windowSize() {
Vector2i Sdl2Application::windowSize() const {
#ifndef CORRADE_TARGET_EMSCRIPTEN
Vector2i size;
SDL_GetWindowSize(_window, &size.x(), &size.y());
@ -364,6 +478,16 @@ Vector2i Sdl2Application::windowSize() {
#endif
}
Vector2i Sdl2Application::framebufferSize() const {
#ifndef CORRADE_TARGET_EMSCRIPTEN
Vector2i size;
SDL_GL_GetDrawableSize(_window, &size.x(), &size.y());
return size;
#else
return {_glContext->w, _glContext->h};
#endif
}
void Sdl2Application::swapBuffers() {
#ifndef CORRADE_TARGET_EMSCRIPTEN
SDL_GL_SwapWindow(_window);
@ -633,12 +757,13 @@ Sdl2Application::Configuration::Configuration():
_title("Magnum SDL2 Application"),
#endif
#ifdef CORRADE_TARGET_EMSCRIPTEN
_size{640, 480}
_size{640, 480},
#elif !defined(CORRADE_TARGET_IOS)
_size{800, 600}
_size{800, 600},
#else
_size{} /* SDL2 detects someting for us */
_size{}, /* SDL2 detects someting for us */
#endif
_dpiScalingPolicy{DpiScalingPolicy::Default}
#if defined(MAGNUM_BUILD_DEPRECATED) && defined(MAGNUM_TARGET_GL)
, _sampleCount(0)
#ifndef CORRADE_TARGET_EMSCRIPTEN

281
src/Magnum/Platform/Sdl2Application.h

@ -56,6 +56,10 @@
namespace Magnum { namespace Platform {
namespace Implementation {
enum class DpiScalingPolicy: UnsignedByte;
}
/** @nosubgrouping
@brief SDL2 application
@ -236,6 +240,89 @@ a particular value for details:
- @ref Configuration::WindowFlag::Borderless hides the menu bar
- @ref Configuration::WindowFlag::Resizable makes the application respond to
device orientation changes
@section Platform-Sdl2Application-dpi DPI awareness
On displays that match the platform default DPI (96 or 72),
@ref Configuration::setSize() will create the window in exactly the requested
size and the framebuffer pixels will match display pixels 1:1. On displays that
have different DPI, there are three possible scenarios, listed below. It's
possible to fine tune the behavior either using extra parameters passed to
@ref Configuration::setSize() or via the `--magnum-dpi-scaling` command-line
option.
- Framebuffer DPI scaling. The window is created with exactly the requested
size and all event coordinates are reported also relative to that size.
However, the window backing framebuffer has a different size. This is only
supported on macOS and iOS. See @ref platforms-macos-hidpi for details how
to enable it. Equivalent to passing
@ref Configuration::DpiScalingPolicy::Framebuffer to
@ref Configuration::setSize() or `framebuffer` on command line.
- Virtual DPI scaling. Scales the window based on DPI scaling setting in the
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.
- 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
display with 240 DPI the window size will be 2000x1500 in pixels, but it
will be 21 centimeters wide, the same as a 800x600 window would be on a 96
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
@ref Configuration::setSize() or `physical` on command line.
Besides the above, it's possible to supply a custom DPI scaling value to
@ref Configuration::setSize() or the `--magnum-dpi-scaling` command-line
option. Using `--magnum-dpi-scaling &lt;number&gt;` will make the scaling
same in both directions, with `--magnum-dpi-scaling "<horizontal> <vertical>"`
the scaling will be different in each direction. On desktop systems custom DPI
scaling value will affect physical window size (with the content being scaled),
on mobile and web it will affect sharpness of the contents.
The default is depending on the platform:
- On macOS and iOS, the default and only supported option is
@ref Configuration::DpiScalingPolicy::Framebuffer. On this platform,
@ref windowSize() and @ref framebufferSize() will differ depending on
whether `NSHighResolutionCapable` is enabled in the `*.plist` file or not.
By default, @ref dpiScaling() is @cpp 1.0f @ce in both dimensions but it
can be overriden using custom DPI scaling.
- On Windows, the default is @ref Configuration::DpiScalingPolicy::Framebuffer.
The @ref windowSize() and @ref framebufferSize() is always the same.
Depending on whether the DPI awareness was enabled in the manifest file or
set by the `SetProcessDpiAwareness()` API, @ref dpiScaling() is either
@cpp 1.0f @ce in both dimensions, indicating a low-DPI screen or a
non-DPI-aware app, or some other value for HiDPI screens. In both cases the
value can be overriden using custom DPI scaling.
- On Linux, the default is @ref Configuration::DpiScalingPolicy::Virtual,
taken from the `Xft.dpi` property. If the property is not available, it
falls back to @ref Configuration::DpiScalingPolicy::Physical, querying the
monitor DPI value. The @ref windowSize() and @ref framebufferSize() is
always the same, @ref dpiScaling() contains the queried DPI scaling value.
The value can be overriden using custom DPI scaling.
- On @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten", the default is physical DPI
scaling, taken from [Window.getDevicePixelRatio()](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio). The
@ref windowSize() and @ref framebufferSize() is always the same,
@ref dpiScaling() contains the queried DPI scaling value. The value can be
overriden using custom DPI scaling.
If your application is saving and restoring window size, it's advisable to take
@ref dpiScaling() into account:
- Either divide the window size by the DPI scaling value and use that to
restore the window next time --- but note this might accumulate slight
differences in window sizes over time, especially if fractional scaling is
involved.
- Or save the scaled size and use @ref Configuration::setSize(const Vector2i&, const Vector2&)
with @cpp 1.0f @ce as custom DPI scaling next time --- but this doesn't
properly handle cases where the window is opened on a display with
different DPI.
*/
class Sdl2Application {
public:
@ -472,15 +559,60 @@ class Sdl2Application {
/** @{ @name Screen handling */
public:
/**
* @brief Window size
*
* Window size to which all input event coordinates can be related.
* Note that especially on HiDPI systems the reported window size might
* not be the same as framebuffer size.
* Note that, especially on HiDPI systems, it may be different from
* @ref framebufferSize(). If a window is not created yet, returns
* zero vector. See @ref Platform-Sdl2Application-dpi for more
* information.
* @see @ref dpiScaling()
*/
Vector2i windowSize() const;
#if defined(MAGNUM_TARGET_GL) || defined(DOXYGEN_GENERATING_OUTPUT)
/**
* @brief Framebuffer size
*
* Size of the default framebuffer. Note that, especially on HiDPI
* systems, it may be different from @ref windowSize(). If a window is
* not created yet, returns zero vector. See
* @ref Platform-Sdl2Application-dpi for more information.
*
* @note This function is available only if Magnum is compiled with
* @ref MAGNUM_TARGET_GL enabled (done by default). See
* @ref building-features for more information.
*
* @see @ref dpiScaling()
*/
Vector2i framebufferSize() const;
#endif
/**
* @brief DPI scaling
*
* How the content should be scaled relative to system defaults for
* given @ref windowSize(). If a window is not created yet, returns
* zero vector, use @ref dpiScaling(const Configuration&) const for
* calculating a value independently. See @ref Platform-Sdl2Application-dpi
* for more information.
* @see @ref framebufferSize()
*/
Vector2i windowSize();
Vector2 dpiScaling() const { return _dpiScaling; }
/**
* @brief DPI scaling for given configuration
*
* Calculates DPI scaling that would be used when creating a window
* with given @p configuration. Takes into account DPI scaling policy
* and custom scaling specified on the command-line. See
* @ref Platform-Sdl2Application-dpi for more information.
*/
Vector2 dpiScaling(const Configuration& configuration) const;
protected:
/**
* @brief Swap buffers
*
@ -739,6 +871,13 @@ class Sdl2Application {
typedef Containers::EnumSet<Flag> Flags;
CORRADE_ENUMSET_FRIEND_OPERATORS(Flags)
/* These are saved from command-line arguments */
bool _verboseLog{};
Implementation::DpiScalingPolicy _commandLineDpiScalingPolicy{};
Vector2 _commandLineDpiScaling;
Vector2 _dpiScaling;
#ifndef CORRADE_TARGET_EMSCRIPTEN
SDL_Window* _window;
UnsignedInt _minimalLoopPeriod;
@ -949,6 +1088,33 @@ CORRADE_ENUMSET_OPERATORS(Sdl2Application::GLConfiguration::Flags)
#endif
#endif
namespace Implementation {
enum class DpiScalingPolicy: UnsignedByte {
/* Using 0 for an "unset" value */
#ifdef CORRADE_TARGET_APPLE
Framebuffer = 1,
#endif
#ifndef CORRADE_TARGET_APPLE
#if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID)
Virtual = 2,
#endif
Physical = 3,
#endif
Default
#ifdef CORRADE_TARGET_APPLE
= Framebuffer
#elif !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID)
= Virtual
#else
= Physical
#endif
};
}
/**
@brief Configuration
@ -1025,6 +1191,62 @@ class Sdl2Application::Configuration {
typedef Containers::EnumSet<WindowFlag> WindowFlags;
#endif
/**
* @brief DPI scaling policy
*
* DPI scaling policy when requesting a particular window size. Can
* be overriden on command-line using `--magnum-dpi-scaling` or via
* the `MAGNUM_DPI_SCALING` environment variable.
* @see @ref setSize(), @ref Platform-Sdl2Application-dpi
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
enum class DpiScalingPolicy: UnsignedByte {
/**
* Framebuffer DPI scaling. The window will have the same size as
* requested, but the framebuffer size will be different. Supported
* only on macOS and iOS and is also the only supported value
* there.
*/
Framebuffer,
/**
* Virtual DPI scaling. Scales the window based on UI scaling
* setting in the system. Falls back to
* @ref DpiScalingPolicy::Physical on platforms that don't support
* it. Supported only on desktop platforms (except macOS) and it's
* the default there.
*
* Equivalent to `--magnum-dpi-scaling virtual` passed on
* command-line.
*/
Virtual,
/**
* 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 size on given display physical
* DPI. On platforms that don't have a concept of a window it
* causes the framebuffer to match screen pixels 1:1 without any
* scaling. Supported on desktop platforms except macOS and on
* mobile and web. Default on mobile and web.
*
* Equivalent to `--magnum-dpi-scaling physical` passed on
* command-line.
*/
Physical,
/**
* Default policy for current platform. Alias to one of
* @ref DpiScalingPolicy::Framebuffer, @ref DpiScalingPolicy::Virtual
* or @ref DpiScalingPolicy::Physical depending on platform. See
* @ref Platform-Sdl2Application-dpi for details.
*/
Default
};
#else
typedef Implementation::DpiScalingPolicy DpiScalingPolicy;
#endif
/*implicit*/ Configuration();
~Configuration();
@ -1060,18 +1282,59 @@ class Sdl2Application::Configuration {
/** @brief Window size */
Vector2i size() const { return _size; }
/**
* @brief DPI scaling policy
*
* If @ref dpiScaling() is non-zero, it has a priority over this value.
* The `--magnum-dpi-scaling` command-line option has a priority over
* any application-set value.
* @see @ref setSize(const Vector2i&, DpiScalingPolicy)
*/
DpiScalingPolicy dpiScalingPolicy() const { return _dpiScalingPolicy; }
/**
* @brief Custom DPI scaling
*
* If zero, then @ref dpiScalingPolicy() has a priority over this
* value. The `--magnum-dpi-scaling` command-line option has a priority
* over any application-set value.
* @see @ref setSize(const Vector2i&, const Vector2&)
*/
Vector2 dpiScaling() const { return _dpiScaling; }
/**
* @brief Set window size
* @param size Desired window size
* @param dpiScalingPolicy Policy based on which DPI scaling will be set
* @return Reference to self (for method chaining)
*
* Default is @cpp {800, 600} @ce and @cpp {640, 480} @ce on
* Emscripten. On iOS it defaults to a "reasonable" size based on
* whether HiDPI support is enabled using @ref WindowFlag::AllowHighDpi,
* but not necessarily native display resolution (you have to set it
* explicitly).
* Emscripten with @p dpiScalingPolicy set to
* @ref DpiScalingPolicy::Default. On iOS it defaults to a size that
* matches display resolution. See @ref Platform-Sdl2Application-dpi
* for more information.
* @see @ref setSize(const Vector2i&, const Vector2&)
*/
Configuration& setSize(const Vector2i& size, DpiScalingPolicy dpiScalingPolicy = DpiScalingPolicy::Default) {
_size = size;
_dpiScalingPolicy = dpiScalingPolicy;
return *this;
}
/**
* @brief Set window size with custom DPI scaling
* @param size Desired window size
* @param dpiScaling Custom DPI scaling value
*
* Compared to @ref setSize(const Vector2i&, DpiScalingPolicy) which
* autodetects the DPI scaling value according to given policy, this
* function sets the DPI scaling directly. The resulting
* @ref Sdl2Application::windowSize() is @cpp size*dpiScaling @ce and
* @ref Sdl2Application::dpiScaling() is @p dpiScaling.
*/
Configuration& setSize(const Vector2i& size) {
Configuration& setSize(const Vector2i& size, const Vector2& dpiScaling) {
_size = size;
_dpiScaling = dpiScaling;
return *this;
}
@ -1156,7 +1419,9 @@ class Sdl2Application::Configuration {
std::string _title;
#endif
Vector2i _size;
DpiScalingPolicy _dpiScalingPolicy;
WindowFlags _windowFlags;
Vector2 _dpiScaling;
#if defined(MAGNUM_BUILD_DEPRECATED) && defined(MAGNUM_TARGET_GL)
Int _sampleCount;
#ifndef CORRADE_TARGET_EMSCRIPTEN

Loading…
Cancel
Save