Pre-rendered Layer-Screens

Last modified by Microchip on 2024/07/29 16:08

Introduction

This example application shows you how to use multiple Microchip Graphics Suite (MGS) Harmony Composer layers and canvas objects to create pre-rendered 'layer-screens.' Layer-screens are logical screens that the application can use to show different frames on the display with GFX Canvas.

Since these layer-screens are completely rendered during initialization and are persistent throughout the lifecycle of the application, these screen transitions are very quick and require minimal processing.

In addition, effects like sliding screen transitions can be applied using the GFX Canvas and the LCD overlays. This enables smooth screen transitions with no frame redraw and minimal system overhead.

Concept and Theory

Before proceeding, you must be familiar with the GFX Canvas. Refer to the "Using the Graphics Canvaspage for more information.

To support pre-rendered 'screens,' the application utilizes the ability of the MGS Harmony Composer and library to draw multiple layers to dedicated frame buffers using GFX Canvas.

Each layer represents a logical screen in the application, which we will refer to as 'layer-screen' in order to differentiate it from a regular screen that's used in a multi-screen MGS Harmony Composer design. Since each layer-screen uses a dedicated frame buffer, the size of memory available in the target device determines the maximum number of layer-screens that can be created. Figure 1 describes this configuration.

UI Design to Canvas Theory

Figure 1

  1. At design time, each screen in the Graphical User Interface (GUI) is designed as a layer in MGS Harmony Composer. 
  2. MGS Harmony Composer will be a single-screen project with multiple layers. Each layer represents a logical screen in the GUI, which we will refer to as a layer-screen.
  3. At run-time, each layer-screen in MGS Harmony Composer will be associated with a GFX Canvas object.
  4. At boot, the library will render each of these layer-screens drawn in a dedicated frame buffer in RAM.
  5. The application will use the GFX Canvas Application Programming Interface (APIs) to show or hide layer-screens by assigning a layer or overlay in the LCD controller and enabling or disabling those LCD layers. 
  6. The LCD controller will display and composite the visible layer-screens on the display. For LCD controllers with multiple layers/overlays, layer screens can be overlaid on top of another to create effects like semi-transparent windows or dialog boxes.

This mechanism of showing or hiding layer-screens effectively simulates screen transitions in the GUI. Since the layer-screens are already rendered in dedicated frame buffers, these screen transitions do not require a redraw of the screen and are quick to complete.

Also, since the widgets in the layer-screens are stored by the MGS library as layers in a screen, the handle and objects for these widgets are persistent in memory. Widget states are preserved during layer-screen transitions.

Microchip Graphics Suite (MGS) Example Project

Refer to the project in GitHub for the example.

The application is configured as shown in Figure 2. 

UI Design to Canvas

Figure 2

The example application has six layer-screens. Each layer screen is designed in MGS Harmony Composer as discrete layers.

At run-time, these layers are rendered to dedicated GFX Canvas objects with their associated frame buffers in Random-access Memory (RAM). The main screen provides menu buttons that will show different screens when pressed. These screen transitions are accomplished by calling the GFX Canvas APIs to hide, show, or move these canvas objects using the associated LCD layers or overlays. 

For these specific applications, the main screen and screens 1-4 are shown using the LCD base layer. The popup screen is designed to slide over the main screen so that it uses the LCD overlay above the base layer.

Back to Top

MGS Simulator Output

Here's the MGS Simulator output. The simulator output is interactive, use your mouse to click the buttons and interact with the GUI.

Figure 3: Canvas Pre-rendered Screens Example Simulator Output (Interactive)

At boot, the main menu screen is shown.

Main Screen

Figure 4

Pressing the buttons labeled 1-4 will show screens 1-4.

Showing Screen 1

Figure 5

On each of these screens, pressing the buttons will toggle the button. Notice that the states of the button will persist between screen transitions (i.e., a pressed button remains pressed even after exiting the screen). 

Layer-screen shown with button pressed.

Figure 6

Pressing the back arrow button at the bottom left will return to the main screen.

On the main menu screen, pressing the Pop Up button will slide up a semi-transparent pop-up screen.

Pop up screen

Figure 7

Pressing the back arrow will slide the pop-up screen down.

Back to Top

MPLAB® Code Configurator (MCC) Harmony Configuration

Here's how the project is configured in MPLAB® Code Configurator (MCC) Harmony.

Canvas component in MCC Project

Figure 8

The Graphics Canvas component is added to the project and connected between the MGS GFX library (Legato) and the MGS Simulator components.

Note that this project graph is for a simulator-based project. Projects that would run on actual HW would have the simulator replaced with the display driver and peripheral library components for the target device.

The Graphics Canvas component is configured as shown in Figure 9. There are six canvas objects and each canvas object is associated with a layer-screen in the GUI design.

Canvas Settings in MCC Project

Figure 9

The GFX Canvas objects are configured with the same color mode, size, and buffer allocation. 

When configuring for an embedded target, the frame buffer allocation type must be compatible with the type that is supported by the target device. Refer to the "Using the Graphics Canvas" page for more information about frame buffer allocations.

Back to Top

MGS Harmony Composer Design

Project Settings

The MGS Harmony Composer project was created with the display settings set to WQVGA, and width and height are set to 480x272.

Project Display Settings

Figure 10

The project renderer color mode is set to RGBA8888 to allow layer-screens with alpha values. This enables complex effects like overlaying semi-transparent screens.

Project Color Mode Settings

Figure 11

Screen Design

The MGS Composer design looks like the one shown in Figure 12. There's only one screen (Screen 0) in the design, and its tree contains six layers that each represent a layer-screen in the GUI.

Composer Design

Figure 12

Screen Event Callbacks

Note that the On Show and On Update screen event callbacks are enabled. These callbacks allow the application to initialize the screen-layers and show the main screen at boot. This will be covered in more detail in the the Application Code section of this user guide.

Screen Events Property

Figure 13: Enable On Show and On Update screen events

Button Widget Event Callbacks

Released event callbacks are enabled for buttons in the Main layer-screen. This allows the application to define a callback function that will be used to show the appropriate layer-screen using the GFX Canvas APIs.

Button Event Callbacks

Figure 14: Enable released event callbacks on Main Screen buttons

Released event callbacks are enabled for the Back/Home buttons in the sub layer-screens. This allows the application to define a callback function that will be used to go back and show the Main screen.

Back/Home button event enabled

Figure 15: Enable released event callbacks on Home/Back buttons

Using Background Panel Widgets

Notice a pattern on how each layer-screen tree is configured.

A background panel the size of the layer is used as parent to all the widgets in the layer-screen. 

Use a panel widget as background and parent to all widgets in the layer-screen.

Figure 16: Use a panel widget as background and parent to all widgets in the layer-screen.

This is important, as this parent panel widget is used as a container to enable or disable touch events on the layer-screen.

Since the application directly uses the LCD controller (via GFX Canvas) to hide or show a layer-screen on the display, the GFX library does not know which layer-screen is visible or hidden. To the GFX library, all these layers are visible and stacked on top of each other in the z-order that's designed in MGS Harmony Composer. This means that the top layer-screen will get all the touch events even if it is not being displayed.

To remedy this, the application must disable touch events on each layer-screen that is not displayed and only enable touch events on layer-screens that are shown. This is accomplished by using a background panel as parent or container for all the widgets in the layer screen.

Disabling the parent background panel disables all touch events on that parent-layer and lets touch events propagate to parent-layers below it. Enabling the parent background panel allows touch events to all its child widgets.

Using Editor Options to Manage the UI Design

Since MGS Composer layers are used to design layer-screens in the UI, the project can grow and become difficult to view and edit with multiple layer-screens stacked on top of each other in the screen designer. 

To manage multiple layer-screens in the design, use the Hidden setting in the Editor Options to hide layers that you are not working on and show layers-screens that you need to design or configure. Selecting the layer-screen in the Screen Tree will show the layer editor options.

Hide unused layers in the Editor.

Figure 17: Hide unused layers in the Editor.

Back to Top

Application Code

The application screen code are located in the app_screen.c file.

Note that the OnShow and OnUpdate screen events were enabled.

The OnShow() event callback function is defined to initialize and hide all the layer-screens at boot.

void Screen0_OnShow(void)
{
   /* Initialize all layer-screens */
    APP_Screen_Initialize();
   
   /* Hide all the layer-screens at boot */
    APP_LayerScreenHideAll();    
   
    appScreenState = APP_SCREEN_INIT;
}

The APP_Screen_Initialize() function contains the GFX Canvas API calls used to set the LCD layer, size, and position of each layer-screen.

void APP_Screen_Initialize(void)
{
   unsigned int i;
   
   for (i = 0; i < MAX_SCREENS; i++)
    {
       /* Set the LCD layers for each layer-screen */
        gfxcSetLayer(i, appLayerScreen[i].lcdLayer);
       
       /* WQVGA size for all layer-screens */
        gfxcSetWindowSize(i, DEFAULT_WIDTH, DEFAULT_HEIGHT);
       
       /* 0,0 position for all layer-screens */
        gfxcSetWindowPosition(i, 0, 0);
    }
}

 The OnShow() event callback function also calls APP_LayerScreenHideAll() to hide all the layer-screens at boot.

/* Helper function to hide all screens */
static void APP_LayerScreenHideAll(void)
{
   unsigned int i;
   
   for (i = 0; i < MAX_SCREENS; i++)
    {
        APP_LayerScreenHide(i);
    }
}

It uses the APP_LayerScreenHide() helper function that contains the GFX Canvas API calls to hide a layer-screen.

/* Helper function to hide a specific layer-screen */
static void APP_LayerScreenHide(uint32_t id)
{
   /* Hide the canvas layer */
    gfxcHideCanvas(id);
    gfxcCanvasUpdate(id);

   /* Disable the parent panel. This will disable events and allow events to
     * pass through to the lower level layer-screens */

    WIDGET_CLEAR_ENABLE_FLAG(*appLayerScreen[id].bkgdPanel);        
}

A helper function for showing a layer-screen is also defined and used in the application.

/* Helper function to show a specific layer-screen */
static void APP_LayerScreenShow(uint32_t id)
{
   /* Hide all layer-screens first */
    APP_LayerScreenHideAll();
   
   /* Show the layer-screen */
    gfxcShowCanvas(id);
    gfxcCanvasUpdate(id);
   
   /* Enable the layer-screen panel for event processing */
    WIDGET_SET_ENABLE_FLAG(*appLayerScreen[id].bkgdPanel);
}

Note the calls to macros for clearing and setting the widget-enabled flag.

/* Macros for setting the widget enabled flag
 * Enabled widgets get touch events.
 * Disabled widgets (and its child widgets) do not get touch events,
 * and allow events to go through it to lower level widgets.
 */

#define WIDGET_SET_ENABLE_FLAG(widget) (widget)->flags |= LE_WIDGET_ENABLED;
#define WIDGET_CLEAR_ENABLE_FLAG(widget) (widget)->flags &= ~LE_WIDGET_ENABLED;

The widget-enabled flag is used to enable or disable the parent background panel when a layer-screen is shown or hidden, respectively. This allows the application to indicate to the graphics library which layer-screens should receive and process touch events. 

For the sliding pop-up screen, the GFX Canvas move effect is used to slide the pop-up layer-screen from off-screen to view. The function APP_ScreenShowPop() shows the series of GFX Canvas API calls used to accomplish this.

/* Function to show the pop-up layer-screen */
static void APP_ScreenShowPop(void)
{
   /* Show the layer-screen */
    gfxcSetWindowPosition(POPUP, 0, 272);
    gfxcShowCanvas(POPUP);
    gfxcCanvasUpdate(POPUP);
   
   /* Start pop-up sliding from bottom to show layer-screen */
    gfxcStartEffectMove(POPUP,
                        GFXC_FX_MOVE_DEC,
                       0,
                       272,
                       0,
                       0,
                        LAYER_FX_MOVE_DELTA);
   
   /* Enable the pop-up panel so it can receive touch events */
    WIDGET_SET_ENABLE_FLAG(*appLayerScreen[POPUP].bkgdPanel);     
}

An equivalent function is defined to slide the pop-up layer-screen from view to off-screen. 

void APP_ScreenHidePop(void)
{
   /* Start pop-up sliding from top to hide layer-screen */
    gfxcStartEffectMove(POPUP,
                        GFXC_FX_MOVE_DEC,
                       0,
                       0,
                       0,
                       272,
                        LAYER_FX_MOVE_DELTA);
   
   /* Disable the parent panel. This will disable events */
    WIDGET_CLEAR_ENABLE_FLAG(*appLayerScreen[POPUP].bkgdPanel);   
       
}

The button event callback function does the appropriate helper function call to show the associated layer-screen.

void event_Screen0_Screen1Button_OnReleased(leButtonWidget* btn)
{
   /* Show Screen 1 layer-screen */
    APP_LayerScreenShow(SCREEN_1);
}

The screen OnUpdate() function manages the application state machine. Here it waits for the GFX library to completely render all the layer-screens before showing the main menu screen by calling the leRenderer_IsIdle() API.


void Screen0_OnUpdate(void)
{
   switch(appScreenState)
    {
       case APP_SCREEN_INIT:
        {
           /* Wait for the renderer to complete drawing all the layer-screens */
           if (leRenderer_IsIdle() == true)
               break;
           
           /* Show the main menu screen */
            APP_LayerScreenShow(MAIN_MENU);
           
            appScreenState = APP_SCREEN_RUNNING;
           
           break;
        }
       case APP_SCREEN_RUNNING:
        {
           break;
        }
       default:
           break;
    }
}

Refer to the "Microchip Graphics Suite (MGS) Graphics Canvas API Documentation" page for more details about the APIs used in this example project.

Back to Top

Design Guidelines and Recommendations

Use the following information to determine if pre-rendering with GFX Canvas will work for your UI design.

GUIs and the associated application code may need to be architected so that they can be efficiently implemented using GFX Canvas. Refer to the guidelines and recommendations for steps on how to optimize your GUI for pre-rendering.

  1. Determine if all the screens in your GUI design can be pre-rendered into RAM. This means that you must have enough RAM for the frame buffers needed to render all the screens at boot. 
  2. If there is not enough RAM to render all the screens, consider grouping similar screens as sub-screens to a superset screen.
    • The superset screen can be implemented as a layer-screen with multiple parent panels that contain the designs for the sub-screens. These panels can then be hidden or shown at run-time to show a specific sub-screen. Refer to the "How-To: Create Dialog Boxes" page for a guide on how to show/hide sub-screens.
    • Since these panels are hidden or shown at run-time within a single layer-screen, the GFX library will need to update and re-draw the sections of the layer-screen for these sub-screens.
    • Use the leRenderer_IsIdle() API to wait for the layer-screen to be updated with the new sub-screens before calling the GFX Canvas APIs to show the layer-screen.
  3. If a layer-screen needs to be updated (redrawn) by the GFX library, always use leRenderer_IsIdle() to make sure that the update is done before showing the layer-screen. This prevents screen tearing from being visible.
  4. For designs with a lot of screens, there will be a delay during boot while the GFX library is rendering the screens.
    • To promptly show an active screen while pre-rendering, consider showing a splash screen. This splash screen can be implemented by having the contents of only one layer-screen as visible by default (the splash screen) and having the other layer-screens invisible. Use the visible state of the parent panel widget to set the visible state of a layer-screen.
    • Once the splash layer-screen is rendered, use the GFX Canvas API to show the layer-screen on the display controller. 
    • While the splash layer-screen is visible, set the visible state of all the other layer-screens to visible so the GFX library will render them.
    • Keep the splash layer-screen visible while the other layer screens are being rendered. Use the leRenderer_IsIdle() API to wait for rendering to complete.
    • Once the GFX library is done rendering (leRenderer_IsIdle() returns LE_TRUE), use the GFX Canvas API to show the first screen on the LCD controller.
  5. The handles and objects for all the widgets in the layer-screens will be allocated in the GFX library's memory pool and will persist through the life cycle of the GUI application.
    • Make sure there is enough memory to hold all this widget information. Refer to the "Optimizing the Memory Manager Settings" for a guide on how to properly size the GFX memory pools for your application.

Back to Top