跳转到主要内容

性能

本节将讨论嵌入式图形用户界面的性能。

这里的高性能是指在获得所需图形效果和动画时实现高帧率。

我们回顾一下上一节中关于主循环如何影响用户界面帧率的内容。 再次假设有一台连接到LTDC的并行RGB显示屏和两个帧缓冲。 基本情况如下:

双帧缓冲

假设显示屏每秒刷新60次,则每次刷新之间的间隔约16 ms。 计算过程如下:1 s / 60 = 0.01667 s = 16.67 ms。

在1号帧缓冲的传输开始时,TouchGFX开始将1号帧绘制到2号帧缓冲。 如果1号帧的渲染在下一次传输开始前完成,则可以传输2号帧缓冲。 如果没有在16.67 ms内完成,则再次传输1号帧缓冲,并且显示屏的显示内容不变:

主循环时间超过16.67 ms

这种情况意味着丢帧。

采集和更新阶段的时间通常是极短的,如少于1 ms,在考虑主循环消耗的总时间时,多少有些微不足道。 因此,在下文和全文中,在考虑渲染时间时,其中包括采集和更新阶段。

如果许多帧的渲染时间超过16.67 ms的时限,显示屏上的帧率将是30帧每秒(fps)。

如果渲染时间大体上短于16.67 ms,但有一些帧超过16.67 ms,则平均帧率可能接近60 fps,但在用户看来动画可能不流畅。 动画中的某些步骤可能看起来过快而某些又过慢,具体取决于应用。 这不符合我们的期望。

渲染时间还可能更长。 如果刚好超过33 ms,由于每三次传输只有一个新帧就绪,帧率将降至20 fps。

FPS(帧率)最长渲染时间
6016.67 ms
3033.34 ms
2050.00 ms
1566.67 ms

此表显示了给定帧率可获得的最长渲染时间(包括采集和更新阶段)。

为了获得良好的用户界面性能,最好定期检查和监测帧率。 可使用两种方法:

  • 测量渲染时间
  • 丢帧计数

测量渲染时间

测量渲染时间的第一种方法提供了最详细的信息。 其理念实际上是测量从帧传输到渲染阶段结束之间的时间。 图形引擎在采集阶段开始时调用GPIO类的函数,并在渲染阶段结束时再次调用。 应用定义这些函数,并能钩子函数以便执行测量。

测量可通过两种方式来执行:

  • 使用外部计时设备,如示波器:为了使用示波器进行测量,应用应从GPIO接口实现set(GPIO_ID)clear(GPIO_ID)方法。 然后,示波器可以测量输出为高电平的持续时间,以此作为渲染时间。
  • 使用内部计时器:另一种方法是使用内部计时器,如sysTick计时器。 在调用GPIO::set(RENDER_TIME),应用可将计时器的值保存在变量中。 在进行清零调用时,应用可再次读取计时器并减去前值,从而获得渲染时间。 计时器的速度将决定测量精度。 应用必须以某种方式使渲染时间可见。 一种方式是将值保存在全局变量中,并且可以在界面上的TextArea中显示值。 也可使用调试器检查值。

丢帧计数

图形引擎对上一个采集-更新-渲染阶段中发生的传输次数进行计数。 应用可轻松地检查此值,以便了解是否丢帧以及帧率是否下降。

计数在HAL类中提供:

void handleTickEvent() {
tickCounter += 1;
if (HAL::getInstance()->getLCDRefreshCount() > 1) {
//Alert programmer somehow
...
}
}

丢帧补偿

如果丢帧且某个动画的帧率因此下降,可进行一定程度的补偿。 我们可以:

  • 等待其结束 - 让动画继续,这会导致动画持续时间变长,并且动画可能不流畅。
  • 跳过一些帧 - 通过跳帧确保整个动画的持续时间不会超过预定时间。

如果丢帧,可指示TouchGFX自动跳过一些帧。 可通过在每个实际帧将动画tick一次以上来实现。 当渲染时间不稳定时,这有助于让动画更流畅。

HAL.hpp
void setFrameRateCompensation(bool enabled)

影响渲染时间的因素有哪些?

影响渲染时间的因素有许多:更新部分的大小、分层的使用、控件的复杂度和渲染可使用的硬件支持。

界面更新了多少?

渲染时间通常与必须更新的像素数成正比。 如果动画需要的渲染时间过长,一种可能的解决办法是缩小动画面积。 例如,如果有一张旋转图像而性能不够好,则可通过缩小图像尺寸来改善性能。

通过缩小图像尺寸缩短渲染时间

记住,图形引擎会重绘应用使之无效的区域。 这意味着必须只使实际需要刷新的区域无效。

无效区域越大,渲染时间越长。

图形中的层数

在典型应用中,图形将包含彼此堆叠的不同元素。 如果更新了元素中的一个,通常必须重绘所有元素。

典型的例子是背景图像、帧和一些文本:

分层图形元素

此用户界面的创建方法是将TextArea控件放在显示了一个透明帧的Image控件上方。 二者都在背景图像上层:

TouchGFX Designer中的分层图形元素

此解决方案在应用中经常用到。 这是一种十分简单的解决方案,具有高度的灵活性,例如,可以在运行时间更改帧或在背景上移动帧和文本。

关于渲染时间的问题是如果在运行时间更新了文本且需要重绘,图形引擎还需要重绘背景和帧,然后是新的文本。 这会显著增加文本的渲染时间。

无效区域的层数越多,渲染时间就越长。

渲染像素的复杂度

将每个像素渲染到帧缓冲的难度并不一致。 在所有类型的渲染中,图形引擎必须将最终的像素写入帧缓冲。 但是,要写入像素的计算需要的消耗并不相同。

固定色彩(如Box Widget中使用的色彩)的消耗最低,只需计算一个像素并将结果重复用于所有像素。 这意味着使用许多Box可获得非常高的性能。 由于这会导致用户界面质量不高,因此不建议这样做。

第二低的是图像的像素计算消耗,这是因为像素均以可直接使用的格式存储在位图中。 计算要写入帧缓冲的像素关系到从位图中的正确位置加载色彩值。

文本的消耗与图像相当,每个字母实际上都是一幅小图像。 事实上,由于大量小图像导致了相当高的“开始-停止”消耗,因此文本的消耗更高。 例如,每个字母的位置计算。 为了让文本看起来尽可能美观,会将文本显示为具有透明度的小图像,请参见下文关于透明的注释。

旋转或缩放后的图像消耗更高。 任务同样是从位图加载像素值,但由于图形引擎必须包含缩放和旋转,因此这时的计算更耗时。

几何元素(如圆)的消耗比之更高。 这时我们不能从位图加载像素色彩,而是必须计算圆的形状和圆中每个像素的色彩。

透明度增加了元素的绘制消耗。 如果一些像素不是实心的,那么元素是透明的。 图形引擎首先必须绘制透明元素后方的元素(如“帧中的文本”部分所述),这会增加绘制的消耗。 其次,图形引擎随后必须将背景像素与透明元素的像素进行组合,并将结果写入帧缓冲。 此类计算的耗时显著多于只写入计算过的像素的场景。

Box、Image、旋转Image和圆。 实心元素位于第一行。 透明元素在下方。

透明总是需要多出一层。 但是,将实心像素放在其他实心像素的上方并不一定会增加层数。 图形引擎尽量不绘制被其他实心像素覆盖的像素,因为这是在浪费宝贵的时间。

无效区域中元素的复杂度越高,渲染时间就越长。

记住,只有无效区域中的元素才会增加渲染时间。 无效区域之外的元素对渲染时间无影响。

点击此处阅读关于UI组件和性能的更多内容。

渲染的硬件支持

一些STM32微控制器包含图形加速器Chrom-ART(或DMA2D)。 此加速器可缩短渲染时间。 由于加速器与微控制器核心并行运行,微控制器可以在加速器渲染图形时自由地运行其他任务。

Chrom-ART主要用于图像和文本。 如果有,图形引擎会自动使用它。

何时应考虑渲染时间

渲染时间并非总是那么重要。 当低帧率可被用户观察到时,应注意渲染时间。 当动画在屏幕的一部分上运行(如旋转的图标)或您在界面上移动或滑动某元素时,通常就属于这种情况。 如果更新频率低,那么在用户看来,动画将呈现出分步显示而非流畅的状态。 如果是这样,应检查渲染时间。

另一方面,如果用新界面替代整个界面,当更换期间帧率显著下降时,用户通常注意不到。 这是因为用户看不到渲染何时开始,只能看到它何时结束。

这两条规则意味着对于动画元素(如移动元素)而言,应使用较少的层数,避免使用复杂元素和许多层数。 对于界面的其余部分,这些不是问题。

模拟时钟和滚动列表

在本例中,左侧有一个模拟时钟。 通过旋转三幅细长的图像渲染三根指针。 这通常不难实现,因为指针并非总是在移动。 但如果我们要让时钟在界面上到处移动,将会在每一帧中重绘指针,由于绘制旋转图像通常比较耗时,因此会比较复杂。

右侧是一个滚动列表。 用户可以上下移动此数字列表,为了让用户界面显示出高响应性,需要高帧率。 因此,必须考虑滚动列表中元素的渲染时间,或者缩小滚动列表的尺寸。

通过使内容无效来优化性能

通常整个控件均无效,但图形引擎只能使控件的内容无效,而不能使整个控件无效。 通过减少无效区域,渲染时间一般会明显缩短。 渲染时间的改进取决于:

  • 控件内容覆盖的区域相对于整个控件的大小。
  • 背景控件部分或全部被控件覆盖。

下图以TextArea控件为例说明了内容无效的概念。 图1显示了控件的整个区域。 图2显示了使用TextArea::invalidate()时的无效区域。 图3显示了使用TextArea::invalidate()时的无效区域。

图1. 横跨整个屏幕宽度的文本区域

图2. 使用TextArea::invalidate()时无效的区域(红色)

图3. 使用TextArea::invalidateContent()时无效的区域(绿色)

使用TextArea::invalidateContent()的示例

在控件与其他控件重叠的情况下,使用TextArea::invalidate()使整个TextArea无效时,需要重新绘制其他控件。 通过改用TextArea::invalidateContent(),我们将不必要的无效和重绘控件的风险降至最低。 这对于昂贵的控件尤其如此,例如:圆、仪表等。

下图说明了如何避免使用TextArea::invalidateContent()使背景控件(图像-意法半导体标志)无效。 如果我们使用TextArea::invalidate(),将会使后台控件无效并重新绘制。

使用TextArea::invalidateContent()的示例

获得良好性能的建议

我们总结了获得良好性能的建议,以结束本节内容:

  • 不要重绘未更改的部分 确保没有误操作将界面上不必要的部分失效。 这会降低性能且无任何益处。
  • 在质量与速度之间寻求平衡 降低元素的复杂度有助于提高性能。 复杂度与性能之间的良好平衡通常极为关键。
  • 利用硬件能力 具有硬件加速(Chrom-ART)的微控制器的能力通常高于没有硬件加速的微控制器。 考虑使用具有Chrom-ART的微控制器。
  • 用图像替代计算图形 计算得到的圆比圆图像慢。 一般而言,图像可替代许多静态元素。
  • 调整显示屏刷新率 如本节开头所述,刷新率是渲染时间的硬性限制。 如果渲染时间超过刷新率,帧率将下降。 如果渲染时间只超过刷新率一点点,也许能够将显示屏的刷新率降至如55 Hz(相当于18.2 ms)这样的水平,并维持高帧率。