Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for high DPI screens #202

Open
rogerdahl opened this issue Jan 23, 2017 · 25 comments
Open

Support for high DPI screens #202

rogerdahl opened this issue Jan 23, 2017 · 25 comments

Comments

@rogerdahl
Copy link

Thank you for NanoGUI, it's awesome!

Are there any plans to add support for scaling on Linux? I wrote an app using NanoGUI and it uses the size passed to Screen directly as pixel size. After upgrading my monitor to 4K and increasing the OS scale factor to match, most apps and GUI elements scaled appropriately, but my NanoGUI app is now tiny...

@svenevs
Copy link
Collaborator

svenevs commented Jan 23, 2017

Are you using Screen::pixelRatio() (or its equivalent mPixelRatio member variable)? You should be able to use it to multiply with Widget::mSize, but you may have to deal with float vs int stuff at some point too.

@rogerdahl
Copy link
Author

I checked now and found that Screen::pixelRatio() return 1.

@svenevs
Copy link
Collaborator

svenevs commented Jan 23, 2017

That's unfortunate :/ Out of curiosity, if you manually resize your screen class to scale everything by 2 does it appear as you desire? (by 2 to go from 1920x1080 to 3840x2160).

If it does, then I'm not sure what to say. At the top of screen.cpp is where this is getting setup, I'm inclined to assume you may not have gtk or something. One thing you could try is getting rid of the __linux__ part so that it looks like

/* Calculate pixel ratio for hi-dpi devices. */
static float get_pixel_ratio(GLFWwindow *window) {
#if defined(_WIN32)
    HWND hWnd = glfwGetWin32Window(window);
    HMONITOR monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
    /* The following function only exists on Windows 8.1+, but we don't want to make that a dependency */
    static HRESULT (WINAPI *GetDpiForMonitor_)(HMONITOR, UINT, UINT*, UINT*) = nullptr;
    static bool GetDpiForMonitor_tried = false;

    if (!GetDpiForMonitor_tried) {
        auto shcore = LoadLibrary(TEXT("shcore"));
        if (shcore)
            GetDpiForMonitor_ = (decltype(GetDpiForMonitor_)) GetProcAddress(shcore, "GetDpiForMonitor");
        GetDpiForMonitor_tried = true;
    }

    if (GetDpiForMonitor_) {
        uint32_t dpiX, dpiY;
        if (GetDpiForMonitor_(monitor, 0 /* effective DPI */, &dpiX, &dpiY) == S_OK)
            return std::round(dpiX / 96.0);
    }
    return 1.f;
#else
    Vector2i fbSize, size;
    glfwGetFramebufferSize(window, &fbSize[0], &fbSize[1]);
    glfwGetWindowSize(window, &size[0], &size[1]);
    return (float)fbSize[0] / (float)size[0];
#endif
}

There doesn't appear to be anything in there that wouldn't work on linux, and I know this code is reliable for OSX.

@rogerdahl
Copy link
Author

rogerdahl commented Jan 23, 2017

Thank you for your help! The version I'm using does include a path for Linux in get_pixel_ratio():

#elif defined(__linux__)
    (void) window;

    /* Try to read the pixel ratio from GTK */
    FILE *fp = popen("gsettings get org.gnome.desktop.interface scaling-factor", "r");
    if (!fp)
        return 1;

    int ratio = 1;
    if (fscanf(fp, "uint32 %i", &ratio) != 1)
        return 1;

    if (pclose(fp) != 0)
        return 1;

    return ratio >= 1 ? ratio : 1;

I'm on Mate and gsettings get org.gnome.desktop.interface scaling-factor returns 0.

Wonder if there's a way to get that value that works on more Linux desktops.

@rogerdahl
Copy link
Author

A quick test shows that forcing the return value of get_pixel_ratio() to a value above 1 causes everything to scale nicely.

@svenevs
Copy link
Collaborator

svenevs commented Jan 23, 2017

Ahhh I see the problem. Mate != Gnome, so even if you set org.gnome.desktop.interface scaling-factor, it won't matter because you can't run gnome and mate at the same time.

Before investigating more obtuse alternatives, does it work if you get rid of the #elif defined(__linux__) so that the code being executed for you is

Vector2i fbSize, size;
glfwGetFramebufferSize(window, &fbSize[0], &fbSize[1]);
glfwGetWindowSize(window, &size[0], &size[1]);
return (float)fbSize[0] / (float)size[0];

?

If that code is also not working for you, there are alternatives. To help, please paste the output of

  1. xrandr | fgrep '*', and
  2. xdpyinfo | grep dim

@rogerdahl
Copy link
Author

Looks like querying X11 directly could be the way to go.

#include <X11/Xlib.h>

DisplayWidth();
DisplayHeight();
DisplayWidthMM();
DisplayHeightMM();

The returned values seem to make sense. I think the mm sizes are extrapolated from the display resolution and DPI values I've set in the desktop.

@rogerdahl
Copy link
Author

Removing the Linux path and using the glfwGet functions did not work (got the small size). But the xrandr commands you suggested show what's happening. There is also a 1080p monitor hooked up to the box and that monitor is showing up as device 0. The primary 4k monitor is showing up below in the list. So I think it comes down to pulling the values for the monitor on which the NanoGUI window is going to appear instead of the monitor at device 0.

@dvicini
Copy link
Contributor

dvicini commented Sep 27, 2017

There is also an issue when using KDE as a desktop. KDE does not use the gsettings file and therefore nanogui does not scale with high DPI screens when using KDE. I am not sure though how to retrieve the UI scaling factor in KDE.

@wjakob
Copy link
Owner

wjakob commented Sep 27, 2017

@dvicini: Would you mind checking if there is a way to do this on a KDE environment?

@svenevs
Copy link
Collaborator

svenevs commented Sep 27, 2017

Would you mind checking if there is a way to do this on a KDE environment?

It's kreadconfig.

However, maybe we can just use xdpyinfo? My understanding is that is part of X server, unrelated to the desktop manager.
- Note: awk '{ print $2 }' is not safe, e.g. if grep is aliased. Easy solution is to /usr/bin/grep (almost certainly available), better solution would probably be an explicit regex for dimensions:\s+(\d+x d+)\s+

Otherwise, we need a way of detecting things. I think the variables that need to be relied on here are the XDG_* variants:

$ printenv | grep -i desktop
DESKTOP_SESSION=gnome
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
XDG_SESSION_DESKTOP=gnome
XDG_CURRENT_DESKTOP=GNOME

I believe the promise that desktop managers make is to always set XDG_CURRENT_DESKTOP, kind of like how xdg-open filename.txt (if you look at the actual script) just cycles through all of the managers.

This approach (I believe) rules out anybody on linux who built their own window manager, though.

@dvicini can you post back the output of these two commands:

  1. xdpyinfo | grep dimensions
  2. printenv | grep -i desktop

This still only solves single-monitor scenarios BTW. Good enough for a hotfix for KDE though 😉

@dvicini
Copy link
Contributor

dvicini commented Sep 27, 2017

Hi,

Thanks for the input. So I ran the commands you suggested and get

dimensions:    3840x2160 pixels (613x352 millimeters)

and

QT_QUICK_CONTROLS_STYLE=org.kde.desktop
DESKTOP_SESSION=plasma
XDG_SESSION_DESKTOP=plasma
XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0
XDG_CURRENT_DESKTOP=KDE
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0

i don't think the dimensions from xdpyinfo really help us here. For KDE, the suggested way of scaling the display content on high dpi screens is to go through the display settings:
https://wiki.archlinux.org/index.php/HiDPI#Using_KDE_system_settings

If I change these, the dimensions output from xdpyinfo does not change (the size in millimeters is always the physical size of my screen).

I also found the kreadconfig command, but I don't know how to access the display settings using that, as there does not seem to be a list of available settings?

UPDATE:
I found where the setting in question is stored. In my case it is in ~/.config/kdeglobals. I can now retrieve the scaling factor I set in the settings by using
kreadconfig5 --group KScreen --key ScaleFactor
So I guess this would be the right command to use?

Delio

@dvicini
Copy link
Contributor

dvicini commented Sep 27, 2017

I think I figured out how to detect which desktop environment is used. I changed the relevant section in screen.cpp to:

    /* Try to read the pixel ratio from GTK */
    float ratio = 1.0f;
    if (std::getenv("XDG_CURRENT_DESKTOP") == std::string("KDE")) {
        FILE *fp = popen("kreadconfig5 --group KScreen --key ScaleFactor", "r");
        if (!fp)
            return 1;
    
        if (fscanf(fp, "%f", &ratio) != 1)
            return 1;    
        if (pclose(fp) != 0)
            return 1; 
    } else {
        FILE *fp = popen("gsettings get org.gnome.desktop.interface scaling-factor", "r");
        if (!fp)
            return 1;
    
        int ratioInt = 1;
        if (fscanf(fp, "uint32 %i", &ratioInt) != 1)
            return 1;
        ratio = ratioInt;
    
        if (pclose(fp) != 0)
            return 1;
    }
    return ratio >= 1 ? ratio : 1;

This works for me now, but it's not a very general solution, as it only works for KDE5 I guess. Should I make a pull request or are you looking for a more general solution?

@wjakob
Copy link
Owner

wjakob commented Sep 27, 2017

This looks reasonable. But what if std::getenv returns nullptr?

@wjakob
Copy link
Owner

wjakob commented Sep 27, 2017

The pclose(fp) can be factored out.

@svenevs
Copy link
Collaborator

svenevs commented Sep 27, 2017

i don't think the dimensions from xdpyinfo really help us here

Aren't we just checking the ratio of width / 1920 and height / 1080? I was under the impression thats what the NanoGUI reference resolution is. No, that's not how DPI works sorry. However, an excerpt from OSX:

default screen number:    0
number of screens:    1

screen #0:
  dimensions:    1440x878 pixels (381x232 millimeters)
  resolution:    96x96 dots per inch

Thus xdpyinfo tabled for another day...

But what if std::getenv returns nullptr?

Then we can't do anything, in which case just return scale of 1?

@rogerdahl
Copy link
Author

How about using xdpyinfo on the platforms where it returns correct resolution and physical size? Then calculate DPI.

@wjakob
Copy link
Owner

wjakob commented Sep 27, 2017

I prefer the scale factor approach. This is largely also a user choice -- a user might prefer 1.5x or 2.5x on a given screen regardless of the actual DPI value.

@dvicini
Copy link
Contributor

dvicini commented Sep 27, 2017

Yes, the scale factor is user specific and should be accounted for, as the rest of the user interface is scaled by this factor. I will clean up the code (add nullptr check, factor out pclose) and create a pull request.

@dbird137
Copy link

First @wjakob, great work on this incredible library. Thank you.

(This is also related to #347 )

A few weeks ago I did some work on this as I was also having trouble with scaling on a stock X11 Ubuntu 18.04. I think the problem was that Gnome itself had a bug where gsettings get org.gnome.desktop.interface scaling-factor always returned 0 (despite what was set in the display settings UI) so get_pixel_ratio() in screen.cpp always returned 1.

GLFW 3.3 had not yet been completed but forcing nanogui to use a dev 3.3 version allowed me to use their new glfwGetWindowContentScale() which seems like a great platform agnostic solution. My testing showed this to work on that Ubuntu 18.04, macOS 10.14 (current, I think), and Windows 10.0.18362.

Now that GLFW 3.3 has landed, my thinking is that something like the following could replace everything currently in get_pixel_ratio().

/* Calculate pixel ratio for hi-dpi devices. */
static float get_pixel_ratio(GLFWwindow *window) {

    (void) window;

    float ratio = 1.0f;
    float xscale;
    float yscale;

    glfwGetWindowContentScale(window, &xscale, &yscale);

    ratio = xscale;

    return ratio;
}

Some things to think about (and things I'm not sure of):

  • I'm not sure if glfwGetWindowContentScale() or glfwGetMonitorContentScale() is more appropriate and in what cases those scales would differ.
  • I've not tested anything running KDE or Wayland.
  • I set it up to return the xscale as it is for _WIN32. I'm not sure if the yscale being different from xscale has a valid use case that should be accounted for.
  • There's also a fancy new function glfwSetWindowContentScaleCallback() that might be a nice add to nanogui to deal with a user's scale change or moving the window between differently scaled displays.

@rogerdahl
Copy link
Author

glfwGetWindowContentScale() looks great but before moving to it, it would be a good idea to take a look at how it works and determine how reliable it is likely to be across all the platforms that NanoGUI runs on.

@rogerdahl
Copy link
Author

glfwGetWindowContentScale() branches out to platform specific functions. This is the one for X:

https://github.com/glfw/glfw/blob/a337c568486025d22443535b7c047a563bc34373/src/x11_monitor.c#L336-L343

I'm not sure if this ends up getting the user specified "logical" DPI or the hardware DPI. The description for glfwGetWindowContentScale() is unclear to me as well:

The content scale is the ratio between the current DPI and the platform's default DPI.

Relevant GLFW ticket: glfw/glfw#1019

@svenevs
Copy link
Collaborator

svenevs commented May 6, 2019

Hey @rogerdahl thanks for letting us know, this looks promising. I've been slowly iterating on updating nanogui's build system for modern cmake, it'll be a one-two punch. We've updated every other dependency, but the GLFW stuff needs to be updated with / after the new cmake stuff. I'm getting much closer.

Would you be interested in taking charge of (after we update GLFW / enable external GLFW) putting together a PR to use the new GLFW method? I'll be able to help test hidpi on all three major platforms. If so, I'll CC you on the PR that will enable it. It will be a pretty big PR that we'll need lots of user testing for, but it'll give you a base that you can work from independently.

@rogerdahl
Copy link
Author

@svenevs Sounds good. I'd be happy to help. Just give me a ping when the PR is ready. I can also help with testing on Linux and Windows.

@svenevs
Copy link
Collaborator

svenevs commented May 7, 2019

Awesome, thanks! I'm hoping to finish it this weekend assuming everything goes according to plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants