While SFML currently has partial support for high-DPI displays, their are some issues that cannot be resolved without making SFML's API DPI aware. I've done some research and compiled a list of links to how this is handled on various systems / libraries:
- OS X - Comprehensive documentation for OS X
- Windows - Introduction tutorial for Windows
- Windows - Comprehensive documentation for Windows
- iOS - Section from the iOS documentation
- Android - Description for Android devices
- GLFW- Small section describing DPI handling in GLFW
I was unable to find any good resources for Linux, but it seems like it is a limitation in X that prevents correct DPI awareness (although supposedly Wayland has support for it).
For those who are new to DPI awareness, I would highly recommend the introduction in the OS X documentation: it's not too long and explains the basic concept very well (and has pictures!).
For the most part, it seems like all the systems use the same basic design: window management, mouse coordinates, etc. use a more abstract system for screen coordinates, with various names being used for the measuring unit ("points", "density-independent pixels", "screen coordinates", etc.), whereas lower-level drawing commands ,including most interactions with OpenGL, using the actual pixel coordinates. Then a scaling factor is exposed in the API to convert between these two systems. Since SFML encompasses of these domains, it should make a distinction between these two systems in its own API.
I took the liberty of going through the SFML API and identifying the changes that would have to be made in order to make SFML fully DPI aware. First, the following would have to be changed to use screen coordinates rather than physical pixels (this includes making these values floats):
- sf::Event::MouseButtonEvent: x, y
- sf::Event::MouseMoveEvent: x, y
- sf::Event::MouseWheelEvent: x, y
- sf::Event::SizeEvent: width, height
- sf::Event::TouchEvent: x, y
- sf::Mouse: getPosition(), setPosition()
- sf::VideoMode: width, height
- sf::Window: getPosition(), setPosition(), getSize(), setSize()
The following should continue to use physical pixels:
- sf::RenderTarget: getSize()
- sf::RenderTexture
- sf::Texture
- sf::Image
Other classes in SFML (such as views, shapes, etc.) already use coordinate systems that are not tied to pixels/screen coordinates, and are thus not affected.
In addition to the above changes, the following should be added to the API:
- Add the function sf::Window/RenderWindow::getScaleFactor() that would retrieve the current scaling factor for the window.
- Change the default view generation for sf::RenderWindow so that it is based on screen coordinates rather than pixels. This way, the default view is independent of the DPI of the screen it was created on.
- A scaling field should be added to sf::Event::SizeEvent for cases like when a window is moved between displays with different scaling factors. Alternatively, we could do similar to GLFW and create a new event type that deals exclusively with scaling events (sf::Event::ScaleEvent?).
There is one more modification that needs to be made that concerns
sf::RenderWindow::getSize(). Since
sf::Window::getSize() should use screen coordinates, as it is part of window management, and
sf::RenderTarget::getSize() should use pixels, as it works with OpenGL, which way should
sf::RenderWindow::getSize() go? Here is a short list of ideas:
- Rename the getSize() methods so that the one inherited form sf::Window is kept distinct from the one inherited from sf::RenderTexture.
- Add a bool argument to getSize() to signal which version we want.
- Exclude sf::RnderWindow::getSize() from the public API so the user has to explicitly specify render_window.Window::getSize() or render_window.RenderTarget::getSize().
- Use composition rather than inheritance between sf::RenderTarget and sf::RenderWindow.
None of these options are very clean, however. I'm also not sure of all the other changes planned for SFML 3 though, so maybe they affect this issue somehow, hopefully for the better?
Two final notes: First, dynamic changes to the scaling, such as the user moving the window between two monitors with different scaling factors, does not require full multiple monitor support, as it is possible to query the current scaling factor for a given window directly (at least on OS X and Windows 8.1), so this does not depend on complete multiple monitor support in SFML.
Second, although above I called them physical pixels, it turns out that even what OpenGL works with does not always correspond to physical pixels on the screen. For example, on my Retina display MacBook, the virtual screen is 1440x900 and the physical screen is 2560x1600, but the scaling factor is 2.0x. This is because OS X renders everything to a framebuffer of 2880x1800, then scales it in hardware back to 2560x1600 for output. This happens well after SFML, or even OpenGL, is involved, and we aren't really supposed to be concerned with it, but it is interesting nonetheless.
Anyways, is full DPI awareness support something we should target for SFML 3? And, if so, is it too soon to start work on implementing it, especially considering that it does change quite a lot of types in the API?