【免费下载链接】denial
A smooth, customizable Wayland compositor with glass effects, three window layouts, and settings you can actually click. We got a little carried away.
Denial 是一款 Flutter 原生的 Wayland 合成器,它通过内置的 Volition 原子 DRM/KMS 呈现引擎实现零撕裂的帧同步:无论单屏还是多屏混刷,每一帧画面都会在显示器垂直消隐(vblank)边界被原子地整体替换。本文将用通俗的方式解释 Denial 为什么"丝滑不撕裂",以及它的多输出帧同步机制是如何工作的。
1. 先搞懂"撕裂":画面为什么会裂开?
显示器是逐行"扫描"刷新的:电子枪(或扫描电路)从屏幕顶部一行一行画到底部,画完一整帧后进入垂直消隐(vblank),然后开始下一帧。
传统做法存在一个风险:如果合成器在显示器画到一半时往显存里塞新画面,屏幕上半部分显示旧帧、下半部分显示新帧——这就是"撕裂"。
DRM/KMS(Linux 内核管理显示硬件的子系统)对此的解法是原子提交(atomic commit)+ 页面翻转(page flip):
- 合成器一次性提交一整组新的显示状态(哪块 framebuffer 上屏、裁剪区域、旋转等);
- 内核保证这些属性要么在某个 vblank 边界全部生效,要么完全不生效;
- 绝不会出现"半新半旧"的中间状态。
Denial 的呈现路径正是建立在原子提交之上,而真正决定"提交时机是否精确"的,是它自研的 Volition 模块。
2. Volition:永不阻塞的 KMS 呈现预调度器
Volition 是 Denial 源码树内的显示同步库,位于 compositor/src/volition/mod.rs。它的模块注释把它定义为"有序原子 KMS 呈现前瞻引擎(ordered atomic-KMS presentation lookahead)"。
职责划分非常清晰(这一点对理解整个架构很关键):
| 角色 | 负责什么 |
|---|---|
| 合成器(deniald) | 决定"呈现什么":选帧、持有 buffer 直到翻页完成、处理 Flutter/Wayland/DPMS/截屏策略 |
| Volition | 决定"何时提交":持有 DRM 文件描述符、复用原子 plane 请求、在截止时刻精确提交 |
它的核心设计可以用三个数字概括:
- 提前 2 毫秒提交(
LOOKAHEAD_SUBMIT_LEAD):许多 DRM 驱动的实际"锁存窗口"早于物理 vblank,所以 Volition 会提前一点把原子状态交给内核,让驱动在目标边界成功锁存。 - 100 微秒重试一次(
LOOKAHEAD_RETRY_INTERVAL):提交若被内核短暂拒绝(EBUSY/EAGAIN等),不原地死等,而是重新入队、快速重试。 - 最多等 100 毫秒(
LOOKAHEAD_MAX_WAIT):重试窗口有界。窗口内仍失败则上报Stalled,由合成器重建 KMS 状态,而不是让整个图形会话崩溃。
更妙的一点是:Volition 的所有 ioctl 都是非阻塞的(NONBLOCK标志 +PAGE_FLIP_EVENT)。它在一条名为volition-kms的独立线程上运行一个基于二叉堆的"截止时刻调度器":每个提交任务携带一个not_before时刻,线程只负责"逼近边界",绝不把主合成器线程卡在内核里等待。这带来两个直接收益:
- 关闭会话、热插拔显示器时,主循环不会被一次卡住的 ioctl 拖死;
- 每个输出流最多保留1 个在途提交(
MAX_IN_FLIGHT_COMMITS_PER_STREAM),队列满时返回Backpressured(背压),上游帧管线据此自然降速,而不需要任何忙等。
提交请求的入口是Volition::submit_for_target():合成器把"渲染已完成的这一代画面 + 目标显示时刻"交给 Volition,由它负责逼近边界并提交。相关的调用方实现在 compositor/src/bin/deniald/output_scheduler.rs。
3. 多输出帧同步:让 144Hz 与 60Hz 各自丝滑
多显示器场景有个经典难题:两块屏幕刷新率不同(比如 240Hz 主屏 + 60Hz 副屏),帧"节拍"各不相同。Denial 的答案在 compositor/src/bin/deniald/frame_scheduler.rs,它的模块注释称之为"混刷帧管线(mixed-refresh frame pipeline)":
- 每个通电输出都拥有自己独立的时间线,各自计算渲染截止时刻与呈现目标(presentation target);
- 最快的输出同时是整个 Dart 桌面的"唯一时钟":Flutter 只按这个主时钟渲染一次;
- 较慢的输出直接重放最近一次保留的画面投影,不额外触发渲染;
- 物理呈现从不授权渲染:屏幕翻页事件只用于对"同一个输出时间线的未来边界"做有界的相位校准,不会打乱节拍。
配合"一块桌面级 GBM 大画布(atlas)"的设计——每个 CRTC(扫描输出控制器)从同一块 framebuffer 里扫描各自不同的源矩形——多屏不再意味着逐屏拷贝,屏数增加也不引入合成开销。这正是 compositor/README.md 里描述的"one desktop-wide GBM atlas"。
每块屏幕的输出调度器(output_scheduler.rs)还内置了两道保险:
- 独立邮件箱(mailbox):每个 CRTC 各自的提交队列互不阻塞,一个屏忙不会拖住另一个屏;
- 2 秒呈现看门狗(
PRESENTATION_STALL_TIMEOUT):被接受的提交若迟迟不翻页,说明状态可能卡住,看门狗会触发恢复流程,而不是让画面"冻结"下去。
4. 出错时怎么办:重试、恢复、绝不黑屏
Volition 把内核错误分成了三类,每类都有明确归宿(见 compositor/src/volition/mod.rs 中的lookahead_failure_disposition):
| 错误类型 | 例子 | 处理方式 |
|---|---|---|
| 可重试 | EBUSY、EAGAIN、EINTR(内核忙) | 100µs 后重新入队重试,且允许其他输出的提交插队 |
| 可恢复 | EACCES、EINVAL、ENODEV等(链路重训、DRM 对象被替换、主权限交接) | 上报Stalled,合成器重建 KMS 状态——"重建显示状态是正确的边界,终止图形会话不是" |
| 致命 | 其余错误 | 上报Failed,交由会话层处置 |
注意重试的一个细节:重试是"重新入队"而不是"原地重试"。这样当某个输出正忙时,另一块屏已经到点的提交可以先走,一块屏的拥塞不会让其他屏掉帧——这就是多输出"互不拖累"的代码级保证。
实测数据来自官方 README:在 DP-4/DP-5 双输出环境下,稳态循环跑在约180 Hz(跟随较慢的输出),平均渲染时间约0.32 ms;2400 帧的 Vulkan 客户端压测以 179.96 Hz 完成。
5. 相关源码与延伸阅读
想深入阅读或跟进开发,以下是关键入口:
- Volition 呈现引擎:compositor/src/volition/mod.rs
- 输出调度与多屏邮件箱:compositor/src/bin/deniald/output_scheduler.rs
- 混刷帧时间线:compositor/src/bin/deniald/frame_scheduler.rs
- 总体架构(含 Volition 定位说明):docs/architecture.md
- 多输出与 VRR 配置(
outputs.conf):compositor/README.md - 未来的硬件平面直扫方案(direct scanout):docs/direct_scanout.md
- Rust 测试策略(KMS/热插拔/回滚契约):compositor/TESTING.md
合成器的快速自检命令(来自官方 README):
cargo test --manifest-path compositor/Cargo.toml --lib --bins --tests --features flutter6. 常见问题
Q:这套机制需要手动开启吗?不需要。Volition 是deniald的核心组成部分,所有 KMS 呈现默认都经过它,用户只需正常安装并启动 Denial 会话。
Q:VRR(可变刷新率)下还会撕裂吗?Denial 支持按输出开启vrr=。VRR 改变的只是"边界何时到来",而 Volition 依旧在每条边界做原子锁存,同步保证不变。
Q:为什么 240Hz 游戏不会拖慢 60Hz 副屏?因为混刷管线让慢屏重放保留投影、每屏独立邮件箱;direct scanout 路线图更进一步:直扫时由硬件平面直接显示游戏画面,Flutter 只在自身有改动时才渲染,进一步降低跨屏耦合。
一句话总结:Denial 的丝滑来自"时机精确"(2ms 前瞻逼近 vblank)与"永不阻塞"(非阻塞 ioctl + 有界重试 + 背压)的组合,而多输出帧同步则来自每屏独立时间线 + 独立邮件箱 + 共享大画布的架构。这套机制不追求炫技,只追求一件事:每一帧,都在正确的边界,完整地上屏。
【免费下载链接】denial
A smooth, customizable Wayland compositor with glass effects, three window layouts, and settings you can actually click. We got a little carried away.
相关推荐
告别画面撕裂:Craft游戏帧同步与垂直同步优化指南
告别画面撕裂:Craft游戏帧同步与垂直同步优化指南 你是否曾在游戏中遇到画面撕裂、卡顿或帧率波动的问题?这些现象不仅影响游戏体验,还可能导致操作延迟。本文将深
游戏开发3D渲染为什么你的魔兽争霸3总是卡顿?三步实现180帧丝滑体验
为什么你的魔兽争霸3总是卡顿?三步实现180帧丝滑体验 还在为魔兽争霸3的卡顿问题烦恼吗?这款经典RTS游戏在现代硬件上常常遭遇性能瓶颈,但通过正确的性能优化方
游戏开发逆向工程告别画面撕裂:RetroArch Linux KMS模式刷新率切换完全指南
告别画面撕裂:RetroArch Linux KMS模式刷新率切换完全指南 你是否在Linux系统中使用RetroArch时遇到过画面撕裂、帧率不稳定的问题?特
游戏开发跨平台音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考