Original text is available at zhuanlan.zhihu.com/p/687939663
自从wayland合成器成功接管android合成器后,一直尝试处理上次未解决的问题:3D硬件加速。
缺失的 msm_drm 驱动
在处理硬件加速之前,先试着在平板上运行了一下 wayland(labwc),毕竟平板比手机更适合跑桌面软件。
显示输出很成功,但触摸有问题,光标反映的位置和实际点的位置旋转了90度,好在 labwc(wlroots) 使用的输入框架是 libinput ,可以调用 libinput_device_config_calibration_set_matrix 函数来对触摸输入执行旋转。
注释如下
c
* Apply the 3x3 transformation matrix to absolute device coordinates. This
* matrix has no effect on relative events.
*
* Given a 6-element array [a, b, c, d, e, f], the matrix is applied as
* @code
* [ a b c ] [ x ]
* [ d e f ] * [ y ]
* [ 0 0 1 ] [ 1 ]
* @endcode
.....
* Note that any rotation requires an additional translation component to
* translate the rotated coordinates back into the original device space.
* The rotation matrixes for 90, 180 and 270 degrees clockwise are:
* @code
* 90 deg cw: 180 deg cw: 270 deg cw:
* [ 0 -1 1] [ -1 0 1] [ 0 1 0 ]
* [ 1 0 0] [ 0 -1 1] [ -1 0 1 ]
* [ 0 0 1] [ 0 0 1] [ 0 0 1 ]
* @endcode
但labwc没有对应的配置文件,所以添加了这个功能放在了 GitHub 上。(更新: 已合并至上游)
好不容易处理好触摸,实际使用后发现用CPU渲染的性能太糟糕了,甚至不如容器内跑 vncserver,去网上找了半天的 msm_drm 驱动,翻看了几个手机的开源内核,发现高通的 msm_drm 内核模块以及用户空间的 adreno 驱动都只提供了二进制文件。(都是安卓HAL干的好事qaq...)所以只能先休息了。
回过头来只能寄托在开源 mesa 驱动上了。
上面缺失的 drm 驱动文件,查看源码可知,可以使用 MESA_LOADER_DRIVER_OVERRIDE 环境变量强制指定同目录下别的文件
用来选择drm驱动的函数
同目录下别的驱动文件
shell
SnowNF@localhost:~$ ls /usr/lib/aarch64-linux-gnu/dri/
armada-drm_dri.so imx-drm_dri.so mediatek_dri.so r600_dri.so sun4i-drm_dri.so
d3d12_dri.so imx-lcdif_dri.so meson_dri.so r600_drv_video.so swrast_dri.so
d3d12_drv_video.so ingenic-drm_dri.so mi0283qt_dri.so radeonsi_dri.so tegra_dri.so
etnaviv_dri.so kgsl_dri.so msm_dri.so radeonsi_drv_video.so v3d_dri.so
exynos_dri.so kirin_dri.so mxsfb-drm_dri.so rcar-du_dri.so vc4_dri.so
hdlcd_dri.so kms_swrast_dri.so nouveau_dri.so repaper_dri.so virtio_gpu_dri.so
hx8357d_dri.so komeda_dri.so nouveau_drv_video.so rockchip_dri.so virtio_gpu_drv_video.so
ili9225_dri.so lima_dri.so panfrost_dri.so st7586_dri.so vmwgfx_dri.so
ili9341_dri.so mali-dp_dri.so pl111_dri.so st7735r_dri.so zink_dri.so
imx-dcss_dri.so mcde_dri.so r300_dri.so stm_dri.so
可以尝试一下 kgsl 与 msm ,他们和高通CPU关系比较大,都是主线linux上内核暴露给用户空间的接口,一个是 /dev/kgsl-3d0 还有一个是 /dev/dri/card0
不出意外,都失败了
本来想看看具体是哪里创建失败了,但想起以前看 gallium、dri、egl 和 glx 的噩梦,还是算了。
既然 wlroots 用 gles2 不行,那么 vulkan 怎么样?毕竟 vulkan 驱动很'薄', 我也用 vulkan 写过一些程序,对他也比较熟悉。
mesa 上实现高通的 vulkan 驱动叫做 turnip,他支持 kgsl 与 msm 两种模式
mesa的meson_options.txt配置文件
使用 kgsl 模式,会因为不支持 VK_EXT_physical_device_drm 扩展而被丢弃,这个很正常,毕竟 kgsl 和 drm(/dev/dri/card0)没有多大关系
shell
SnowNF@localhost:~$ WLR_RENDERER=vulkan labwc -d
00:00:00.000 [../src/main.c:139] using config dir (/home/SnowNF/.config/labwc)
00:00:00.000 [../src/config/rcxml.c:1418] cannot read (/home/SnowNF/.config/labwc/rc.xml)
00:00:00.000 [../src/config/rcxml.c:1237] load default key bindings
00:00:00.000 [../src/config/rcxml.c:1242] load default mouse bindings
00:00:00.000 [../src/config/rcxml.c:1137] Loaded 32 merged mousebinds
00:00:00.000 [../src/config/rcxml.c:1284] load default window switcher fields
00:00:00.000 [../src/main.c:153] LABWC_PID=31120
.....
00:00:00.155 [render/vulkan/vulkan.c:111] Vulkan instance extension VK_LUNARG_direct_driver_loading v1
00:00:00.262 [render/vulkan/vulkan.c:258] Vulkan device: 'Turnip Adreno (TM) 725'
00:00:00.262 [render/vulkan/vulkan.c:259] Device type: 'integrated'
00:00:00.262 [render/vulkan/vulkan.c:260] Supported API version: 1.3.274
00:00:00.262 [render/vulkan/vulkan.c:261] Driver version: 24.0.2
00:00:00.262 [render/vulkan/vulkan.c:349] Driver name: turnip Mesa driver (Mesa 24.0.2)
00:00:00.262 [render/vulkan/vulkan.c:353] Ignoring physical device "Turnip Adreno (TM) 725": VK_EXT_physical_device_drm not supported
使用 msm 模式,会因为 drm 设备不兼容而自我丢弃 (不会出现在vkEnumeratePhysicalDevices中) 在这里,使用了 TU_DEBUG=all 显示全部日志才能看见丢弃信息。
shell
SnowNF@localhost:~$ TU_DEBUG=all WLR_RENDERER=vulkan labwc -d
00:00:00.000 [../src/main.c:139] using config dir (/home/SnowNF/.config/labwc)
00:00:00.000 [../src/config/rcxml.c:1418] cannot read (/home/SnowNF/.config/labwc/rc.xml)
00:00:00.000 [../src/config/rcxml.c:1237] load default key bindings
00:00:00.000 [../src/config/rcxml.c:1242] load default mouse bindings
00:00:00.000 [../src/config/rcxml.c:1137] Loaded 32 merged mousebinds
00:00:00.000 [../src/config/rcxml.c:1284] load default window switcher fields
00:00:00.000 [../src/main.c:153] LABWC_PID=26567
.....
00:00:00.069 [render/vulkan/vulkan.c:111] Vulkan instance extension VK_KHR_portability_enumeration v1
00:00:00.069 [render/vulkan/vulkan.c:111] Vulkan instance extension VK_LUNARG_direct_driver_loading v1
TU: info: TU_DEBUG=0x3fffffb
TU: info: Created an instance
TU: info: TU_DEBUG=0x3fffffb
TU: info: Created an instance
TU: error: ../src/freedreno/vulkan/tu_knl.cc:232: device /dev/dri/renderD128 (msm_drm) is not compatible with turnip (VK_ERROR_INCOMPATIBLE_DRIVER)
TU: error: ../src/freedreno/vulkan/tu_knl.cc:232: device /dev/dri/renderD128 (msm_drm) is not compatible with turnip (VK_ERROR_INCOMPATIBLE_DRIVER)
00:00:00.103 [render/vulkan/vulkan.c:258] Vulkan device: 'llvmpipe (LLVM 17.0.6, 128 bits)'
所以为什么会不兼容呢,翻看源码可知,drm 节点名称对不上
这里判断的是 msm 而不是 msm_drm
按照这种方法修改几次后,还是不能运行,几乎都失败在 drmCommandWriteRead 函数上,而他操作的对象,则是在这里打开的 fd
获取方式为 drm_device->nodes[DRM_NODE_RENDER] 设备上的路径为 /dev/dri/renderD128
为什么就他会失败呢? 观察源代码发现,基本上都是用它来发送渲染命令,再结合 DRM_NODE_RENDER 这个名字可以推测,这个 dri/renderD128 是专门用来 渲染 的,而 dri/card0 是用来 输出呈现 的。依据这个猜测,分别在 GNU/Linux 和 Android 上进行了测验
shell
$ sudo lsof /dev/dri/card0
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root 191u CHR 226,0 0t0 356 /dev/dri/card0
Xorg 1206 root 19u CHR 226,0 0t0 356 /dev/dri/card0
Xorg 1206 root 20u CHR 226,0 0t0 356 /dev/dri/card0
kwin_wayl 1531 snownf 18u CHR 226,0 0t0 356 /dev/dri/card0
.....
$ sudo lsof /dev/dri/renderD128
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
Xwayland 1565 snownf 12u CHR 226,128 0t0 355 /dev/dri/renderD128
Xwayland 1565 snownf 14u CHR 226,128 0t0 355 /dev/dri/renderD128
ksmserver 1586 snownf 9u CHR 226,128 0t0 355 /dev/dri/renderD128
kded5 1588 snownf 16u CHR 226,128 0t0 355 /dev/dri/renderD128
kded5 1588 snownf 17u CHR 226,128 0t0 355 /dev/dri/renderD128
fcitx5-pl 1603 snownf 15u CHR 226,128 0t0 355 /dev/dri/renderD128
plasmashe 1658 snownf 17u CHR 226,128 0t0 355 /dev/dri/renderD128
krunner 2285 snownf 17u CHR 226,128 0t0 355 /dev/dri/renderD128
java 2416 snownf 101u CHR 226,128 0t0 355 /dev/dri/renderD128
konsole 3312 snownf 13u CHR 226,128 0t0 355 /dev/dri/renderD128
qq 3709 snownf 24u CHR 226,128 0t0 355 /dev/dri/renderD128
.....
可见 GNU/Linux 上使用 card0 进行呈现(被wayland合成器、Xorg等占用),使用 renderD128 进行渲染。
shell
diting:/ # lsof /dev/dri/card0
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
composer-servic 1306 system 9u CHR 226,0 0t0 1449 /dev/dri/card0
composer-servic 1306 system 17u CHR 226,0 0t0 1449 /dev/dri/card0
composer-servic 1306 system 20u CHR 226,0 0t0 1449 /dev/dri/card0
composer-servic 1306 system 21u CHR 226,0 0t0 1449 /dev/dri/card0
composer-servic 1306 system 22u CHR 226,0 0t0 1449 /dev/dri/card0
composer-servic 1306 system 24u CHR 226,0 0t0 1449 /dev/dri/card0
.....
diting:/ # lsof /dev/dri/renderD128
diting:/ # lsof /dev/kgsl-3d0
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
surfaceflinger 1478 system mem CHR 505,0 1315 /dev/kgsl-3d0
surfaceflinger 1478 system mem CHR 505,0 1315 /dev/kgsl-3d0
.....
而 Android 则使用 card0 进行呈现,使用 kgsl 执行渲染。
那么只要一些修改,把 renderD128 的操作改到 kgsl 上就可以了,修改后的 mesa 代码也放到了 GitHub 上
然后编译安装修改过的mesa
shell
SnowNF@localhost:~/source$ git clone https://github.com/snownf/mesa
SnowNF@localhost:~/source$ cd mesa
SnowNF@localhost:~/source/mesa$ meson setup build
......
SnowNF@localhost:~/source/mesa$ cd build
SnowNF@localhost:~/source/mesa/build$ ninja
SnowNF@localhost:~/source/mesa/build$ ninja install
[1/2] Installing files.
Installing subprojects/lua-5.4.6/liblua.so.5.4.6 to /usr/local/lib/aarch64-linux-gnu
Installation failed due to insufficient permissions.
Attempt to use /usr/bin/sudo to gain elevated privileges? [y/n] y
Installing subprojects/lua-5.4.6/liblua.so.5.4.6 to /usr/local/lib/aarch64-linux-gnu
Installing src/freedreno/vulkan/libvulkan_freedreno.so to /usr/local/lib/aarch64-linux-gnu
Installing src/freedreno/vulkan/freedreno_icd.aarch64.json to /usr/local/share/vulkan/icd.d
Installing /home/SnowNF/source/mesa/src/util/00-mesa-defaults.conf to /usr/local/share/drirc.d
Installing symlink pointing to liblua.so.5.4.6 to /usr/local/lib/aarch64-linux-gnu/liblua.so.5.4
Installing symlink pointing to liblua.so.5.4 to /usr/local/lib/aarch64-linux-gnu/liblua.so
然后使用vulkan后端渲染
shell
SnowNF@localhost:~$ export WLR_RENDERER=vulkan
SnowNF@localhost:~$ labwc -d
00:00:00.000 [../src/config/session.c:71] read environment file /etc/xdg/labwc/environment
00:00:00.000 [../src/config/tablet.c:93] Adding button map for 0x140 with 0x110
00:00:00.000 [../src/config/tablet.c:93] Adding button map for 0x14b with 0x111
00:00:00.000 [../src/config/tablet.c:93] Adding button map for 0x14c with 0x112
.....
00:00:00.039 [render/vulkan/vulkan.c:117] Vulkan instance extension VK_KHR_portability_enumeration v1
00:00:00.039 [render/vulkan/vulkan.c:117] Vulkan instance extension VK_LUNARG_direct_driver_loading v1
00:00:00.050 [render/vulkan/vulkan.c:253] Vulkan device: 'Turnip Adreno (TM) 650'
00:00:00.050 [render/vulkan/vulkan.c:254] Device type: 'integrated'
00:00:00.050 [render/vulkan/vulkan.c:255] Supported API version: 1.3.277
00:00:00.050 [render/vulkan/vulkan.c:256] Driver version: 24.0.99
00:00:00.050 [render/vulkan/vulkan.c:344] Driver name: turnip Mesa driver (Mesa 24.1.0-devel (git-cbd7bacb6a))
00:00:00.050 [render/vulkan/vulkan.c:358] Found matching Vulkan physical device: Turnip Adreno (TM) 650
00:00:00.050 [render/vulkan/vulkan.c:443] Vulkan device extension VK_KHR_16bit_storage v1
.....
最后合成器就有了基于 vulkan 的硬件加速
mc 与 sfwbar
chromium、wlroots 与 wvkbd-mobintl
此时 /dev/dri/card0 /dev/dri/renderD128 /dev/kgsl-3d0 均有容器内软件在使用
shell
olivine:/ # lsof /dev/dri/card0
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
seatd 9262 root 7u CHR 226,0 0t0 13486 /data/data/com.termux/files/home/debian/dev/dri/card0
labwc 9481 system 13u CHR 226,0 0t0 13486 /data/data/com.termux/files/home/debian/dev/dri/card0
labwc 9481 system 14u CHR 226,0 0t0 13486 /data/data/com.termux/files/home/debian/dev/dri/card0
composer-servic 9483 system 6u CHR 226,0 0t0 13486 /dev/dri/card0
composer-servic 9483 system 8u CHR 226,0 0t0 13486 /dev/dri/card0
composer-servic 9483 system 9u CHR 226,0 0t0 13486 /dev/dri/card0
composer-servic 9483 system 10u CHR 226,0 0t0 13486 /dev/dri/card0
.....
olivine:/ # lsof /dev/dri/renderD128
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
labwc 9481 system 16u CHR 226,128 0t0 1625 /data/data/com.termux/files/home/debian/dev/dri/renderD128
labwc 9481 system 18u CHR 226,128 0t0 1625 /data/data/com.termux/files/home/debian/dev/dri/renderD128
labwc 9481 system 19u CHR 226,128 0t0 1625 /data/data/com.termux/files/home/debian/dev/dri/renderD128
labwc 9481 system 21u CHR 226,128 0t0 1625 /data/data/com.termux/files/home/debian/dev/dri/renderD128
labwc 9481 system 22u CHR 226,128 0t0 1625 /data/data/com.termux/files/home/debian/dev/dri/renderD128
olivine:/ # lsof /dev/kgsl-3d0
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
labwc 9481 system mem CHR 506,0 5371 /data/data/com.termux/files/home/debian/dev/kgsl-3d0
labwc 9481 system mem CHR 506,0 5371 /data/data/com.termux/files/home/debian/dev/kgsl-3d0
labwc 9481 system mem CHR 506,0 5371 /data/data/com.termux/files/home/debian/dev/kgsl-3d0
labwc 9481 system mem CHR 506,0 5371 /data/data/com.termux/files/home/debian/dev/kgsl-3d0
labwc 9481 system mem CHR 506,0 5371 /data/data/com.termux/files/home/debian/dev/kgsl-3d0
labwc 9481 system mem CHR 506,0 5371 /data/data/com.termux/files/home/debian/dev/kgsl-3d0
labwc 9481 system mem CHR 506,0 5371 /data/data/com.termux/files/home/debian/dev/kgsl-3d0
......
用起来比之前流畅多了,但还是没有高刷就很遗憾。具体来说,这个平板 (骁龙870) wlroots 从 DRM 获取到的刷新率本身就没有高刷,而较新的手机 (骁龙8+Gen1) 就正常。
后记:
vulkan + wayland 的组合下,turnip 驱动暂不支持 "kgsl_bo_export_dmabuf" 特性,使得渲染的每一帧都要"复制"到 vulkan 缓冲区,无法直接映射,相比于现代 wayland 直接映射还是差那么一点。
shell
SnowNF@localhost:~$ TU_DEBUG=all vkcube-wayland
TU: info: TU_DEBUG=0x3fffffb
TU: info: Created an instance
TU: info: TU_DEBUG=0x3fffffb
TU: info: Created an instance
TU: error: ../src/freedreno/vulkan/tu_knl.cc:232: device /dev/dri/renderD128 (msm_drm) is not compatible with turnip (VK_ERROR_INCOMPATIBLE_DRIVER)
TU_MOD: info: select kgsl drm
TU: info: Found compatible device '/dev/dri/renderD128' (msm_drm).
Selected GPU 0: Turnip Adreno (TM) 650, type: IntegratedGpu
PIPE_FORMAT_B8G8R8A8_UNORM: 500x500x1@4x1: 0: stride=2048, size=1048576, 8192, aligned_height=512, offset=0x2000,0x0, layersz 1048576, 8192 UBWC
TU: error: ../src/freedreno/vulkan/tu_knl_kgsl_drm.cc:176: FINISHME: stub kgsl_bo_export_dmabuf
vkcube-wayland: ./cube/cube.c:1369: demo_prepare_buffers: Assertion `!err' failed.
已放弃
临时的解决方案便是使用 MESA_VK_WSI_DEBUG=sw 环境变量进行复制缓冲区再展示, vulkan + x11 也只能这么做
shell
SnowNF@localhost:~$ MESA_VK_WSI_DEBUG=sw TU_DEBUG=startup vkcube-wayland
TU: info: TU_DEBUG=0x1
TU: info: Created an instance
TU: info: TU_DEBUG=0x1
TU: info: Created an instance
TU: error: ../src/freedreno/vulkan/tu_knl.cc:232: device /dev/dri/renderD128 (msm_drm) is not compatible with turnip (VK_ERROR_INCOMPATIBLE_DRIVER)
TU_MOD: info: select kgsl drm
TU: info: Found compatible device '/dev/dri/renderD128' (msm_drm).
Selected GPU 0: Turnip Adreno (TM) 650, type: IntegratedGpu
^C
SnowNF@localhost:~$ MESA_VK_WSI_DEBUG=sw TU_DEBUG=startup vkcube
TU: info: TU_DEBUG=0x1
TU: info: Created an instance
TU: info: TU_DEBUG=0x1
TU: info: Created an instance
TU: error: ../src/freedreno/vulkan/tu_knl.cc:232: device /dev/dri/renderD128 (msm_drm) is not compatible with turnip (VK_ERROR_INCOMPATIBLE_DRIVER)
TU_MOD: info: select kgsl drm
TU: info: Found compatible device '/dev/dri/renderD128' (msm_drm).
Selected GPU 0: Turnip Adreno (TM) 650, type: IntegratedGpu
^C
对于纯软件渲染来说,都不是问题
shell
SnowNF@localhost:~$ VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.aarch64.json vkcube
Selected GPU 0: llvmpipe (LLVM 17.0.6, 128 bits), type: Cpu
^C
SnowNF@localhost:~$ VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.aarch64.json vkcube-wayland
Selected GPU 0: llvmpipe (LLVM 17.0.6, 128 bits), type: Cpu
^C
所以有时候软件渲染(llvmpipe)会比使用 turnip 渲染更快
OpenGL + EGL + Wayland 的情况下,会使用 zink , 但因为 turnip 缺少 dmabuf 特性,无法运行。
缺少 kgsl_bo_export_dmabuf 以及 FINISHME
那么尝试软件渲染呢?
环境变量联合施法后,仍然无法运行,但屏幕上确实显示了一帧
OpenGL + EGL/GLX +Xwayland 可以使用 zink + turnip + 环境变量施法,让他支持硬件加速渲染
MESA_VK_WSI_DEBUG=sw 用来创建vulkan界面,LIBGL_KOPPER_DRI2=true 用来让zink在DRI2环境下也能渲染
不过转这么多层,对于渲染任务较小的程序,远不如 llvmpipe/lavapipe 速度快。
总的来说,turnip 驱动完善后,是可以达到运行主线linux的性能,不过还要先解决部分旧设备无法高刷的问题。
后后记:
根据评论区提供的 termux 补丁,让 turnip 实现了 dmabuf, 对应修改后的 mesa 也放到了 main-termux 分支。
然后可以使用 zink 来运行 labwc, 并让他启动带有 DRI3 支持的 xwayland
shell
SnowNF@localhost:~$ WLR_RENDERER=gles2 MESA_LOADER_DRIVER_OVERRIDE=zink labwc -d # 这两个都可以
SnowNF@localhost:~$ WLR_RENDERER=vulkan MESA_LOADER_DRIVER_OVERRIDE=zink labwc -d # 但我更喜欢vulkan
00:00:00.000 [../src/config/session.c:54] read environment file /etc/xdg/labwc/environment
00:00:00.000 [../src/config/tablet.c:93] Adding button map for 0x140 with 0x110
00:00:00.000 [../src/config/tablet.c:93] Adding button map for 0x14b with 0x111
00:00:00.000 [../src/config/tablet.c:93] Adding button map for 0x14c with 0x112
00:00:00.000 [../src/config/rcxml.c:1600] read config file /home/SnowNF/.config/labwc/rc.xml
......
此时 wayland 程序可以使用 dmabuf 直接映射到 vulkan 缓冲区, xwayland 也有现代的 DRI3 加速。
vkcube(左) 与 vkcube-wayland, 均使用 --present_mode 1 并且都选择了 Turnip Adreno (TM) 650
eglgears_x11(左) 与 eglgears_wayland, 均使用 zink 和 Turnip Adreno (TM) 650, 其中 xwayland 默认开启了垂直同步,只能以60帧显示
现在的性能水平终于和现代 Linux 差不多了 (好累啊qwq)....