Skip to main content

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 methodDescription
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:

  1. Caching allocates on top of the previously allocated bitmaps:

Allocation sequence of bitmaps in memory

  1. Removal marks the memory unused:

Unused memory in cache after removal of cached bitmap

  1. Allocating the next bitmap compacts the cache and allocates on the top:

The cache reclaims unused memory before caching a bitmap

  1. When you remove the topmost (last allocated) bitmap, the memory is freed immediately along with any free memory just below it:

Topmost bitmap cache removal

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);

Caching and uncaching bitmaps