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”客户端进程(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
- 客户端先向 Hyprland 要一个 frame 回调,收到后才开始画。
- 客户端调用
eglSwapBuffers,DDK 把渲染任务经 kbase 交给 Mali。 - DDK 把 buffer 包成
wl_buffer,attach加commit交给 Hyprland,不等 GPU 画完。 - Hyprland 从 dma-buf 导出隐式 fence,等它触发后才采用这次提交。
- 有损伤时,Hyprland 用 GLES 把所有窗口合成到一块 GBM buffer。这时它就给客户端发 frame 回调,不等真正上屏。
- 渲染器把合成好的 buffer 交给 Aquamarine。
- Aquamarine 组装 atomic 请求:主平面的
FB_ID指向这块 buffer。显示驱动支持 syncobj 时,还会带上合成的 fence(IN_FENCE_FD)。 - 显示控制器在 VSYNC 切换画面,内核发出翻页完成事件。
- 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 | 合成结果无法上屏 |
| kbase | GPU 写 dma-buf 时,把 fence 挂到 dma-buf 上 | 画面撕裂、闪出半帧 |
完整的检查表和验证命令见 dev-notes 的《在 Mali G2-Ultra NX 的 SoC 上点亮 Omarchy》。