跳转到内容

Hyprland 绘图框架:一个进程包办合成和送屏,所有窗口都用 GPU 合成

结论:Hyprland 的一帧画面只经过两类进程:客户端把窗口画进 dma-buf,通过 Wayland 协议交给 Hyprland;Hyprland 用 GPU 把所有窗口合成到一块 buffer,再由进程内的 Aquamarine 直接提交给 KMS。 和 Android 相比,少了三样东西:

  • 没有 HWC:送屏由 Hyprland 自带的 Aquamarine 库完成,不是厂商写的 HAL;
  • 没有 gralloc:buffer 由 GPU 驱动自带的 GBM 分配;
  • 没有多 plane 分配:所有窗口都由 GPU 合成,只用主平面和光标平面。

本文对应 Omarchy 使用的 Hyprland 版本(2026-10 的主线)。函数级调用栈见 dev-notes 的《把手机上的 Android 换成 Omarchy》,本文只讲结构。

逻辑视图:客户端和 Hyprland 两个进程,靠 Wayland 协议传 buffer

Section titled “逻辑视图:客户端和 Hyprland 两个进程,靠 Wayland 协议传 buffer”

逻辑视图:Hyprland 绘图框架的进程、部件和内核接口

客户端进程(Qt、GTK 应用,Omarchy 的状态栏 Quickshell 也是):

  • 工具包收到 Hyprland 的 frame 回调后才画下一帧,相当于 Android 的 VSYNC-app。
  • GLES / Vulkan 驱动是 Linux 版 Mali DDK,等上游支持 G2 后可以换成 Mesa。它和 Android 版 DDK 的区别只在用户态:链接 glibc,窗口系统换成 Wayland 和 GBM。
  • EGL 的 Wayland 平台相当于 Android 的 BLASTBufferQueue:分配几块 buffer 轮流用,eglSwapBuffers 时把当前 buffer 交给 Hyprland。
  • linux-dmabuf 协议把 dma-buf 文件描述符、格式和 modifier 包成 wl_buffer。随后的 wl_surface.commit 相当于 Android 的 Transaction。

Hyprland 进程:合成器和送屏都在这一个进程里。

  • 协议处理:实现 xdg-shell(窗口)、linux-dmabuf(buffer)、drm-syncobj(显式同步)、presentation-time(上屏时间反馈)等 Wayland 协议。
  • 窗口管理:平铺布局、工作区、动画,并记录每帧的损伤区域。没有损伤就不画。
  • 渲染器(CHyprOpenGLImpl):把每个窗口的 dma-buf 导入成 GLES 纹理,按布局画进一块 GBM buffer。
  • Aquamarine:Hyprland 团队维护的后端库。它管三件事:
    • DRM 后端:做 atomic 提交;
    • GBM 分配器:分配合成用的 buffer;
    • 会话:经 libseat 向 logind 申请显示和输入设备,经 libinput 读输入事件。

系统服务 systemd-logind 把 /dev/dri/card0 和输入设备交给当前登录的会话。它相当于 Android 里 init 和 SELinux 给 SurfaceFlinger 的设备权限。

内核和 Android 共用:kbase 驱动 Mali,KMS 驱动控制显示,dma-buf 承载 buffer。差别是客户端的 fence 默认挂在 dma-buf 上(隐式同步),不跟着 buffer 显式传递。

进程视图:一帧画面走九步,跨两个进程

Section titled “进程视图:一帧画面走九步,跨两个进程”
sequenceDiagram
  participant C as 客户端
  participant GPU as Mali(DDK + kbase)
  participant H as Hyprland
  participant AQ as Aquamarine
  participant KMS as KMS 驱动
  H-->>C: ① frame 回调:可以画下一帧
  C->>GPU: ② eglSwapBuffers:提交渲染
  C->>H: ③ wl_surface.attach + commit(dma-buf)
  Note over H: ④ 从 dma-buf 取隐式 fence,等 GPU 画完
  H->>GPU: ⑤ GLES 合成所有窗口到 GBM buffer
  H->>AQ: ⑥ 提交这一帧
  AQ->>KMS: ⑦ atomic 提交:主平面 FB_ID,可带 IN_FENCE_FD
  KMS-->>AQ: ⑧ 翻页完成事件
  AQ-->>H: present 事件
  H-->>C: ⑨ presentation 反馈、wl_buffer.release
  1. 客户端先向 Hyprland 要一个 frame 回调,收到后才开始画。
  2. 客户端调用 eglSwapBuffers,DDK 把渲染任务经 kbase 交给 Mali。
  3. DDK 把 buffer 包成 wl_buffer,attach 加 commit 交给 Hyprland,不等 GPU 画完。
  4. Hyprland 从 dma-buf 导出隐式 fence,等它触发后才采用这次提交。
  5. 有损伤时,Hyprland 用 GLES 把所有窗口合成到一块 GBM buffer。这时它就给客户端发 frame 回调,不等真正上屏。
  6. 渲染器把合成好的 buffer 交给 Aquamarine。
  7. Aquamarine 组装 atomic 请求:主平面的 FB_ID 指向这块 buffer。显示驱动支持 syncobj 时,还会带上合成的 fence(IN_FENCE_FD)。
  8. 显示控制器在 VSYNC 切换画面,内核发出翻页完成事件。
  9. Hyprland 给客户端发上屏时间反馈;客户端旧 buffer 的引用计数归零后,发 wl_buffer.release 归还。

和 Android 的十步相比,少了“问 HWC 每层怎么合成”这一来一回,代价是第 ⑤ 步每帧都要用 GPU。

合成策略:只用主平面,全屏时可以跳过合成

Section titled “合成策略:只用主平面,全屏时可以跳过合成”

Hyprland 的 plane 用法比 HWC 简单得多:

场景做法plane 用量
普通桌面所有窗口用 GLES 合成到一块 buffer主平面 1 个
有鼠标指针指针单独放在光标平面再加 1 个
全屏单窗口,格式合适直接扫描输出:客户端 buffer 直接给主平面,跳过合成(默认关闭,render:direct_scanout)主平面 1 个

所以显示驱动只要把主平面做对就能出图,bring-up 门槛比 Android 低。代价是播视频、滚动这类场景多了一次整帧的 GPU 读写,功耗比 Android 高。详见两套框架对照。

对驱动的要求:显示驱动做标准 atomic,GPU 驱动提供 GBM 和 Wayland

Section titled “对驱动的要求:显示驱动做标准 atomic,GPU 驱动提供 GBM 和 Wayland”
部件必须提供缺了会怎样
KMS 显示驱动atomic、通用 plane、真实的 vblank 中断、PRIME 导入Aquamarine 放弃这块卡,没有输出
KMS 显示驱动DRIVER_SYNCOBJ_TIMELINE送屏退回隐式同步,仍能出图
Mali 用户态驱动GLES 3.0 以上、EGL_KHR_platform_gbm、EGL_ANDROID_native_fence_sync、dma-buf 导入扩展Hyprland 启动即退出
Mali 用户态驱动在显示控制器的 card0 上建 GBM,分配显示控制器读得了的 buffer合成结果无法上屏
kbaseGPU 写 dma-buf 时,把 fence 挂到 dma-buf 上画面撕裂、闪出半帧

完整的检查表和验证命令见 dev-notes 的《在 Mali G2-Ultra NX 的 SoC 上点亮 Omarchy》。