性能
本节将讨论嵌入式图形用户界面的性能。
这里的高性能是指在获得所需图形效果和动画时实现高帧率。
我们回顾一下上一节中关于主循环如何影响用户界面帧率的内容。 再次假设有一台连接到LTDC的并行RGB显示屏和两个帧缓冲。 基本情况如下:
假设显示屏每秒刷新60次,则每次刷新之间的间隔约16 ms。 计算过程如下:1 s / 60 = 0.01667 s = 16.67 ms。
在1号帧缓冲的传输开始时,TouchGFX开始将1号帧绘制到2号帧缓冲。 如果1号帧的渲染在下一次传输开始前完成,则可以传输2号帧缓冲。 如果没有在16.67 ms内完成,则再次传输1号帧缓冲,并且显示屏的显示内容不变:
这种情况意味着丢帧。
采集和更新阶段的时间通常是极短的,如少于1 ms,在考虑主循环消耗的总时间时,多少有些微不足道。 因此,在下文和全文中,在考虑渲染时间时,其中包括采集和更新阶段。
如果许多帧的渲染时间超过16.67 ms的时限,显示屏上的帧率将是30帧每秒(fps)。
如果渲染时间大体上短于16.67 ms,但有一些帧超过16.67 ms,则平均帧率可能接近60 fps,但在用户看来动画可能不流畅。 动画中的某些步骤可能看起来过快而某些又过慢,具体取决于应用。 这不符合我们的期望。
渲染时间还可能更长。 如果刚好超过33 ms,由于每三次传输只有一个新帧就绪,帧率将降至20 fps。
FPS(帧率) | 最长渲染时间 |
---|---|
60 | 16.67 ms |
30 | 33.34 ms |
20 | 50.00 ms |
15 | 66.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控件上方。 二者都在背景图像上层:
此解决方案在应用中经常用到。 这是一种十分简单的解决方案,具有高度的灵活性,例如,可以在运行时间更改帧或在背景上移动帧和文本。
关于渲染时间的问题是如果在运行时间更新了文本且需要重绘,图形引擎还需要重绘背景和帧,然后是新的文本。 这会显著增加文本的渲染时间。
无效区域的层数越多,渲染时间就越长。
渲染像素的复杂度
将每个像素渲染到帧缓冲的难度并不一致。 在所有类型的渲染中,图形引擎必须将最终的像素写入帧缓冲。 但是,要写入像素的计算需要的消耗并不相同。
固定色彩(如Box Widget中使用的色彩)的消耗最低,只需计算一个像素并将结果重复用于所有像素。 这意味着使用许多Box可获得非常高的性能。 由于这会导致用户界面质量不高,因此不建议这样做。
第二低的是图像的像素计算消耗,这是因为像素均以可直接使用的格式存储在位图中。 计算要写入帧缓冲的像素关系到从位图中的正确位置加载色彩值。
文本的消耗与图像相当,每个字母实际上都是一幅小图像。 事实上,由于大量小图像导致了相当高的“开始-停止”消耗,因此文本的消耗更高。 例如,每个字母的位置计算。 为了让文本看起来尽可能美观,会将文本显示为具有透明度的小图像,请参见下文关于透明的注释。
旋转或缩放后的图像消耗更高。 任务同样是从位图加载像素值,但由于图形引擎必须包含缩放和旋转,因此这时的计算更耗时。
几何元素(如圆)的消耗比之更高。 这时我们不能从位图加载像素色彩,而是必须计算圆的形状和圆中每个像素的色彩。
透明度增加了元素的绘制消耗。 如果一些像素不是实心的,那么元素是透明的。 图形引擎首先必须绘制透明元素后方的元素(如“帧中的文本”部分所述),这会增加绘制的消耗。 其次,图形引擎随后必须将背景像素与透明元素的像素进行组合,并将结果写入帧缓冲。 此类计算的耗时显著多于只写入计算过的像素的场景。
透明总是需要多出一层。 但是,将实心像素放在其他实心像素的上方并不一定会增加层数。 图形引擎尽量不绘制被其他实心像素覆盖的像素,因为这是在浪费宝贵的时间。
无效区域中元素的复杂度越高,渲染时间就越长。
记住,只有无效区域中的元素才会增加渲染时间。 无效区域之外的元素对渲染时间无影响。
点击此处阅读关于UI组件和性能的更多内容。
渲染的硬件支持
一些STM32微控制器包含图形加速器Chrom-ART(或DMA2D)。 此加速器可缩短渲染时间。 由于加速器与微控制器核心并行运行,微控制器可以在加速器渲染图形时自由地运行其他任务。
Chrom-ART主要用于图像和文本。 如果有,图形引擎会自动使用它。
何时应考虑渲染时间
渲染时间并非总是那么重要。 当低帧率可被用户观察到时,应注意渲染时间。 当动画在屏幕的一部分上运行(如旋转的图标)或您在界面上移动或滑动某元素时,通常就属于这种情况。 如果更新频率低,那么在用户看来,动画将呈现出分步显示而非流畅的状态。 如果是这样,应检查渲染时间。
另一方面,如果用新界面替代整个界面,当更换期间帧率显著下降时,用户通常注意不到。 这是因为用户看不到渲染何时开始,只能看到它何时结束。
这两条规则意味着对于动画元素(如移动元素)而言,应使用较少的层数,避免使用复杂元素和许多层数。 对于界面的其余部分,这些不是问题。
在本例中,左侧有一个模拟时钟。 通过旋转三幅细长的图像渲染三根指针。 这通常不难实现,因为指针并非总是在移动。 但如果我们要让时钟在界面上到处移动,将会在每一帧中重绘指针,由于绘制旋转图像通常比较耗时,因此会比较复杂。
右侧是一个滚动列表。 用户可以上下移动此数字列表,为了让用户界面显示出高响应性,需要高帧率。 因此,必须考虑滚动列表中元素的渲染时间,或者缩小滚动列表的尺寸。
获得良好性能的建议
我们总结了获得良好性能的建议,以结束本节内容:
- 不要重绘未更改的部分 确保没有误操作将界面上不必要的部分失效。 这会降低性能且无任何益处。
- 在质量与速度之间寻求平衡 降低元素的复杂度有助于提高性能。 复杂度与性能之间的良好平衡通常极为关键。
- 利用硬件能力 具有硬件加速(Chrom-ART)的微控制器的能力通常高于没有硬件加速的微控制器。 考虑使用具有Chrom-ART的微控制器。
- 用图像替代计算图形 计算得到的圆比圆图像慢。 一般而言,图像可替代许多静态元素。
- 调整显示屏刷新率 如本节开头所述,刷新率是渲染时间的硬性限制。 如果渲染时间超过刷新率,帧率将下降。 如果渲染时间只超过刷新率一点点,也许能够将显示屏的刷新率降至如55 Hz(相当于18.2 ms)这样的水平,并维持高帧率。