1. 双缓冲基础概念:屏幕刷新、撕裂与核心思想
大家好,我是这次跟你一起啃这块硬骨头的工程师。双缓冲这个名词,做嵌入式的应该都听过。但说实话,真正把它吃透、能在驱动里用好的,不多。
这一章,我们先打地基。把屏幕刷新是怎么回事、单缓冲为什么会有撕裂、双缓冲到底解决了什么问题,掰开揉碎了讲清楚。
1.1 屏幕刷新:你看到的每一帧都是“画”出来的
屏幕显示图像,不是一瞬间完成的。它是一行一行、从左到右、从上到下扫描出来的。这个过程叫刷新。
拿常见的 60Hz 屏幕来说,每秒刷新 60 次。每次刷新,电子枪(或者 LCD 的驱动电路)从左上角开始,逐行把像素数据刷到屏幕上。刷完一帧,再从头开始。
这个过程中,有两个关键时间点:
- 垂直消隐期(VBlank):一帧结束、下一帧还没开始的间隙。屏幕不显示数据,是安全的更新窗口。
- 刷新进行中:屏幕正在逐行输出图像。这时候你动显存里的数据,画面就会出问题。
1.2 单缓冲的撕裂问题:为什么画面会“劈叉”?
单缓冲,就是整个系统只有一个显存区域。CPU/GPU 往这个区域写数据,屏幕控制器也从同一个区域读数据去刷新。
问题来了——读写是并发的。屏幕正在刷新第 N 帧,读到一半,CPU 把显存内容改成了第 N+1 帧的数据。结果呢?
屏幕上半部分是旧帧,下半部分是新帧。中间有一条明显的错位线。这就是 撕裂(Tearing)。
我曾经在一个车载仪表盘项目里遇到过这问题。车速指针在快速摆动时,屏幕中间总有一条撕裂线。客户反馈说“指针像被切了一刀”。嗯,那就是典型的单缓冲撕裂。
为什么会这样?因为指针更新频率高,每次刷新周期内,CPU 都在修改显存。屏幕读到一半,数据变了,画面自然就“劈叉”了。
| 对比项 | 单缓冲 | 双缓冲 |
|---|---|---|
| 缓冲区数量 | 1 个 | 2 个 |
| 读写冲突 | 存在,直接导致撕裂 | 不存在,读写分离 |
| 画面完整性 | 可能撕裂 | 完整无撕裂 |
| 实现复杂度 | 低 | 中 |
| 内存占用 | 低 | 高(2 倍) |
1.3 双缓冲的核心思想:读写分离,各干各的
双缓冲的思路很简单:准备两个缓冲区。
- 前端缓冲区(Front Buffer):屏幕控制器正在读取并刷新的缓冲区。它只读,不写。
- 后端缓冲区(Back Buffer):CPU/GPU 正在绘制的缓冲区。它只写,不读。
屏幕刷新时,始终从前端缓冲区取数据。CPU 绘制时,始终往后端缓冲区写数据。两者互不干扰。
等后端缓冲区绘制完成,在 VBlank 期间,交换两个缓冲区的角色。原来的后端变成前端,原来的前端变成后端。这个过程叫 缓冲区交换(Buffer Swap)。
你想想看,这样一来,屏幕永远读到的是完整的一帧。撕裂问题,从根本上被解决了。
下面这张图,把单缓冲和双缓冲的流程对比画出来了。你看一眼就明白了。
1.4 缓冲区交换的两种方式
交换缓冲区,不是简单地把数据从后端拷贝到前端。那样太慢了,等于白干。实际做法是交换指针。
有两种主流方式:
- Page Flipping(页翻转):直接修改显示控制器的帧缓冲地址寄存器。让它指向新的缓冲区。这是最常用的方式,效率最高。
- Bit Blit(位块传输):把后端缓冲区的数据拷贝到前端缓冲区。这种方式有额外开销,但某些硬件不支持页翻转时,只能这么干。
1.5 双缓冲的代价:延迟与内存
双缓冲不是没有代价的。主要有两个:
- 内存翻倍:原来一个缓冲区的大小,现在要两份。对于 1080p 的 32 位色深,一个缓冲区就要 8MB 左右。两个就是 16MB。在嵌入式设备上,这可能是笔不小的开销。
- 输入延迟增加:因为 CPU 绘制的是后端缓冲区,屏幕显示的是前端缓冲区。从用户输入到画面响应,中间至少隔了一帧。对于 60Hz 屏幕,就是 16.7ms 的延迟。玩游戏时能感觉到。
我记得在一个工业 HMI 项目里,客户对触控响应要求极高。双缓冲带来的 16ms 延迟,他们觉得“卡”。后来我们用了三重缓冲,配合 VSync 优化,才把延迟压到可接受范围。
1.6 小结
这一章,我们把双缓冲的底裤扒干净了。
- 屏幕刷新是逐行扫描的,VBlank 是安全更新窗口。
- 单缓冲的撕裂,本质是读写冲突。
- 双缓冲的核心是读写分离,用空间换时间。
- 缓冲区交换推荐 Page Flipping,效率最高。
- 代价是内存翻倍和额外一帧的输入延迟。