[haiku-development] HiDPI strategies, current and future

  • From: "Mr. waddlesplash" <waddlesplash@xxxxxxxxx>
  • To: haiku-development@xxxxxxxxxxxxx
  • Date: Mon, 30 Aug 2021 17:06:46 -0400

Hello,

Recently, there was a rather long discussion that got to be very
technical on the forums about how HiDPI "scaling" is accomplished at
the Interface Kit level. If you have some time and would like to read
it, you can here: [1], but it should not be necessary to understand
this email, I just mention it because a lot of what I am going to
write or suggest here came from thinking about things due to or from
that.

THE PROBLEMS
========================================
Our current HiDPI strategy is currently based purely around font
sizes. That is, if you set a larger font size in Appearance settings,
the whole UI gets larger.

This is slightly annoying because you have to set a larger font size 4
times ... but that could have a relatively "simple" fix (even if it
requires a bikeshed argument.) However, the larger problem is that it
already creates slightly complicated logic in the Interface Kit: if
you want to have something be a minimum size even with a smaller font
size, you have to now add min() calls in various places. Or,
occasionally there are metrics that are not immediately tied to the
font size, and so there are places where a proper factor is computed
by computing "font size / 12", which is not ideal (though indeed some
places that do this could and should base their computations off of
BControlLook metrics functions instead, I intend to work on that one
way or another.)

It is also noteworthy that the font sizes we specify are in points,
which are theoretically supposed to be of a fixed physical size, and
so it is technically incorrect that we set 18pt font sizes when really
we mean that we are viewing these fonts on a display with a density
150% of normal.

The biggest problem, though, is that this is all going to get
exponentially more complicated when we switch to supporting multiple
monitors of varying DPIs. (Notably, most applications and toolkits do
rather poorly with this at present on other OSes, and we should avoid
their pitfalls. For that matter, most software does poorly with
fractional, non-integer scaling factors -- macOS does especially badly
with them, it basically resorts to software scaling -- this is an area
where we already do much better.)

When we have multiple monitors of varying DPIs, no longer will
"be_plain_font->Size" make any sense at all. This becomes a problem
because this is how we presently determine what the default scale is
(BViews may of course have different fonts set for drawing purposes
and that cannot be relied on.) Probably we will have to introduce a
BScreen::DefaultFont(...) method, or something like it, and also a
BWindow::Screen() method or something like it to handle this
indirection.

Ultimately, though, that is going to create a lot more code given the
current state of things, and I think there are some significant
changes we can make now to make coding for HiDPI a lot easier.

WHY WE GOT HERE
=========================================
Scaling the whole UI around the font size was picked because it was
the "path of least resistance." It seems that tests with larger font
sizes began almost immediately after the introduction of Layouts in
the Interface Kit -- in 2007! [2] Ultimately major work to really
scale everything only picked up in 2016-2018, though.

Beyond being the "path of least resistance", though, there are a lot
of very good reasons why the UI scaling factor should be tightly
coupled to the font size. For one, most of the UI is driven by text
one way or another, and so computing the rest of the metrics (text
margins, etc.) based on the font size just makes sense. Even UI
elements which "seem" unrelated to the font size (scroll bars) wind up
being related (e.g. Tracker's status view is the same height as the
scroll bars and contains text.)

Most other scaling systems adopted by other OSes or toolkits split
their pixel system into "device pixels" and "device independent
pixels", with applications operating in the second unless somehow they
request the first, and then everything is translated before rendering.
This was probably intended so that most code could continue operating
as if it was drawing things at precisely the same number of pixels as
before and let the painting system translate everything. Unfortunately
it has some bad corner cases even in the ideal case of integer
scaling, and at fractional "device pixel ratios" of 1.5 or 1.75, it is
often pretty bad (Qt applications still do not handle fractional
scales well, if at all.) It also creates massive API usage headaches
if you ever want to operate in device pixels instead of device
independent pixels, with coordinate and API call mixup footguns being
very common.

Clearly, coupling the "scale factor" tightly to the font size, and
having most things compute UI metrics based on the font size was a
good idea. So, how can we have the "best of both worlds" where we have
comparatively few computations outside of the Interface Kit itself,
without resorting to hiding device pixels from programmers by default?

WHERE TO GO FROM HERE
=========================================
Returning to the question of font size and pixels: I propose that we
split the pixel size of a font from its point size. Beyond making
basic sense given the definition of "points," I note that FreeType
itself has a mechanism to account for "DPI" directly in the font size
instantiation routine, which we currently hardcode to 72 [3].

Now, it is generally hard to determine display DPI correctly, and
users probably will want scales that do not precisely match their
display DPI, and DPI itself is not a very fun unit to work with, so I
am not suggesting we present DPIs to the user or even have it as a
parameter to BFont. Instead I suggest we have a "density" value which
is functionally similar to what other OSes call "device pixel ratio",
but instead is tightly coupled to the font size.

So, in other words, the hardcoding of 72 at [3] would be changed to be
multiplied by the "density" factor, and BFont would have
PixelDensity()/SetPixelDensity() methods (or whatever we decide to
name them.) Most things that compute font sizes could completely drop
the be_plain_font->Size() usage, and instead set a point size however
they used to and let the density take care of the rest. (Actually this
would probably mean more applications would work without any specific
scaling code, so long as they already computed everything based on
font pixel metrics, because now they would not have to worry about
scaling the sizes of secondary fonts.)

Further, supporting multiple displays with multiple DPIs now gets much
easier. Applications could just instantiate a BFont with an unset
default density (-1), and then when setting the font on a BView, the
view assigns whatever density it is supposed to have based on the
current screen's density.

When the density changes, whether because of user action or because of
moving to a new screen with a different density, so long as the
application has not set any explicit densities, the views can then
automatically update their BFont densities and the UI will scale
without any code required on the programmer's part in the general
case. (Applications that need to handle metrics specially could catch
B_FONTS_UPDATED and do calculations, of course.)

be_plain_font->Size() will still make sense (though the
metrics-calculating routines of be_plain_font will not) as font sizes
will now remain the same across displays, and only the density will
change. Further, users will be able to edit density on a per-screen
basis, instead of changing all fonts.

Programmers will still be able to override things by explicitly
setting densities of fonts and view fonts accordingly; but in the
general case, they will have even less to worry about than they do in
the current model. Further, the "density" value substitutes as a
scaling factor for whatever applications want to scale things that way
instead of using the font metrics (probably ported applications most
specifically.)

QUESTIONS, ETC.
=========================================
So far as I can determine, this proposed system has all the advantages
of the current model, few if any new downsides, and a whole lot more
advantages, especially regarding multiple-screen multiple-DPI support.

I will of course be the one doing all or nearly all of the work here,
as the first "major project" undertaken as part of the development
contract (though I expect once we complete technical and bikeshed
discussions, this is a few weeks of work at most, including the work
to scale whatever UI elements are not as yet handled across the
system.)

-waddlesplash

[1]: 
https://discuss.haiku-os.org/t/how-to-scale-the-ui-properly-with-high-dpi-screens/11086
[2]: https://www.freelists.org/post/haiku-development/18pt-font-GUI-test
[3]: 
https://github.com/haiku/haiku/blob/master/src/servers/app/ServerFont.cpp#L362

Other related posts: