跳轉到主要內容

快取點陣圖

在本節中,我們將討論TouchGFX中的點陣圖快取。 點陣圖快取是專用RAM緩衝區,可將點陣圖保存(或快取)在應用程式中。 如果快取了點陣圖,在繪製點陣圖時,TouchGFX將自動使用RAM的快取作為像素來源。

點陣圖快取在許多情況下十分有用。 從RAM讀取資料通常比從快閃記憶體讀取要快(特別是在使用紋理映射器時,原因是它使用非線性記憶體存取),因此,快取到RAM可提升UI性能。 注意,從內部快閃記憶體快取到外部RAM可能會降低性能。 如果緩存到RAM,由於將從RAM讀取點陣圖(在某些情況下,寫入快閃記憶體要求是非記憶體映射快閃記憶體),因此可以在顯示UI的同時將快閃記憶體用於其他目的(如日誌檔)。 當您需要修改點陣圖像素時,點陣圖需位於可修改的記憶體中,這一點很實用。

出於性能方面的考慮,TouchGFX要求存儲在外部快閃記憶體中的所有圖形資料都能直接存取(通過指標),無需通過驅動層。 這意味著TouchGFX不能從非記憶體映射快閃記憶體(如SD卡)直接渲染。 為了打破這一限制,點陣圖快取提供了一種在上電期間快取RAM中的部分或全部點陣圖資料的機制。 因此,當您需要將點陣圖保存在慢速外部記憶體(如硬碟或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方法更好。

快取存儲空間管理

為了獲得點陣圖快取的完整效果,必須理解快取的內部操作。

快取以棧(stack)的形式實現。 在之前快取的點陣圖之後快取新點陣圖。 當點陣圖從快取中刪除時,點陣圖使用的存儲空間會被標記為“free”,但不能立即使用該存儲空間,除非刪除的點陣圖位於棧(stack)頂部。 如果點陣圖位於快取的“中間”,將在下一次呼叫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);

快取和未快取點陣圖