Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: DPI awareness on Windows  (Read 298 times)

0 Members and 1 Guest are viewing this topic.

texus

  • Sr. Member
  • ****
  • Posts: 487
    • View Profile
    • TGUI
    • Email
DPI awareness on Windows
« on: November 03, 2022, 11:22:58 pm »
I've written an implementation to provide minimal High-DPI support on Windows. The code can be found at https://github.com/texus/SFML/tree/feature/high-dpi-windows (view diff).
I can make a PR for this once the design has been discussed. I don't expect the code to be merged as-is, the design can be discussed and altered, but the code itself is already functional (although not extensively tested).

I've looked at how DPI scaling is handled on Windows in SDL and GLFW and did some research on my own. When I saw GLFW's implementation I started questioning whether we really need to have a framebuffer and window that have seperate sizes like in SDL: while GLFW has a separate framebuffer size, it keeps the same size as the window by default. Based on this I designed the DPI support to work without much changes to SFML's API.

Current SFML behavior

Before creating the window, System Aware scaling is enabled, but the window size is exactly as requested (i.e. no scaling is happening).

Unfortunately when dragging the window to a monitor with different scaling, Windows stretches the window and draws it at a different size without SFML knowing about it.

New default behavior

Per Monitor aware scaling is requested when creating the window. This changes nothing when only one monitor is involved or if they all have the same scaling, the window will still be exactly the size as requested. Moving the window to a monitor with different scaling (or changing the DPI on the monitor where the window is open) keeps the exact same window size though.

I've created a PR on github to make this the default behavior even in SFML 2.6, as this should be better behavior than System Aware.

New behavior with HighDpi

When manually choosing to have a high-DPI window (in my code this is done through VideoMode) the window may be created at a larger size than requested. If the main monitor has a scaling of 150% and a window of 800x600 is created, SFML would create a window of 1200x900. Everything has a 1:1 relation afterwards: rendering and mouse events all happen with the 1200x900 rectangle, only the initial size of the window differs.
When dragging the window to a monitor with 200% scaling (or when changing the scaling of the display that contains the window), the window will automatically be resized to 1600x1200. When moved back to the first monitor, it will become 1200x900 again.

On Windows 10 version 1703 or higher, the title bar of the window will also be scaled with the window (Per Monitor V2 awareness). On older Windows versions, the title bar will keep the size which it has when creating the window.

I did notice a rare bug on my system where the window sometimes gets a few pixels smaller or larger when moving monitor, but this occurs because a Windows event (WM_GETDPISCALEDSIZE) isn't being send to the app. I reproduced this in both SDL and GLFW so I'm assuming this is a bug in Windows, so I didn't bother trying to work around it.

There is currently no special event when the window changes monitor, but you would get an event about the window changing size and you could call window.getDpiScale() to figure out whether the resize was the result of the scaling changing or not.

Example code

#include <SFML/Graphics.hpp>
#include <iostream>

const bool highDpi = true;

int main()
{
    sf::RenderWindow window{ sf::VideoMode{{800, 600}, 32, highDpi}, "SFML - HighDpi" };

    // The green rectangle is centered in the window at 100% scaling
    sf::RectangleShape shape1;
    shape1.setFillColor(sf::Color::Green);
    shape1.setPosition({ 100, 100 });
    shape1.setSize({ 600, 400 });

    // The red rectangle is placed just outside the view at 100% scaling
    sf::RectangleShape shape2;
    shape2.setFillColor(sf::Color::Red);
    shape2.setPosition({ 800, 600 });
    shape2.setSize({ 800, 600 });

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();

            else if (event.type == sf::Event::Resized)
            {
                window.setView(sf::View{ sf::FloatRect{{0.f, 0.f},
                    {static_cast<float>(event.size.width), static_cast<float>(event.size.height)}} });

                std::cerr << "Resized: "
                          << event.size.width << "x" << event.size.height
                          << "  dpi-scale=" << window.getDpiScale()
                          << "\n";
            }
        }

        window.clear();
        window.draw(shape1);
        window.draw(shape2);
        window.display();
    }
}


How to test

Go to Display Settings in Windows and change the Scale to a value other than 100%.
If you have 2 monitors, set the same or a different scale and see what happens when the window is dragged to the other monitors.
You can also change the scale while the window is open.

The code contains different code paths depending on the system. While I did test different code paths by manually editing the code, I only tested on Windows 10 21H2.
- Windows 10 version 1703 or newer (Per Monitor V2 awareness)
- Windows 10 version 1607 (Per Monitor V2 introduced, documentation is unclear about whether you need 1607 or 1703 to use V2)
- Windows 8.1 or Windows 10 before version 1607 (Per Monitor V1 awareness)
- Windows Vista, Windows 7 and Windows 8.0 (Only System Aware, no per monitor settings)
- Windows XP or any other version older than Vista (no DPI scaling at all)
« Last Edit: November 05, 2022, 11:15:22 am by texus »
TGUI: C++ SFML GUI

 

anything