Physical vs logical DPI on X
Tor Arne Vestbø
Tor.arne.Vestbo at qt.io
Sun Sep 20 13:45:48 UTC 2020
Greetings!
We're currently implementing RandR 1.5 in Qt, and in that work popped up the age old question of DPI, so I thought I'd check with the horse's mouth, so to speak :)
I've read the very informative post by Alex on why EDID is not trustworthy [1], and other related materials, but I may have misunderstood things, so please correct me if I'm wrong!
Now, the question from Qt's side is what we should report from QScreen's physicalDpi() and logicalDpi() on X [2]. The former being the actual DPI of the glass/panel, and the latter a "virtual DPI", affected by OS decisions such as multiplying the physical DPI with 1.3 to account for viewing distance, rounding it to common fixed values, or based on user preference.
For reference, macOS has a fixed logical DPI of 72, whereas Windows allows the user to set the logical DPI in various (possibly fractional) increments of 96 DPI.
(Both OSes allow us to also get the physical DPI (however inaccurate it is from EDID), which we report as physicalDpi(), e.g. for users who want to draw graphics they can put a ruler up to their screen and compare against.)
Now, in the past this logical DPI was mostly relevant for how to draw text, but nowadays this is also how platforms like Windows deals with high-DPI panels. Setting a scale of 200% in the Windows display preferences results in Windows reporting 192DPI to us, which we in turn report to the user as a device independent coordinate system with a 96DPI and 2.0 device pixel ratio, resulting in scaled text, and UI.
Now, for X, there's at least four different things to consider, as far as I can tell:
1) The resolution and size of the X Screen
2) The resolution and size of the individual outputs
3) The resolution and size of the RandR 1.5 monitors
4) The Xft.DPI setting.
(For all the things exposed through RandR (1-3), as far as I can tell they are all stored as resolution and size (in mm), so all DPI-numbers going in or out of X are effectively converted to a width and height in mm to represent that DPI with the current resolution taken into account.)
The last one is the easy one, it's clearly a logical DPI, and we reflect that in Qt if set. Unfortunately it's a global DPI.
Now, I'm guessing that #1, as set by Xorg -dpi, xorg.conf DisplaySize, or xrandr --dpi, originally was meant as a physical DPI override, for cases where the detection and heuristics in X would fail? But nowadays, especially with a single X Screen representing multiple physical displays, with potentially different physical DPIs, it feels like it's effectively a logical DPI setting on an X level, with the same limitation as Xft.DPI in that it's a global setting. What is your take on this?
If it's the former — a physical DPI override (however little that makes sense when reflecting multiple displays) — we don't want to reflect it per QScreen, as that would not be specific enough in a multi monitor setup. Nor do we want to reflect it for a QScreen's logicalDpi, if it's strictly defined as a physical property, not to be used for adjusting logical DPI.
But if it's in practice the latter — a logical DPI override — then we should reflect it through a QScreen's logicalDpi, if Xft.DPI hasn't been set to override it.
Now, for #2, as far as I can tell there isn't any option in xrandr to override this, nor does tweaking DisplaySize in xorg.conf affect it (even for multiple Monitor sections), so I'm guessing it's strictly a physical size picked up from EDID? If that's not the case, and it's possible to override it for the user, then the same questions as for #1 apply: Does that make it a logical DPI?
Finally, for #3, this is where it gets interesting. From reading the RandR spec [3] about the new Monitors introduced in 1.5, this seems like a defined logical DPI:
"This new object separates the physical configuration of the hardware
from the logical subsets of the screen that applications should
consider as single viewable areas."
It's possible to combine two outputs into one monitor, to split a single output into multiple monitors, or even to override the auto-generated monitor for a an output. And all these allow you to pass a width and height, effectively setting the DPI. E.g.
xrandr --setmonitor DUMMY0-DPIOVERRIDE 1600/200x1200/200+0+0 DUMMY0
This seems like the definition of logical DPI, where the desktop environment can give the user a nice control panel on how to adjust these things, either directly by adding/removing/moving monitors, or by setting a DPI or scale (200% e.g.) on an individual monitor, and then reflect that as RandR updates.
Based on all of this, it seems Qt should do the following:
1. If Xft.DPI has been set, respect that as a global override, and reflect that as the logical DPI for all QScreens
2. If not, reflect the resolution and size of individual RandR 1.5 monitors as logical DPI per QScreen
3. If 1.5 is not available, reflect the resolution and size of the X Screen as a global logical DPI for all QScreens
4. Reflect the resolution and size of the individual outputs as physical DPI, or read EDID ourselves
As far as I can tell this should cover DEs like Ubuntu 20.04 that sets a global 192 Xft.DPI to represent 200% scaling (and fractional scales in between 100% and 200%), as well as DEs that (in the future) allow per-monitor DPI/scale control via the 1.5 monitors.
Thanks for reading this far! Let me know if I missed something, and what your thoughts are :)
Cheers,
Tor Arne
[1] https://lists.fedoraproject.org/pipermail/devel/2011-October/157671.html
[2] https://doc.qt.io/qt-5/qscreen.html#logicalDotsPerInch-prop
[3] https://gitlab.freedesktop.org/xorg/proto/xorgproto/-/raw/master/randrproto.txt
More information about the xorg-devel
mailing list