Linux 的 HiDPI 配置问题

其实这个问题对我来说从来也不是个问题,因为我已经基本深入理解了这个问题的本质……然而似乎有不少人对于怎么配置,为什么这么配置,为什么又出了问题一知半解,因为最近在 Twitter 上回复一些比较复杂的内容,特此整理记录一下。

在过去,电脑的屏幕一般都是 96 DPI,DPI 的含义是每英寸点数(像素点),具体计算的方法也非常简单,用像素的个数除以屏幕的长宽即可。随着技术发展(显示器和处理器),电脑逐渐可以支持更高的 DPI 用于显示。抛开具体发展不提,但是这里引入了一个问题,你程序并不支持在这样的屏幕上正常显示。通常来说的问题就是界面特别小。

为什么?

因为程序过去从来也不需要考虑这个问题,都是按照像素来进行的设计,例如显示一个 100 x 100 的窗口,在 96 DPI 上大小正常,但是 DPI 翻倍的话,就只有原来的 1/4 大。人的眼睛在乎的是它现实中有多大(像素 / DPI = 长度),而不是有多少像素点。为了让程序能够正常显示,同时又不需要修改太多代码,大家也是努力想了很多办法。

1、设置字体大小。

界面框架大小不提,字看不清那是最难受的,因此只调整字体大小这样的设置,成为了大部分人解决问题的第一步。但是,在 X 上,这个设置是全局的,因此也就不能针对不同的屏幕,来设置一个统一的 DPI。一个屏幕大一个屏幕小也可以说是常态了,不少人就选择设置一个居中的值来自己适应。毕竟人是活的,程序是死的,改变不了世界那就只好自己适应世界。

2、程序来支持内部缩放。

这里,当然也就是 Qt / Gtk 这样的大框架才有精力和能力去支持这种功能。这个功能在程序不需要进行额外修改,或者只进行少量修改的前提下,直接在内部添加坐标的转换功能,乘以一个倍数,变相将程序放大。这样,在高分辨率的前提下,就可以用正常的大小显示界面。

3、设置分辨率来进行缩放

也许有人说,显示器不都可以配置分辨率么?你3200×1800的屏幕,我强行设置 1600×900 不就完全正常大小了么?这样说当然是没有问题,但是相对的你也就没有利用上这个屏幕的资源,原本你可以在更高的像素点上进行更好的反锯齿。相当于说,你把大图片缩小再放大,你得到的能是原始图片的效果么?必然是有数据损失的。当然,还是那句话,人是活的,程序是死的。一句 I don’t care 什么问题都能解决。

上面说的分辨率缩放只是在配置当中其中的一个方向。相反,还有故意把 1600×900 伪装成 3200×1800 这种操作,这是为什么呢?

这是因为,尽管在 2 中你可以靠 Toolkit 来放大程序的界面,但是如果你同时有两个不一样的屏幕,那这可就倒了大霉了。一个屏幕 96 DPI 一个屏幕 289 DPI,那你的程序是选哪一个 DPI 来显示呢?

关于这点,其实甚至还没有到「选哪个」的问题,支持根据屏幕动态切换 DPI 的 Toolkit 在 Linux 上只有 Qt 这一家。Gtk 根本就不支持动态在运行时根据屏幕切换 DPI。那你既想要显示“锐利”,又想要大小正常,那就只有一个办法了。把这个 96 DPI 的屏幕也变相变成 HiDPI 的。尽管你没有 HiDPI 的屏幕,但是你可以通过软件「装作」你有这么一个屏幕。

xrandr --output eDP-1 --scale 2x2 --mode 1920x1080 --pos 0x0 --output DP-1-0 --mode 2560x1440 --pos 3840x0

上面这条命令,就是把 eDP-1 在软件层面变成 3840×2160 的屏幕,放在左边,尽管它原生只有 1920×1080。这样有两个 DPI 相近的屏幕,你自然就可以对按照 2 对程序设置统一的缩放倍率了。

4、将他们组合在一起

先来说说 X 到底是怎么回事。对 X 来说,它不知道窗口里面什么 HiDPI 不 HiDPI,你给我什么,我就显示什么。所有坐标在 X 层面都是原生的。也就是说,它没法把不支持 HiDPI 的程序放大,例如一些 Java 程序或者 Xlib 程序。那怎么办?如果你非要一切程序都正常大小,那就只能走暴殄天物的方案 3 ,把分辨率人工降下来。

还是再重复一次,「人是活的,代码是死的」。你当然可以选择「那些不支持 HiDPI 的程序都是辣鸡,我不用了!」。为了享受「锐利」,你选择了只用 Gtk / Qt 的程序,其他程序如果能用调整程序内部字体凑合就凑合一下,凑合不了干脆不用,反正 Gtk / Qt 的选择那么多。

那么在你选择这条路的时候,首先就来了解一下 Gtk 和 Qt 设定上的不同。Gtk 的 scale factor 是影响字体大小的,而 Qt 的是不影响字体大小的。

通过下图,你可以很容易对比理解 Qt 和字体 DPI 组合的行为。

https://pbs.twimg.com/media/EvcWJYPVIAEo0x_?format=jpg&name=orig

然后再来对比一下 Gtk 的行为。

图像

也就是说,如果你想 Qt 和 Gtk 同时达到两倍缩放,并且大小相当,需要设置:

1、Xft DPI  192
2、QT_SCREEN_SCALE_FACTORS=2
3、GDK_SCALE=2 GDK_DPI_SCALE=0.5

具体数值你可以根据你的屏幕自行修改。当然,这些数值具体的设置方式可以通过环境变量,又或者可以通过桌面环境的设置,但不管通过哪种手段,本质其实是一样的。

在这个设置的基础上,又可以分别分成两个路线,使用 3 的方法伪装低 DPI 的屏幕,又或者你连 Gtk 也一起放弃,采用 Qt 可以支持不同屏动态切换 DPI 的设置。

QT_SCREEN_SCALE_FACTORS="eDP-1=1;DP-1-0=2"

例如这样设置的话,就可以在 eDP-1 用 1 倍缩放,DP-1-0 用两倍缩放。尽管 Xft DPI 还是只能一个值,但是 Qt 会自动帮你绑定到某个屏幕,另一个屏幕还是采取包括字体大小在内的完全缩放,把窗口在不同屏幕移动的时候也会自动调整程序的大小。当然为什么只有 Qt 实现了这个我猜想主要是因为 Windows 上只有这一种方案可以总是保持原生状态渲染,就顺带着也支持了 Linux 。

如果说到 Wayland(目前已有的实现),本质上是一样类似的,但是参数实际上就没有这么多花活可以弄。Wayland 对于窗口可以有一个窗口本身的 scale 是多少的属性,当然,得是原生 Wayland 窗口,X wayland 的窗口 scale 会自动当成是 1。然后这个窗口会在不同的屏幕上,根据屏幕的 scale 进行匹配缩放。这也就是为什么 Wayland 可以支持所谓的「分数」缩放(非整数百分比,例如 150%)。其实 X 如果你把 xrandr 的 scale 改成带小数的倍数也是可以的。

其实 Qt 在 X 下,也是可以支持非整数的 scaleFactor。但是这里有一个问题。浮点数的计算是有精度损失的,所以如果你原生进行内部坐标非整数的运算,实际上更有可能出问题,在哪里界面上缺掉一像素也是常见的事,相比整数倍数运算之后再缩放来说的方案,问题会少一些。但是希望大家还没有忘记 Wayland 下也还有X wayland 需要显示。根据之前说的情况 X Wayland 和 X 并没有什么差别,只是 3 中描述的操作现在交给 wayland compositor 进行了。GNOME 默认的行为是不缩放 X,可以通过 gsettings 启用一个实验性功能来让 X 被「放大」。KDE 是默认采用设置的倍数放大。相对来说,反而和 X11 相比更加不「自由」,并不能分开指定 Wayland 和 X 的缩放倍率。

毕竟,对有些人来说,显示锐利本身比大小更重要。左侧是原生 Wayland,右侧是 X wayland。尽管大小一致,在 150% 缩放的情况下 X 的程序自然是模糊了(从 100% 放大到 150 %)。而 Wayland 这个则是从 200% 缩小到 150%(当然,也许用 300 % 绘制再缩小 150 % 效果可能更好?但具体来说现在系统实现就是这样的)

https://pbs.twimg.com/media/EvcdZv1VIAQ4lkM?format=jpg&name=orig

整个 HiDPI 问题就是那么一个问题,怎么把原本针对小屏幕设计的窗口放到高分辨率上显示,和 X 还是 Wayland 有什么相关么?没有,只是在不同平台你有相对不同的变量可以操纵。有没有完美的解决方案?当然也是没有。你偏要把一个 100×100 的窗口当成 200×200 显示,窗口不支持缩放当然只能模糊。窗口支持缩放却又不支持动态切换,混合分辨率的在低分辨率上那就只能浪费资源来缩放,这本来就是两难的问题。真正完美的纯动态切换现在也就只有 X11 + Qt,但大部分人也不是靠纯 Qt 程序不是?非要概括的话,就是有钱能解决大部分问题,有钱就买俩 HiDPI 屏幕,有钱就硬件强力,浪费资源缩放也无所谓。

This entry was posted in Linux and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.