3、刷新策略基础:全屏刷新原理、局部刷新原理、脏矩形算法

好,咱们今天聊点实在的。TouchGFX 的动画到底是怎么一帧一帧画出来的?说白了,就是「刷新策略」在背后干活。我刚开始接触嵌入式 GUI 时,总觉得屏幕刷新不就是把整张图怼上去吗?后来踩了坑才发现——全刷和局部刷,差别大了去了。

3.1 全屏刷新:简单粗暴,但代价不小

全屏刷新,顾名思义,每一帧都把整个 framebuffer 的内容全部推到显示屏上。这种方式在逻辑上最直接,代码也最好写。

核心流程:

  1. TouchGFX 渲染引擎绘制完一帧完整画面到 framebuffer
  2. LTDC(或 DMA2D)将整个 framebuffer 通过 RGB 接口发送给 LCD 驱动 IC
  3. LCD 驱动 IC 逐行扫描整屏,完成刷新

但问题来了——全屏刷新意味着每次都要传输 width × height × 像素深度 的数据量。举个例子,480×272 的屏幕,16 位色深,一帧就是 480×272×2 = 261,120 字节。如果刷新率是 60fps,每秒要传 15.6MB 的数据。这在低端 MCU 上,带宽压力非常大。

我曾经踩过的坑: 在一个 STM32F429 项目上,我图省事用了全屏刷新做动画,结果帧率死活上不去,CPU 占用率飙到 90%。后来才发现,大部分时间都耗在了刷屏上。嗯,从那以后我再也不敢无脑全刷了。

3.2 局部刷新:只改该改的地方

局部刷新的思路很简单——既然只有一小块区域在变化,为什么要把整个屏幕重画一遍?TouchGFX 的局部刷新机制,说白了就是「哪里变了刷哪里」。

它的工作流程是这样的:

  • 引擎检测到某个控件(比如按钮、滑块)的状态发生变化
  • 只更新 framebuffer 中对应的矩形区域
  • 通过 LTDC 或 SPI 接口,仅将这部分数据发送给 LCD

你想想看,如果屏幕上只有一个进度条在动,局部刷新可能只需要传输几千字节,而不是几十万字节。这对带宽的节省是数量级的。

对比项 全屏刷新 局部刷新
数据传输量 整屏(大) 仅脏区域(小)
CPU 占用 高 低
实现复杂度 简单 中等
适用场景 静态界面、全屏切换 动画、控件交互

我个人习惯: 在项目初期先用全屏刷新把功能跑通,等界面稳定了再切到局部刷新做优化。这样调试阶段少了很多麻烦。

3.3 脏矩形算法:局部刷新的核心

局部刷新听起来很美,但有个关键问题——引擎怎么知道「哪里变了」?答案就是脏矩形算法。

脏矩形(Dirty Rect)本质上是一个或多个矩形区域,记录了当前帧相对于上一帧发生变化的像素范围。TouchGFX 内部维护了一个脏矩形列表,每次渲染循环都会做这几件事:

  1. 标记脏区域: 当某个控件的位置、大小、内容发生变化时,引擎将其 bounding box 标记为「脏」
  2. 合并脏矩形: 如果有多个相邻或重叠的脏矩形,引擎会尝试合并,减少刷新次数
  3. 裁剪渲染: 只对脏矩形内的控件进行重绘,跳过未变化的部分
  4. 提交刷新: 将最终的脏矩形列表传给底层驱动,执行局部刷屏

这里有个细节值得注意——合并策略。如果两个脏矩形相隔很近,合并成一个大的矩形可能反而更高效,因为减少了多次刷新的开销。但如果它们离得很远,强行合并会导致大量未变化区域也被刷新,得不偿失。

避坑指南: 我曾经在一个仪表盘项目上,发现动画有撕裂感。排查了半天,原来是脏矩形合并策略太激进,把不相干的区域也刷了,导致帧率下降。后来我调整了合并阈值,问题就解决了。

3.4 三种策略的对比与选择

在实际项目中,全屏刷新和局部刷新并不是非此即彼的关系。TouchGFX 允许你根据场景动态切换。我个人建议这样选:

  • 静态页面(菜单、设置页): 用全屏刷新,简单稳定
  • 动态控件(滑块、进度条、图表): 用局部刷新 + 脏矩形,省带宽
  • 全屏过渡动画(页面切换): 用全屏刷新,因为整个画面都在变

下面这张图展示了脏矩形算法在局部刷新中的核心位置:

脏矩形算法核心流程 用户交互 / 控件变化 标记脏矩形 合并脏矩形 裁剪渲染 局部刷屏 关键点 • 脏矩形越小,传输量越少 • 合并策略影响性能 • 裁剪渲染减少绘制开销

3.5 实际项目中的权衡

最后说点实在的。你在选刷新策略时,不能只看理论,还得考虑硬件限制。比如:

  • MCU 的 DMA 带宽: 如果 DMA 支持双缓冲 + 自动传输,局部刷新的优势会更明显
  • LCD 接口类型: RGB 并行接口适合全刷,SPI 串口更适合局部刷
  • 帧率要求: 60fps 的动画,局部刷新几乎是必须的;30fps 的静态菜单,全刷也够用

我建议: 在项目初期,先用 TouchGFX 的默认配置跑起来,然后用性能分析工具(比如 STM32CubeMonitor)看看瓶颈在哪。别一上来就搞局部刷新优化——先确保功能正确,再谈性能。