跳转到主要内容

缓存位图

在本节中,我们将讨论TouchGFX中的位图缓存。 位图缓存是专用RAM缓冲区,应用可将位图保存(或缓存)在其中。 如果缓存了位图,在绘制位图时,TouchGFX将自动使用RAM缓存作为像素来源。

位图缓存在许多情况下十分有用。 从RAM读取数据通常比从闪存读取要快(特别是在使用纹理映射器时,原因是它使用非线性存储器存取),因此,缓存到RAM可提升UI性能。 注意,从内部闪存缓存到外部RAM可能会降低性能。 如果缓存到RAM,由于将从RAM读取位图(在某些情况下,写入闪存要求是非存储器映射闪存),因此可以在显示UI的同时将闪存用于其他目的(如日志文件)。 当您需要修改位图像素时,位图需位于可修改的存储器中,因此这一点也很有用。

出于性能方面的考虑,TouchGFX要求存储在外部闪存中的所有图形数据都能直接访问(通过指针),无需通过驱动层。 这意味着TouchGFX不能从非存储器映射闪存(如SD卡)直接渲染。 为了打破这一限制,位图缓存提供了一种在上电期间缓存RAM中的一些或全部位图数据的机制。 因此,当您需要将位图保存在慢速外部存储器(如U盘或SD卡)上时,位图缓存将十分有用。

设置位图缓存

为了使用位图缓存特性,您首先需要向TouchGFX提供位图缓存配置,然后(在某些情况下)提供BlockCopy函数的硬件特定的实现,以便从外部存储器读取数据。

位图缓存配置

位图缓存配置包括指向缓冲区的指针和缓冲区大小。 在调用Bitmap::setCache时,必须向TouchGFX提供这两个值。 此调用通常位于FrontendApplication.cpp文件中:

FrontendApplication.cpp (extract)
#include <gui/common/FrontendApplication.hpp>
#include <touchgfx/Bitmap.hpp>

FrontendApplication::FrontendApplication(Model& m, FrontendHeap& heap)
: FrontendApplicationBase(m, heap)
{
// Place cache start address in SDRAM at address 0xC0008000;
uint16_t* const cacheStartAddr = (uint16_t*)0xC0008000;
const uint32_t cacheSize = 0x300000; //3 MB, as example
Bitmap::setCache(cacheStartAddr, cacheSize);
}

在上面的示例中,外部存储器中的3 MB缓冲作为位图缓存被传递到TouchGFX。 地址由应用程序员选择。 在下一个示例中,我们只声明一个阵列,并且只传递阵列的地址和大小。 阵列的具体位置将取决于链接器脚本。 在内部RAM中创建(小)位图缓存时最常使用此方法:

FrontendApplication.cpp (extract)
#include <gui/common/FrontendApplication.hpp>
#include <touchgfx/Bitmap.hpp>

// Define an array for the bitmap cache
uint16_t cache[128*1024]; //256 KB cache

FrontendApplication::FrontendApplication(Model& m, FrontendHeap& heap)
: FrontendApplicationBase(m, heap)
{
Bitmap::setCache(cache, sizeof(cache));
}

使用TouchGFX Generator时启用位图缓存

如果您使用CubeMX和TouchGFX Generator,则也可以在TouchGFXHAL.cpp中启用和配置位图缓存。

TouchGFXHAL.cpp (extract)
void TouchGFXHAL::initialize()
{
/* Initialize TouchGFX Engine */
TouchGFXGeneratedHAL::initialize();

uint16_t* cacheStartAddr = (uint16_t*)0xC0008000;
uint32_t cacheSize = 0x300000; //3 MB, as example

touchgfx::Bitmap::setCache(cache, sizeof(cache));
}

如果想重用缓存,在设置新的缓存之前必须删除之前的缓存,方法是调用touchgfx::Bitmap::removeCache()。 如果仅在应用中设置一次,则无需删除缓存。

如需缓存所有位图,则缓存大小必须足够大,才能包含所有位图数据。 注意:会有少量存储空间用于记账(8个字节 x 应用中的位图数量),因此必须分配比原始像素数据的实际需求稍大的存储空间。 该值取决于应用中的位图数量,但额外多几千字节通常即已足够。

BlockCopy将数据从闪存复制到缓存

在缓存位图时,TouchGFX会使用HAL类中的BlockCopy函数将像素从原始位置复制到位图缓存。

如果位图存储在普通可寻址闪存(如内部闪存或存储器映射外部闪存(如QSPI闪存))中,则您无需执行任何操作。 内置实现正常工作。

另一方面,如果位图存储在不可寻址的闪存(如文件系统或非存储器映射闪存)中,则标准复制法是不够的,您需要提供能够从特定闪存读取的更新版本。

阅读使用非存储器映射闪存存储图像一节中关于该主题的更多内容。

缓存操作

位图缓存操作全部放在Bitmap类中:

位图缓存方法说明
bool Bitmap::cache(BitmapId id)此方法缓存一个位图。 仅当缓存中有足够大的未使用存储空间时,才缓存位图。 如果位图被缓存,返回true。 缓存已缓存的位图不会有任何操作。
bool Bitmap::cacheReplaceBitmap(BitmapId out, BitmapId in)此方法用另一个位图(入)替换缓存中的位图(出)。 仅当要替换的位图已缓存且位图大小(字节数)相同时,此方法才能成功。
bool Bitmap::cacheRemoveBitmap(BitmapId id)此方法删除缓存中的位图。 位图使用的存储空间此后可用于缓存另一个位图。
void Bitmap::clearCache()此方法删除缓存中的所有已缓存位图。
void Bitmap::cacheAll()此方法缓存所有位图。 如果分配用于缓存的RAM空间(或可用空间)小于位图的总大小,则不能使用此方法。

缓存策略

如果可分配用于位图缓存的RAM空间小于位图的总大小,则启动过程中不能缓存所有位图。 举个例子,您可以选择只缓存第一个屏幕需要的位图。 在屏幕之间切换时,您可以删除一些或全部已缓存位图,并缓存下一个屏幕所需的位图。 下一节对此进行了举例说明。

按屏幕缓存位图

应用用户界面由一组视图组成。 视图几乎都会使用一些位图。 一种简单的缓存策略是在View::setupScreen中缓存所有位图,并在View::tearDownScreen中清除缓存:

Screen1View.cpp (extract)
void Screen1View::setupScreen()
{
//ensure background is cached
Bitmap::cache(BITMAP_SCREEN2_ID);
//cache some icons
Bitmap::cache(BITMAP_ICON10_ID);
Bitmap::cache(BITMAP_ICON11_ID);
Bitmap::cache(BITMAP_ICON12_ID);
}

void Screen1View::tearDownScreen()
{
//Remove all bitmaps from the cache
Bitmap::clearCache();
}

缓存的存储空间要求是位图使用量最多的屏幕使用的位图大小。 此方法的缺点是如果两个视图都使用一个位图,从第一个视图退出时缓存中的位图会被擦除,并在进入第二个视图时再次缓存。

可对Bitmap::cacheRemoveBitmap使用选择性未缓存位图,从而减少此开销。 cacheRemoveBitmap的缺点是缓存存储空间碎片化。

缓存的另一个普遍缺点是在更改UI(如添加按钮)时,您可能需要更新缓存代码来包含新位图。

替换缓存中的背景位图

如果应用有一组小位图(如图标)和一些大全屏“背景”位图,则可以使用另一种策略:

在进入第一个屏幕之前缓存所有小位图。 FrontendApplication构造函数是执行缓存的好位置。 另外,缓存第一个屏幕的背景位图:

FrontendApplication::FrontendApplication(Model& m, FrontendHeap& heap)
: touchgfx::MVPApplication(),
transitionCallback(),
frontendHeap(heap),
model(m)
{
//cache some icons
Bitmap::cache(BITMAP_ICON10_ID);
Bitmap::cache(BITMAP_ICON11_ID);
Bitmap::cache(BITMAP_ICON12_ID);

//cache first background
Bitmap::cache(BITMAP_SCREEN1_ID);
backgroundBitmapCached = BITMAP_SCREEN1_ID; //remember ID in a variable
}

View::setupScreen方法中,用所需的位图替换缓存的背景位图:

Screen1View::setupScreen()
{
//ensure background is cached
Bitmap::cacheReplaceBitmap(backgroundBitmapCached, BITMAP_SCREEN1_ID);
backgroundBitmapCached = BITMAP_SCREEN1_ID; //remember new ID of cached bitmap
}
void Screen1View::tearDownScreen()
{
//nothing cache related
}

使用此策略的缓存存储空间要求是缓存的位图和一张背景位图的总大小。 相比于前一种方法,此方法由于视图代码较少,其代码更易于维护。 由于移入和移出缓存的位图较少,因此性能更优。

由于存储空间不会碎片化,cacheReplaceBitmap操作比 cacheRemoveBitmap方法更好。

缓存存储空间管理

为了获得位图缓存的完整效果,必须理解缓存的内部操作。

缓存以栈的形式实现。 在之前缓存的位图之后缓存新位图。 当位图从缓存中删除时,位图使用的存储空间会被标记为“空闲”,但不能立即使用该存储空间,除非删除的位图位于栈顶部。 如果位图位于缓存的“中间”,将在下一次调用Bitmap::cache时执行压缩操作,以便取回存储空间。 如果不在缓存中有“空洞”的情况下调用Bitmap::cache,则可以避免这种“高成本”方法。

下图描述了原理:

  1. 在上一个已分配位图之上分配缓存:

存储空间中位图的分配顺序

  1. 删除后标记未使用的存储空间:

删除已缓存位图后缓存中的未使用存储空间

  1. 分配下一张位图时,会压缩缓存并在顶部进行分配:

在缓存位图之前,缓存取回未使用的存储空间

  1. 在删除最顶端(上一次分配)的位图时,会立即释放存储空间及其下方的任何空闲存储空间:

删除最顶端位图缓存

在这种情况下,下一次缓存操作将不包括压缩。

该动画展示了此代码的完整顺序:

Bitmap::cache(BITMAP_BITMAP1_ID);
Bitmap::cache(BITMAP_BITMAP2_ID);
Bitmap::cache(BITMAP_BITMAP3_ID);
...
Bitmap::cacheRemoveBitmap(BITMAP_BITMAP2_ID);
...
Bitmap::cache(BITMAP_BITMAP4_ID);
...
Bitmap::cacheRemoveBitmap(BITMAP_BITMAP3_ID);
Bitmap::cacheRemoveBitmap(BITMAP_BITMAP4_ID);

缓存和未缓存位图