帧缓冲与显示原理:显存架构、CRTC扫描、VSync与撕裂现象
做显示驱动,绕不开一个核心问题:数据怎么从内存跑到屏幕上?
我刚开始接触这块时,以为就是简单地把像素数据往显存里一丢,屏幕自然就亮了。后来被撕裂、闪屏、花屏轮番教育过,才老老实实把帧缓冲的底层原理啃了一遍。
这一章,我们就来拆解显示链路的核心环节。你搞懂了这些,后面做双缓冲优化才有底气。
1. 显存架构:帧缓冲的物理基础
帧缓冲(Framebuffer),说白了就是一块专门存放像素数据的内存区域。它可以是系统内存里划出来的一块,也可以是独立的显存(VRAM)。
我个人习惯把帧缓冲理解成「屏幕的底片」。你往底片上画什么,屏幕就显示什么。
| 类型 | 位置 | 特点 | 常见场景 |
|---|---|---|---|
| 系统内存帧缓冲 | DDR | 共享带宽,延迟较高 | 低端嵌入式、早期Linux fbdev |
| 独立显存帧缓冲 | GDDR/HBM | 高带宽,低延迟 | 桌面GPU、游戏主机 |
| 显存映射帧缓冲 | DDR + 显存 | 通过DMA搬运,折中方案 | 移动SoC、部分ARM平台 |
这里有个关键点:帧缓冲的格式。比如 RGB565、ARGB8888、YUV420,每种格式每个像素占用的字节数不同。我曾在项目中遇到过一个问题:配置了 1920x1080 的分辨率,ARGB8888 格式,结果屏幕只显示上半部分。查了半天,发现是帧缓冲大小只分配了 1920x1080x3 字节,忘了 Alpha 通道占的 1 个字节。
2. CRTC扫描:屏幕是怎么一帧一帧刷新的?
CRTC(Cathode Ray Tube Controller)这个名字虽然老,但概念至今沿用。它负责从帧缓冲里一行一行地读取像素数据,然后送给显示接口。
你想想看,屏幕刷新不是瞬间完成的。CRTC 的工作流程是这样的:
- 水平扫描:从左到右扫完一行像素。
- 水平消隐:扫完一行后,电子枪(或等效逻辑)回到下一行起点,这段时间不传输有效像素。
- 垂直扫描:一行一行往下扫,直到扫完整个画面。
- 垂直消隐:扫完最后一帧后,回到左上角起点,准备下一帧。
嗯,这里要注意:消隐期(Blanking Interval) 是驱动做优化的黄金窗口。因为这段时间 CRTC 不读取帧缓冲,你可以安全地切换缓冲区、更新配置,不会产生撕裂。
3. VSync:垂直同步的真相
VSync(Vertical Synchronization)就是垂直同步信号。它发生在 CRTC 完成一帧扫描、进入垂直消隐期的那个瞬间。
为什么需要 VSync?
- 硬件层面:VSync 是一个硬件中断信号,告诉驱动「上一帧已经显示完了,你可以准备下一帧了」。
- 软件层面:应用程序等待 VSync 信号,再提交新帧,避免在屏幕刷新中途修改帧缓冲。
我见过不少开发者把 VSync 和帧率控制混为一谈。其实 VSync 只是同步机制,帧率是应用层决定的。你开了 VSync,但渲染速度跟不上,帧率反而会掉到 30fps 甚至更低——这就是常见的「卡 VSync」现象。
4. 撕裂现象:为什么画面会裂开?
撕裂(Tearing)是显示驱动里最直观的 bug。屏幕上半部分显示的是旧帧,下半部分显示的是新帧,中间有一条错位的分界线。
为什么会这样?
说白了,就是CRTC 在读取帧缓冲的时候,你往同一个缓冲区里写了新数据。CRTC 读到一半,内容变了,前后不一致,画面就撕裂了。
我举个例子:
- CRTC 开始扫描第 500 行。
- 此时应用程序往帧缓冲里写入了新帧。
- CRTC 继续扫描第 501 行时,读到的已经是新帧的数据。
- 结果:第 1-500 行是旧帧,第 501-1080 行是新帧,中间一条撕裂线。
5. 核心流程:从帧缓冲到屏幕显示
为了让你更直观地理解整个链路,我画了一张流程图。这张图我每次给团队新人讲驱动时都会用,你仔细看看。
6. 关键参数:刷新率与像素时钟
做驱动适配时,你免不了要和显示时序参数打交道。我整理了一份常用对照表,你调试时可以直接参考。
| 分辨率 | 刷新率 | 像素时钟 | 水平总像素 | 垂直总行数 |
|---|---|---|---|---|
| 1920x1080 | 60Hz | 148.5 MHz | 2200 | 1125 |
| 1920x1080 | 30Hz | 74.25 MHz | 2200 | 1125 |
| 1280x720 | 60Hz | 74.25 MHz | 1650 | 750 |
| 1024x768 | 60Hz | 65.0 MHz | 1344 | 806 |
注意看,水平总像素和垂直总行数都大于实际分辨率。多出来的部分就是消隐区。像素时钟 = 水平总像素 × 垂直总行数 × 刷新率。这个公式你记牢了,调时序时经常要用到。
7. 避坑总结:驱动开发者的第一课
最后,我把自己踩过的坑总结成几条原则,你记下来,能省不少调试时间。
- 不要在 VSync 中断之外更新帧缓冲地址——我曾经图省事,在普通线程里直接改寄存器,结果撕裂线飘了三天才找到原因。
- 帧缓冲对齐很重要——很多硬件要求帧缓冲起始地址按 4KB 或 1MB 对齐,不对齐直接黑屏或花屏。
- 消隐期不是无限长的——垂直消隐期通常只有几百微秒到几毫秒,你的缓冲区切换逻辑必须在这个时间内完成,否则会错过 VSync。
- 多缓冲区不是越多越好——双缓冲够用就别上三缓冲,多一级缓冲多一级延迟,游戏场景下尤其明显。