渲染引擎架构:TouchGFX 渲染管线总览
这一章,我们来聊聊 TouchGFX 的渲染引擎。说白了,就是屏幕上那些流畅的动画、丝滑的滑动,到底是怎么画出来的?
我刚开始接触嵌入式 GUI 时,总觉得 UI 刷新就是“把像素点填到屏幕上”这么简单。直到有一次,我在一个低端 MCU 上跑 TouchGFX 的复杂动画,结果帧率掉到了十几帧,画面卡得像幻灯片。嗯,从那以后,我才开始认真研究它的渲染管线。
TouchGFX 的渲染架构,可以分成三层来看:
- 应用层:你写的 Screen、View、Presenter,负责业务逻辑和界面布局。
- 渲染层:负责把 Widget 转换成像素数据,执行裁剪、混合、颜色转换。
- 硬件层:通过 DMA2D、LTDC 等外设,把帧缓冲里的数据推送到显示屏。
这三层之间,通过一个精心设计的管线串联起来。我习惯把这个管线叫做“刷新流水线”。
渲染管线总览:从 Widget 到像素
TouchGFX 的渲染管线,本质上是一个“绘制命令”的流水线。每次屏幕需要刷新时,它不会傻傻地重绘所有东西,而是只更新变化的部分。
管线大致分这么几步:
- 脏矩形收集:系统标记哪些区域需要重绘。
- 裁剪计算:确定每个 Widget 的实际可见区域。
- 绘制排序:按 Z-order 从后往前绘制。
- 像素填充:调用绘制函数,把颜色数据写入帧缓冲。
- 同步输出:帧缓冲切换,LTDC 把数据送到屏幕。
你想想看,如果每次刷新都重绘整个屏幕,那 480x272 的屏幕,每帧要处理 13 万多个像素。TouchGFX 的脏矩形机制,就是为了避免这种浪费。
核心要点:TouchGFX 的渲染管线是“增量式”的。它只绘制变化的部分,而不是全屏重绘。这是它能跑在低性能 MCU 上的关键。
软件渲染 vs 硬件加速
TouchGFX 支持两种渲染方式:纯软件渲染和硬件加速渲染。这两种方式,我都在实际项目中用过,差别还是挺大的。
| 对比项 | 软件渲染 | 硬件加速 |
|---|---|---|
| 实现方式 | CPU 逐像素填充 | DMA2D 硬件搬运 |
| 内存占用 | 较低,只需帧缓冲 | 需要额外显存或缓存 |
| 性能瓶颈 | CPU 占用高,帧率受限 | CPU 几乎零负载 |
| 适用场景 | 低端 MCU,无 DMA2D | STM32F4/F7/H7 系列 |
| 颜色格式 | 支持 RGB565、RGB888 | 支持更多格式,含 L8 压缩 |
软件渲染,说白了就是 CPU 自己动手。每个像素的颜色计算、混合、写入,都是 CPU 一条指令一条指令跑出来的。我早期在 STM32F103 上做项目时,用的就是纯软件渲染。那时候屏幕分辨率只有 320x240,动画稍微复杂一点,CPU 就忙不过来了。
硬件加速就不一样了。STM32 的 DMA2D 外设,可以独立完成像素搬运、颜色格式转换、Alpha 混合。CPU 只需要告诉 DMA2D“从哪里搬到哪里”,然后就可以去干别的事了。我在 H743 上做过测试,开启 DMA2D 加速后,同样的动画帧率从 25fps 直接飙到了 60fps。
我的经验:如果你的 MCU 支持 DMA2D,一定要开启硬件加速。配置起来并不复杂,但性能提升是质的飞跃。我曾经在一个项目中,因为没开 DMA2D,动画卡得不行,开了之后瞬间流畅。
帧缓冲(Frame Buffer)机制
帧缓冲,是渲染引擎的核心。你可以把它理解成一块内存区域,里面存着屏幕上每个像素的颜色值。TouchGFX 把绘制好的像素数据写进帧缓冲,然后 LTDC 外设再把这个数据送到屏幕。
TouchGFX 支持两种帧缓冲模式:
- 单缓冲:只有一个帧缓冲。绘制和显示共用同一块内存。优点是省内存,缺点是会有撕裂现象。
- 双缓冲:有两个帧缓冲。一个用于显示(前台缓冲),一个用于绘制(后台缓冲)。绘制完成后,交换指针。优点是画面完整,无撕裂。
我个人习惯用双缓冲。虽然多占一块内存,但画面质量好很多。你想想看,如果用户正在滑动列表,突然看到屏幕中间有一条撕裂线,那体验多糟糕。
注意:双缓冲模式下,帧缓冲切换的时机很关键。必须在垂直消隐期(VBlank)切换,否则还是会出现撕裂。TouchGFX 默认会等待 VBlank 信号,但如果你自己写底层驱动,一定要处理好这个同步。
帧缓冲的大小,取决于屏幕分辨率和颜色深度。举个例子:
// 480x272 分辨率,RGB565 格式(每像素 2 字节)
// 单帧缓冲大小 = 480 * 272 * 2 = 261,120 字节 ≈ 255 KB
// 双缓冲需要 2 倍,约 510 KB
// 如果是 RGB888 格式(每像素 3 字节)
// 单帧缓冲大小 = 480 * 272 * 3 = 391,680 字节 ≈ 382 KB
嗯,这里要注意。如果你的 MCU 内部 SRAM 不够大,帧缓冲可以放在外部 SDRAM 里。但外部 SDRAM 的访问速度比内部 SRAM 慢,可能会影响帧率。我做过一个项目,把帧缓冲放在 SDRAM 里,结果帧率从 60fps 降到了 45fps。后来优化了 DMA2D 的 burst 传输,才把性能拉回来。
帧缓冲与渲染管线的协同
渲染管线和帧缓冲是怎么配合的?我画个简单的时序图你就明白了。
在双缓冲模式下,每一帧的流程是这样的:
- LTDC 正在显示前台缓冲的内容。
- CPU 或 DMA2D 在后台缓冲里绘制下一帧。
- 绘制完成后,等待 VBlank 信号。
- VBlank 到来,交换前后台缓冲指针。
- LTDC 开始显示新的前台缓冲内容。
- 旧的缓冲变成后台,开始绘制下一帧。
这个流程,说白了就是一个“乒乓操作”。两个缓冲轮流工作,一个负责显示,一个负责绘制。只要绘制时间不超过一帧的时间(比如 60fps 下是 16.7ms),画面就能保持流畅。
关键点:TouchGFX 的渲染引擎会尽量让绘制和显示并行。如果绘制时间超过了帧周期,帧率就会下降。这时候你需要优化绘制逻辑,或者开启硬件加速。
我曾经在一个项目里,因为图片解码太慢,导致绘制时间超过了 30ms,帧率直接掉到 30fps。后来我把图片从 PNG 换成了 L8 压缩格式,绘制时间降到了 8ms,帧率又回到了 60fps。所以,帧缓冲机制虽然好,但前提是你的绘制速度要跟得上。