用可缓存容器实现更优性能
本节介绍如何通过使用RAM来节约一些可重复使用的绘制,从而在一些动画场景中实现更优性能。
在应用中通过拖拽或动画移动控件(如Image或TextArea)时,TouchGFX需要在每一帧中的新位置重新绘制这些控件,另外在大多数情况下,还需要重新绘制之前被这些控件覆盖的部分背景。
当这些控件在计算上十分复杂时,如纹理映射器、形状和大尺寸透明图像,由于没有硬件加速,MCU难以高效地进行渲染。 导致屏幕重新绘制会耗费很多间,进而影响应用性能。
下面我们将讨论如何使用可缓存容器,通过避免高成本的重新绘制为包含计算复杂元素的动画加速。 虽然我们使用的是STM32F429Discovery板执行的本文中的测量,可缓存容器技术也广泛地应用于其他硬件平台。 为了创建位图缓存,需要一些可用的RAM。
Further reading
性能影响
由于使用MCU移动计算成本高的控件会影响性能,包含许多小步骤的动画会因为每一帧的渲染时间过长而显得缓慢和迟滞。 为了更快完成动画(在时间上)对动画进行编程会导致每步变大,并且动画会显得流畅度不够。
下面是一个在STM32F429-DISCO板(240x320)上运行的例子,其中有一个全屏容器垂直向上移动,另一个类似的容器从底部移入。
在下面的视频中,ToggleButton用于切换可缓存容器启用和禁用。 性能差异显而易见。
这两个移动的容器都包含一个背景 框、一个 文本区、 和一个 纹理映射器。 纹理映射器配置为使用双线性渲染算法,全局阿尔法值为174,这使得其绘制成本高昂。 在STM32F429-DISCO板上,整个屏幕的渲染时间约为100 ms。
测试应用
为了移动这两个元素,同时维持它们的相对位置,将它们放在一个名为masterContainer
的父容器中,父容器的高度是任何一个子容器的两倍,其尺寸为240 x 640 (2*320)
。 通过在TouchGFX Designer中将容器声明为移动动画类,使其能够接收应用ticks并随时间的推移形成动画,在这段时间里可以测量性能。
上方容器名为container1
,处于位置 (x=0, y=0)。 下方容器名为container2
,处于位置 (x=0, y=320),在父容器masterContainer
中,它位于container1的正下方。
由于container1
和container2
位于masterContainer
中,在我们移动masterContainer
时,两个元素会一起移动。 例如,如果我们将masterContainer
移动到位置 (x=0, y=320),container1
将不可见,而container2
将完全可见。 这两种状态之间的动画可使用TouchGFX Designer中的交互来创建。
下面的代码实现masterContainer
位于下方时向上移动,在它已经位于上方时向下移动。 为简单起见,将代码插入视图的 handleClickEvent
事件句柄中,实现在用户触摸屏幕上的任何位置(ToggleButton以下)时执行代码:
Screen1View.cpp
void Screen1View::handleClickEvent(const ClickEvent& evt)
{
//Forward event to base View (for the ToggleButton to work)
View::handleClickEvent(evt);
//If touch is released and y > 50 (below the ToggleButton), move masterContainer
if (evt.getType() == ClickEvent::RELEASED && evt.getY() > 50)
{
const int endPosition = masterContainer.getY() >= 0 ? -320 : 0;
masterContainer.startMoveAnimation(masterContainer.getX(), endPosition,
20 /* ticks */,
EasingEquations::cubicEaseInOut,
EasingEquations::cubicEaseInOut);
}
}
重绘复杂容器的性能
如前文所述,当MCU必须在动画的每一个小步骤重新绘制高成本的纹理映射器时,一个帧的渲染时间约为100 ms。 相当于每秒10帧(fps)。 整个动画有20帧,因此需要约两秒钟。
在STM32F429-DISCO评估套件上,渲染时间作为GPIO G14上的数字信号来提供。 VSYNC信号在G13上提供。 GPIO配置在GPIO.cpp
文件中设置。
下图是向上移动masterContainer
时,应用的VSYNC和RENDER_TIME的测量:
渲染时间是第一个信号(低电平有效)。 我们可以看到,移动动画中第一帧的渲染时间是99.29 ms。
较低的信号是VSYNC,在像素时钟开始输出到屏时,每帧都有一个从高向低的转换过程。 在上面测量中可以看出,绘制单帧占了屏显7帧的时间。 在第8个VSYNC信号时开始下一帧的渲染。 在渲染过程中,显示屏重复显示已绘制的上一帧的内容(在另一个帧缓冲中)。
通过缓存改善性能
我们可以通过将容器渲染缓存到内存中来改善上面移动动画的性能。 此后,我们只需将该内存中的像素移动到帧缓冲(使用DMA),而不是使用MCU重新绘制复杂的控件。 即使应用只使用MCU就可以达到每秒60帧,也会忙于(可能是100% MCU负载)重复执行相同计算,而不是执行更重要的任务。
现在,容器的这个“内存图像”可以显示在屏幕上的不同位置,无需重新渲染容器。
第一步就是通过TouchGFX Designer使能缓存,勾选容器的 Cacheable 属性,包括 container1
and container2
:
下一步是在可以将容器缓存到其中的RAM中创建两幅动态位图。
确定位图缓存在RAM中的存储地址。 就本例而言,我们将它放在SDRAM中(STM32F429上从地址0xd0000000开始),就在帧缓冲之后。
对于Windows仿真器,在全局变量中分配缓存:
Screen1View.hpp
#ifdef SIMULATOR
uint32_t sdramBuffer[8*1024*1024/4];
uint16_t* sdram = (uint16_t*)sdramBuffer;
#else
uint16_t* sdram = (uint16_t*)(0xd0000000 + 320*240*2*2);
#endif
初始化位图缓存并创建两幅动态位图用于缓存:
Screen1View.cpp
//Create bitmap cache and two dynamic bitmap for caching, each bitmap is 150Kb
Bitmap::setCache(sdram, 320*1024, 2); //320Kb cache
dynamicBitmap1 = Bitmap::dynamicBitmapCreate(240, 320, Bitmap::RGB565);
dynamicBitmap2 = Bitmap::dynamicBitmapCreate(240, 320, Bitmap::RGB565);
将动态位图分配给容器并将它们设置为缓存模式:
Screen1View.cpp
//Assign the bitmaps to the Cacheable Containers
container1.setCacheBitmap(dynamicBitmap1);
container2.setCacheBitmap(dynamicBitmap2);
//Enable caching
container1.enableCachedMode(true);
container2.enableCachedMode(true);
//Finally update the cached bitmaps
container1.updateCache();
container2.updateCache();
调用Container::updateCache()
会将两个容器渲染到它们各自的位图中。 在任何需要容器更新状态的时候调用此方法。 这必须由开发者在应用代码中进行处理。
在为container1
和container2
启用缓存后,现在的性能测量显示,渲染时间从~99ms缩短到~5ms,这意味着可以轻松地以60帧每秒的速度进行渲染,并在20帧内完成整个动画。
结论
如果对象在计算上十分复杂且在不同动画步骤之间无变化,在制成动画(频繁移动)时,使用包含动态位图的可缓存容器可显著缩短渲染时间。 如果缓存必须更新(如时间更新时的表盘),在应用控制动画期间,可以在特定的点重新计算缓存内容。