Caching Bitmaps
In this section we will discuss the bitmap cache in TouchGFX. The bitmap cache is a dedicated RAM buffer where bitmaps can be stored (or cached) by the application. If a bitmap is cached, TouchGFX will automatically use the RAM cache as pixel source when drawing the bitmap.
Bitmap caching can be beneficial in many cases. Reading data from RAM is often faster than reading from flash (especially when using the Texturemapper because it uses non-linear memory access), so caching to RAM can increase the performance of your UI. Be aware that caching from internal flash to external RAM can reduce performance. Caching to RAM also allows you to use the flash for other purposes like log files while showing your UI, because bitmaps will be read from RAM (in some cases writing to a flash requires it to be non-memory mapped). It can also be useful when you need to modify the pixels of a bitmap and therefore need the bitmap to be in modifiable memory.
For performance reasons, TouchGFX requires all graphics data stored in external flash to be directly accessible (through a pointer), without going through a driver layer. This means that TouchGFX cannot render directly from a non-memory mapped flash (like an SD-card). To overcome this limitation the bitmap cache provides a mechanism for caching some or all of the bitmap data in RAM during power-up. Bitmap caching is therefore useful when you need to store your bitmaps on slow external storage like a USB-disk or SD-card.
Setup the Bitmap Cache
In order to use the bitmap caching feature, you need to first provide
a bitmap cache configuration to TouchGFX, and secondly (in some cases)
to provide a hardware specific implementation of the BlockCopy
function for
reading data from your external storage.
Bitmap Cache Configuration
The bitmap cache configuration consists of a pointer to a buffer and the size of the buffer. These two values must be provided to TouchGFX in the call to Bitmap::setCache. This call is normally found in the FrontendApplication.cpp file:
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);
}
In the above example a 3 MB buffer in external memory is passed to TouchGFX as bitmap cache. The address is selected by the application programmer. In the next example we just declare an array and just pass the address and size of the array. The specific location of the array will depend on your linker script. This method is most often used when creating a (small) bitmap cache in internal 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));
}
Enabling Bitmap cache with TouchGFX Generator
If you are using CubeMX and TouchGFX Generator, enabling and configuring of the bitmap cache could also be done in TouchGFXHAL.cpp. First the any existing Bitmap cache is removed, hereafter a new cache is set based on the memory area provided.
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));
}
You don't need to remove the cache, if you only set it once in your application.
If you need to cache all your bitmaps, of course the size of the cache must be large enough to contain all your bitmap data. Note: There is a small amount of memory used for bookkeeping (8 bytes x number of bitmaps in the application), so you must allocate slightly more memory than actually needed for the raw pixel data. This amount depends on the number of bitmaps in your application, but a few kilobytes of additional memory is usually enough.
BlockCopy Copies Data from Flash to the Cache
When you cache a bitmap, TouchGFX copies the pixels from the original
location to the bitmap cache using the BlockCopy
function in the
HAL class.
If your bitmaps are stored in normal addressable flash (like internal flash or a memory mapped external flash like a QSPI-flash), you do not need to do anything. The built-in implementation works fine.
On the other hand, if your bitmaps are stored in flash that is not addressable, e.g. a filesystem or non-memory mapped flash, then the standard copy method is not sufficient and you need to provide an updated version that is able to read from your specific flash storage.
Read more about this topic Using Non-Memory Mapped flash for storing images section.
Cache Operations
The bitmap caching operations are all placed in the Bitmap
class:
Bitmap caching method | Description |
---|---|
bool Bitmap::cache(BitmapId id) | This method caches a bitmap. The bitmap is only cached if enough unused memory is available in the cache. Returns true if the bitmap was cached. Caching an already cached bitmap does not do any work. |
bool Bitmap::cacheReplaceBitmap(BitmapId out, BitmapId in) | This method replaces a bitmap (out) in the cache with another bitmap (in). The method will only succeed if the bitmap to be replaced is already cached and if the bitmaps have the same size (in bytes). |
bool Bitmap::cacheRemoveBitmap(BitmapId id) | This method removes a bitmap from the cache. The memory used by the bitmap can be used for caching of another bitmap afterwards. |
void Bitmap::clearCache() | This method removes all the cached bitmaps from the cache. |
void Bitmap::cacheAll() | This method caches all bitmaps. It can not be used if the amount of RAM allocated for the cache (or available) is less than the total size of the bitmaps. |
Cache Strategies
When the amount of RAM that you can allocate for your bitmap cache is less than the total size of the bitmaps you can not cache all the bitmaps during startup. You can e.g. select to cache only the bitmaps needed for the first screen. When you change between your screens you can remove some or all of the cached bitmaps and cache the bitmaps needed for the next screen. This is exemplified in the next section.
Cache Bitmap on a Screen Basis
Your application user interface is composed of a set of Views. The
Views probably all use some bitmaps. A simple strategy for caching is
to cache all the bitmaps used by a View in the View::setupScreen
method and clear the cache in the
View::tearDownScreen
method:
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();
}
The memory requirement for the cache is the size of the bitmaps used
by the screen with the biggest use of bitmaps.
The drawback of this method is that if two Views
both use a
bitmap, the bitmap will be erased from the cache on exit from the
first View
and cached again on entry to the second View
.
The Bitmap::cacheRemoveBitmap can be used to selective uncache bitmaps and thus reduce this overhead. The drawback of the cacheRemoveBitmap is that the cache memory will be fragmented.
Another general drawback of caching is that when you change your UI (e.g. adding a button) you may need to update the caching code to include the new bitmap.
Replace the Background Bitmaps in the Cache
If your application has a set of minor bitmaps (e.g. icons) and some large full screen "background" bitmaps another strategy can be advised:
Cache all the small bitmaps prior to entering the first screen. A good place to do this is in the FrontendApplication constructor. Also cache the background bitmap for the first screen:
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
}
In the View::setupScreen
method replace the
cached background bitmap with the required bitmap:
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
}
The memory requirement for the cache using this strategy is the size of the cached bitmaps and one background bitmap. Compared to the previous method the code is simpler to maintain as the views have less code. The performance is better as we move less bitmaps in and out of the cache.
The cacheReplaceBitmap operation is preferable to the cacheRemoveBitmap method as it does not fragment the memory.
Cache Memory Management
In order to get the full effect of the bitmap caching it is necessary to understand the internal operations of the cache.
The cache is implemented as a stack. New bitmaps are cached after the previously cached bitmaps. Memory used by a bitmap is marked as "free" when the bitmap is removed from the cache, but the memory is not immediately useable unless the removed bitmap was on top of the stack. If the bitmap was in "the middle" of the cache a compacting operation is performed the next time Bitmap::cache is called to reclaim the memory. This "costly" method can be avoided if you do not call Bitmap::cache with a "hole" in the cache.
The drawings below illustrates the principles:
- Caching allocates on top of the previously allocated bitmaps:
- Removal marks the memory unused:
- Allocating the next bitmap compacts the cache and allocates on the top:
- When you remove the topmost (last allocated) bitmap, the memory is freed immediately along with any free memory just below it:
The next cache operation will in this case not involve a compact.
This animation shows the whole sequence for this code:
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);