ビットマップのキャッシュ
このセクションでは、TouchGFXのビットマップ・キャッシュについて説明します。 ビットマップ・キャッシュとは、アプリケーションによってビットマップを保存(つまりキャッシュ)できる専用のRAMバッファのことです。 ビットマップがキャッシュされると、TouchGFXは、ビットマップを描画するときのピクセル・ソースとして、そのRAMキャッシュを自動的に使用することになります。
ビットマップのキャッシュは、多くの場合に有益です。 RAMからデータを読み取る方がFlashから読み取るよりも高速であることが多い(特に、非リニアなメモリ・アクセスを使用するTexturemapperの使用時)ので、RAMにキャッシュすることによってUIのパフォーマンスが向上することになります。 ただし、内部Flashから外部RAMにキャッシュするとパフォーマンスが低下するので注意してください。 RAMへのキャッシュによって、UIを表示するときのログ・ファイルなど、Flashを別の目的で使用できるようにもなります。ビットマップがRAMから読み込まれるからです(一部のケースでは、Flashに書き込むためにFlashを非メモリ・マップドにする必要があります)。 ビットマップのピクセルを変更する必要があるときには、ビットマップを変更可能なメモリ内に配置する必要があるので、この方法は有益です。
パフォーマンス上の理由から、TouchGFXでは、すべてのグラフィック・データを外部Flashに配置して、ドライバ・レイヤを介さずに(ポインタを通して)直接アクセスできるようにする必要があります。 つまり、TouchGFXでは非メモリ・マップドのFlash(SDカードなど)から直接描画することはできないのです。 この制限を解決するために、ビットマップ・キャッシュは、電源立ち上げ時に一部またはすべてのビットマップ・データをRAMにキャッシュするメカニズムを提供しています。 このため、ビットマップをUSBディスクやSDカードなどの低速の外部ストレージに保存する必要がある場合には、ビットマップ・キャッシュが役立つのです。
ビットマップ・キャッシュの設定
ビットマップ・キャッシュ機能を使用するには、まずTouchGFXでビットマップ・キャッシュ機能を設定し、次に(場合によっては)、外部ストレージからデータを読み取るために、ハードウェア固有のBlockCopy
関数を実装する必要があります。
ビットマップ・キャッシュ設定
ビットマップ・キャッシュ設定は、バッファへのポインタとバッファのサイズからなります。 これら2つの値を、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);
}
上の例では、外部メモリにある3MBのバッファが、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::removeCache(); //Clear any previous cache
touchgfx::Bitmap::setCache(cache, sizeof(cache));
}
アプリケーション内でのキャッシュの設定が一度きりの場合は、キャッシュを削除する必要はありません。
すべてのビットマップをキャッシュする必要がある場合、当然ながらキャッシュのサイズは、すべてのビットマップ・データを格納するのに十分な大きさにする必要があります。 注意: ブックキーピングに少量のメモリ(8バイト x アプリケーション内のビットマップ数)を使用するため、実際に未加工(raw)のピクセル・データ用に必要な分よりも、わずかに大きいメモリを割り当てる必要があります。 この分量はアプリケーション内のビットマップ数に応じて異なりますが、通常は数キロバイトの追加メモリで十分です。
BlockCopyで、Flashからキャッシュへのデータ・コピー
ビットマップをキャッシュする際、TouchGFXでは、HALクラス内のBlockCopy
関数を使用して、ピクセルが元の場所からビットマップ・キャッシュにコピーされます。
ビットマップが通常のアドレス指定可能なFlash(内部Flashや、QSPI Flashのようなメモリ・マップドの外部Flashなど)に保存されている場合、ユーザは何もする必要がありません。 組込みの実装機能が自動で行います。
一方で、ビットマップがアドレス指定できないFlash(ファイルシステムや非メモリ・マップドFlashなど)に保存されている場合は、標準のコピー方法では不十分なので、特定のFlashストレージから読み取りできるよう更新されたバージョンを用意する必要があります。
このトピックの詳細については、「画像保存のために非メモリ・マップドFlashを使用」セクションを参照してください。
キャッシュの操作
ビットマップ・キャッシュの操作は、すべてBitmap
クラスで実行されます。
Bitmap キャッシュのメソッド | 説明 |
---|---|
bool Bitmap::cache(BitmapId id) | このメソッドはビットマップをキャッシュします。 十分な量の未使用のメモリがキャッシュ内で使用可能な場合のみ、ビットマップがキャッシュされます。 ビットマップがキャッシュされたらtrueを返します。 すでにキャッシュされているビットマップをキャッシュしようとした場合、何もしません。 |
bool Bitmap::cacheReplaceBitmap(BitmapId out, BitmapId in) | このメソッドは、キャッシュ内のビットマップを外に出して、別のビットマップに置き換えます(中に入れます)。 このメソッドが成功するのは、置き換えられるビットマップがすでにキャッシュ済みで、双方のビットマップが同じサイズ(バイト数)の場合のみです。 |
bool Bitmap::cacheRemoveBitmap(BitmapId id) | このメソッドは1つのビットマップをキャッシュから削除します。 そのビットマップが使用していたメモリは、後で別のビットマップをキャッシュするために使用できます。 |
void Bitmap::clearCache() | このメソッドはキャッシュされているすべてのビットマップをキャッシュから削除します。 |
void Bitmap::cacheAll() | このメソッドはすべてのビットマップをキャッシュします。 キャッシュに割り当てられた(使用可能な)RAMの量が、ビットマップの合計サイズよりも小さい場合は使用できません。 |
キャッシュの戦略
ビットマップ・キャッシュに割り当てることのできるRAMの量がビットマップの合計サイズよりも小さい場合、起動時にすべてのビットマップをキャッシュすることはできません。 この場合は、最初のスクリーンに必要なビットマップのみをキャッシュするなどの方法を選択できます。 スクリーン間を移行するときには、キャッシュされたビットマップの一部またはすべてを削除し、次のスクリーンに必要なビットマップをキャッシュします。 次のセクションでこの例を示します。
スクリーン・ベースでビットマップをキャッシュ
アプリケーションのユーザ・インタフェースは、一連のビューで構成されます。 おそらくすべてのビューがいくらかのビットマップを使用します。 キャッシュの単純な方法は、1つのビューで使用するすべてのビットマップを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();
}
キャッシュのメモリ要件は、一番大きなビットマップを使用するスクリーンによって使用されるビットマップのサイズです。 この方法の欠点は、2つのView
がどちらも同じ1つのビットマップを使用する場合、最初のView
の終了時にそのビットマップがキャッシュから消去され、2つ目のView
のエントリ時に再びキャッシュされることです。
Bitmap::cacheRemoveBitmapを使用すると、ビットマップを選択的にキャッシュ削除し、このオーバーヘッドを削減することができます。 cacheRemoveBitmapの欠点は、キャッシュ・メモリが断片化することです。
キャッシュのもう1つの一般的な欠点は、ボタンの追加など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
}
この戦略を使用する場合のキャッシュのメモリ要件は、キャッシュされているビットマップのサイズと1つのバックグラウンド・ビットマップのサイズの合計です。 前のメソッドと比べて、ビューに使用するコードが少ないので、コードの維持が簡単になります。 キャッシュを出入りするビットマップが少ないので、パフォーマンスも向上します。
メモリが断片化しないので、cacheReplaceBitmapの操作の方がcacheRemoveBitmapメソッドより望ましいと言えます。
キャッシュ・メモリの管理
ビットマップ・キャッシュの完全な効果を得るには、キャッシュの内部動作を理解する必要があります。
キャッシュはスタックとして実装されます。 新しいビットマップは、前にキャッシュされたビットマップの後にキャッシュされます。 ビットマップによって使用されたメモリは、そのビットマップがキャッシュから削除されると「解放(free)」とマークされますが、削除されたビットマップがスタックの先頭になかった場合、メモリはすぐに使用可能にはなりません。 そのビットマップがキャッシュの「中ほど」にあった場合、次にBitmap::cacheが呼び出されたときに、メモリを回収するためのコンパクト(圧縮)操作が実行されます。 この「コスト高な」方法は、「穴」のあるキャッシュでBitmap::cacheを呼び出さなければ回避できます。
以下の図は、この原理をわかりやすく示すものです。
- 以前に割り当てられたビットマップの上に、キャッシュが割り当てられます。
- 削除されたメモリは未使用とマークされます。
- 次のビットマップを割り当てると、キャッシュがコンパクト化され、一番上に割り当てられます。
- 一番上の(最後に割り当てられた)ビットマップを削除すると、そのすぐ下の解放済みのメモリと共にメモリがすぐに解放されます。
この場合、次のキャッシュ操作ではコンパクト化は必要ありません。
以下のアニメーションは、次のコードのシーケンス全体を示しています。
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);