通过部分帧缓冲降低内存使用率
本节通过时钟应用示例,阐述如何在配备GRAM的显示屏上配置和使用部分帧缓存,以牺牲部分性能为代价降低内存需求。
下面是在STM32L4R9Discovery评估套件上运行的应用的视频
完整帧缓冲内存
帧缓冲区通常是一个大型内存数组,其存储空间足以容纳显示屏上的所有可用像素数据。 如果在分辨率为480 x 272的24位显示屏上运行,则完整大小的帧缓冲区可容纳480 x 272 x 3字节 = 391,680字节。
一些应用可能有2(“双重缓冲”)甚至3个帧缓冲区。 在这种情况下,要求的总存储空间将是783,360和1,175,040字节。
在绘制UI的任何部分时,TouchGFX向帧缓冲区写入像素值,在所有绘制操作完成后,帧缓冲区被传输到显示屏。 通常会将整个帧缓冲区传输到显示屏,即使只更新了UI的一部分。 在传输前,通常可以将帧缓冲分成许多小块进行更新。
更新1、更新2、更新3、……、更新N,传输至显示屏
在某些情况下,特别是在没有外部RAM的低成本解决方案中,帧缓冲区必须足够小,使内部RAM能满足帧缓冲和应用其余部分对内存的需求。 部分帧缓冲区在这种情况下十分有用。
部分帧缓冲内存
部分帧缓冲区使TouchGFX应用能够在几个小于完整大小的帧缓冲区之上运行。 帧缓冲区的数量和大小是可配置的。 此技术可大幅降低应用的内存空间要求,但也带来了一些限制:
- 部分帧缓冲区只能在具有内置存储器的显示屏上工作。 这些显示屏通常是DSI显示屏或具有并行总线连接(DBI A/B型,8080/6800)或SPI总线连接的显示屏。
- 复杂应用可能发生撕裂。
完全帧缓冲区能存储显示屏上每一个像素的显示数据,与此不同,部分帧缓冲区通常只覆盖部分区域。 在本文使用的时钟示例中,使用了三个大小均为11,700字节的帧缓冲区。 这样帧缓冲区总共占用存储器35,100字节的空间。
当应用需要更新UI的某一部分时,TouchGFX将选择配置的部分帧缓冲区中的一个,在该部分帧缓冲中完成其绘图操作,并将该部分传输到显示屏。 对需要渲染的所有UI区域重复此操作 - 这将更新和传输数据的方式变更为:
更新1、传输1、更新2、传输2、更新3、传输3、……、更新N、传输N
在某些情况下,可以在更新下一个缓冲区的同时进行一个部分帧缓冲区的传输。
显示屏画面撕裂
相比于使用完全帧缓冲区,在使用部分帧缓冲区时,TouchGFX会尽快传输更新过的UI部分。 由于显示屏需要定期刷新,在最多16 ms后(就60 fps显示屏而言),显示屏将在其屏幕上显示接收到的更新。 因此,在所有更新传输完毕之前,对显示屏的最初更新可能会被用户注意到。
如果绘图操作和传输的完整序列需要花费较长时间才能完成(> 16 ms),则用户很可能会看到上一帧与一些新的更新的组合。 这被称为画面撕裂,是不期望发生的。 因此,部分帧缓冲区不适合使用复杂动画、需要长时间渲染的应用。
显示屏更新示例
在讨论如何在应用中配置部分帧缓冲区之前,我们先来看一个具体示例,该示例是一个数字时钟,用移动的圆弧来代表秒数。 绿色圆弧每秒移动6度,一分钟完成一整圈。 用如下图所示的四个控件构建UI:
以下是更新数字时钟和圆弧的代码:
MainView.cpp
void MainView::handleTickEvent()
{
ticks++;
if (ticks == 10)
{
ticks = 0;
secs += 1;
if (secs == 60) //increment minutes
{
secs = 0;
min += 1;
if (min == 60) //increment hours
{
min = 0;
hour += 1;
if (hour == 24)
{
hour = 0;
}
}
//Only update digital clock when minutes or hours change
digitalClock.setTime24Hour(hour, min, secs);
}
//Always update seconds
circleSeconds.updateArc(secs*6 - 20, secs*6);
}
}
下图所示为在圆弧接近顶点和数字时钟更新时前几秒更新的区域(灰色矩形)。 在前两帧中,只有秒数在变化(58和59秒)。 在第三帧中,秒数达到60,小时和分钟文本更新:
上面第三幅图像中更新的矩形为154 x 60像素、 20 x 12像素和33 x 8像素。 在使用标准帧缓冲区时,这些矩形会被存入完整帧缓冲区中(覆盖之前的像素),完整帧缓冲区随后被传输到显示屏。 在使用部分帧缓冲区时,这三个矩形会被存入它们自己的小帧缓冲区中,随后立即被传输到显示屏并显示。
配置部分帧缓冲区
启用TouchGFX部分帧缓存的必要步骤:
- 创建帧缓冲区分配器对象,并分配存储空间
- 配置TouchGFX HAL类以使用该分配器
- 写入代码以将缓冲区传输至显示屏
步骤1和2由TouchGFX Generator通过STM32CubeMX自动生成,而步骤3是一个专用驱动程序,用于将像素数据传输至显示屏。
Further reading
以下示例展示了使用3个1920字节的部分帧缓存块绘制上述章节更新区域的配置方案:
首先来看分配用于绘制小圆圈更新的两个帧缓冲区的位置和大小(上面第二幅图)(假设24Bpp):
矩形 | x | y | 宽度 | 高度 | 像素 |
---|---|---|---|---|---|
矩形1 | 112 | 56 | 22 | 14 | 308像素 = 924字节 |
矩形2 | 153 | 42 | 29 | 11 | 319像素 = 957字节 |
这些矩形都很小,可以放入由帧缓冲区分配器分配的块中。
在上面的第三幅图中,更新了3个矩形:小矩形更新圆圈,较大的矩形覆盖文本:
矩形 | x | y | 宽度 | 高度 | 像素 |
---|---|---|---|---|---|
矩形1 | 126 | 51 | 20 | 12 | 240像素 = 720字节 |
矩形2 | 165 | 42 | 33 | 8 | 264像素 = 792字节 |
矩形3 | 118 | 165 | 154 | 60 | 9,240像素 = 27,720字节 |
同样地,矩形1和2很小,可以放入由帧缓冲区分配器分配的块中,但帧缓冲区3过大。 此矩形过大,将被分成多个可放入帧缓冲区(11,700字节)的矩形。
这里即将更新的第3个矩形太大,无法直接存入最后第3个缓冲区块。 在这种情况下,TouchGFX将等待第一个块传输完毕,然后重复使用第一个块。
将帧缓冲区传输到屏幕
帧缓存块的传输代码实现取决于显示接口类型。 具体实现示例请参见以下文章:
结论
在本文中,我们讨论了对于显示屏具有集成帧缓冲存储器的平台而言,部分帧缓冲区策略如何降低其存储空间要求。