emWin Multi-Buffering

From SEGGER Wiki
Revision as of 13:25, 2 June 2020 by Florian (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Multiple Buffering is a method of using more than one frame buffer to avoid tearing and flickering effects as well as a visible build up of the screen content. While one buffer is visible the other buffers are used to pre-render the screen content.

How it works

When using the Multi-Buffering feature of emWin more than just one frame buffer available will be used (1 front buffer and multiple back buffers). Typically three buffers are getting used with emWin but it is possible to use only two buffers, too.

While one buffer is visible and gets display by the LCD controller the other buffer(s) are used to pre-render the next drawing operations.

The process of multi buffering gets started by calling GUI_MULTIBUF_Begin() which causes copying the current front buffer into the next back buffer. This is required to make sure both buffers have the same content. Any drawing operation after calling GUI_MULTIBUF_Begin() will be performed into the back buffer. After finishing the last drawing operation a call of GUI_MULTIBUF_End() will make the back buffer visible (it becomes the new front buffer) and the previous front buffer becomes the new back buffer.

The buffer switch gets performed on a VSYNC interrupt make sure the switch gets performed within the blanking period of the display. If the buffer switch would occur at any time tearing effects might become visible.

Double buffering vs. triple buffering

With emWin you can choose between double and triple buffering. Both have advantages and disadvantages. Although, it is possible to use more than three buffers, it doesn't make sense.

Double buffering

With double buffering only 2 buffers are available: One front and one back buffer.

The advantage is the smaller RAM requirement, due to a smaller number of buffers.

The disadvantage is the fact that emWin has to wait for the next VSYNC interrupt performing the buffer switch before continuing the drawing process.

Triple buffering

Triple buffering makes use of three buffers: One front buffer and two back buffers.

The advantage of using three buffers is that emWin does not have to wait for the buffer switch to occur. emWin simply uses the the other free back buffer and can continue the drawing operations without waiting for the VSYNC interrupt.

The disadvantage is the higher amount of RAM occupied by the additional buffer.

Requirements

Hardware

Since Multi-Buffering makes use of multiple frame buffers the memory requirements are relatively high. Each additional buffer increases the required memory about the size of the framebuffer. Besides of enough memory it is mandatory to have a interrupt routine which gets called on occurrence of the VSYNC signal of the LCD controller.

Software

The Multi-Buffering feature is available for the GUIDRV_Lin driver of emWin.

The GUIDRV_S1D13L01 makes use of Multi-Buffering, too, but this feature is implemented as part of the LCD controller and will not be covered in this article.

Other drivers, like the GUIDRV_FlexColor driver, can use a cache and do not offer Multi-Buffering.

Configuration for Multi-Buffering

This section does not cover the set up of the hardware and it is presupposed to have a basic configuration with a running LCD controller using the GUIDRV_Lin driver and the ability to show something on the display with emWin. Typically these settings are performed in a configuration file named LCDConf.c.

As a first step we will set up Multi-Buffering with three buffers.

Adapt the frame buffer

Most likely there is already a definition for the frame buffer. In this case the size of the frame buffer can be simply increase by the number of buffers to be used (NUM_BUFFERS). Just make sure NUM_BUFFERS is not 0.

#define NUM_BUFFERS  (3)

static U8 _aVRAM[XSIZE_PHYS * YSIZE_PHYS * BYTE_PER_PIXEL * NUM_BUFFERS] __attribute__ ((section (".SDRAM1.GUI_RAM")));

Adapt the display driver configuration

In the function LCD_X_Config() the driver gets configured in regards of screen size, orientation, hardware interface, etc.. This is a good point to emWin that Multi-Buffer is available and how many buffers are used.

Simply add the following to this function.

void LCD_X_Config(void) {
  //
  // ...
  //
  #if (NUM_BUFFERS > 1)
    GUI_MULTIBUF_ConfigEx(0, NUM_BUFFERS);
  #endif
  //
  // ...
  //
}

Adapt the display driver callback function

Every emWin driver makes use of the callback function LCD_X_DisplayDriver(). This callback function makes it possible to react on certain events triggered by the driver. More information on this callback function can be found in the emWin user manual.

The following case needs to be added to your LCD_X_DisplayDriver() callback function. When either the user or the Window Manager of emWin calls the function GUI_MULTIBUF_End() the GUIDRV_Lin driver calls LCD_X_DisplayDriver() with the command LCD_X_SHOWBUFFER to indicate which buffer should become visible.

Depending on how many buffers are used the handling of the LCD_X_SHOWBUFFER command is slightly different.

With three buffers only the index of the buffer which should become visible gets stored in _PendingBuffer and emWin continuous the drawing operation.

With only two buffers emWin has to wait till the VSYNC interrupt clears the _PendingBuffer flag (shown further below). In _SwitchBuffersOnVSYNC() the program waits for the _PendingBuffers flag to be cleared. Once cleared the buffers can be switched and emWin gets told which buffer has become visible.

#if (NUM_BUFFERS == 2)
static void _SwitchBuffersOnVSYNC(int Index, int LayerIndex) {
  U32 Addr;

  for (_PendingBuffer = 1; _PendingBuffer; );  // Wait until _PendingBuffer is cleared by ISR
  //
  // Calculate address of buffer to be used  as visible frame buffer
  //
  Addr = _aAddr[LayerIndex] + _axSize[LayerIndex] * _aySize[LayerIndex] * Index * _aBytesPerPixels[LayerIndex];
  //
  // Store address into SFR
  //
  LCD_ControllerFunc(Addr);  // Set FB address to LCD controller
  //
  // Tell emWin that buffer is used
  //
  GUI_MULTIBUF_ConfirmEx(LayerIndex, Index);
}
#endif

int LCD_X_DisplayDriver(unsigned LayerIndex, unsigned Cmd, void * pData) {
  //
  // ...
  //
  case LCD_X_SHOWBUFFER: {
    LCD_X_SHOWBUFFER_INFO * p;

    p = (LCD_X_SHOWBUFFER_INFO *)pData;
#if (NUM_BUFFERS == 2)
    _SwitchBuffersOnVSYNC(p->Index, LayerIndex);
#else
    _PendingBuffer = p->Index;
#endif
    break;
  }
  //
  // ...
  //
}

React on the VSYNC interrupt

In the VSYNC interrupt routine the buffer index stored in _PendingBuffer will be used to calculate the correct frame buffer address and to tell emWin (GUI_MULTIBUF_ConfirmEx()) which buffer has become visible.

With only two buffers the _PendingBuffer flag simply gets cleared.

Such an interrupt routine might look like below.

void LCD_TFT_IRQHandler(void);
void LCD_TFT_IRQHandler(void) {
#if (NUM_BUFFERS == 3)
  U32 Addr;

  if (_PendingBuffer >= 0) {
    //
    // Calculate address of buffer to be used  as visible frame buffer
    //
    Addr = &_aVRAM[XSIZE_PHYS * YSIZE_PHYS * BYTE_PER_PIXEL * _PendingBuffer];
    //
    // Store address into SFR
    //
    LCD_ControllerFunc(Addr);  // Set FB address to LCD controller
    //
    // Tell emWin that buffer is used
    //
    GUI_MULTIBUF_ConfirmEx(0, _PendingBuffer);
    //
    // Clear pending buffer flag of layer
    //
    _PendingBuffer = -1;
  }
#else
  _PendingBuffer = 0;
#endif
}

Using Multi-Buffering in your application

There are two ways to use Multi-Buffering in an application. Either by triggering start and end of Multi-Buffering on your own or by using the Window Manager.

Multi-Buffering with the Window Manager

When the Window Manager gets used a simple call of WM_MULTIBUF_Enable(1); is sufficient to make use of multiple buffers. The Window Manager calls automatically GUI_MULTIBUF_Begin() before the first drawing operation of the first invalid window and calls GUI_MULTIBUF_End() after the last drawing operation of the last invalid window.

void MainTask(void) {
  GUI_Init();
  WM_MULTIBUF_Enable(1);
  WM_SetCallback(WM_HBKWIN, _cbBk);
  while (1) {
    GUI_Delay(100);
  }
}

Manual use of Multi-Buffering

When using Multi-Buffering manually a call of GUI_MULTIBUF_Begin() before and GUI_MULTIBUF_End() after a drawing operation is required.

void MainTask(void) {
  GUI_COLOR aColor[] = { GUI_RED, GUI_GREEN, GUI_BLUE };
  U32       Index;

  GUI_Init();
  Index = 0;
  while (1) {
    GUI_MULTIBUF_Begin();  // Copy front to back buffer
    GUI_SetBkColor(aColor[Index]);
    Index = (Index == GUI_COUNTOF(aColor)) ? 0 : Index;
    GUI_Clear();
    GUI_SetColor(aColor[Index]);
    Index = (Index == GUI_COUNTOF(aColor)) ? 0 : Index;
    GUI_DrawLine(0, 0, 50, 50);
    GUI_MULTIBUF_End();  // Switch buffers
    GUI_Delay(100);
  }
}