V8 display driver (double buffer) low FPS & high CPU

I’m waiting for this to be resolved before trying this out. Will post my results when I’ve done so.

I need some more clarity regarding the implementation.

I’ve read that in direct mode, LVGL first redraws all the dirty areas in the screen-sized buffer and then calls the flush function, then in that case what is the meaning of checking lv_disp_flush_is_last(drv) == true because only 1 chunk is being flushed right? (Q1)

Let’s say we have FB1 and FB2 and currently FB1 is active and flush_cb is called, we have to do 2 tasks. 1st copy the invalidated areas from FB1 to FB2 and 2nd, flush the unioned invalidated of FB1 to the screen.

Q2 Can you please comment on the ordering of the 2 tasks?
Q3 How will the FB2 become active for the next rendering? will LVGL internally take care of it or, do I have to do it some way in flush_cb()? If later then please suggest how?

Q4. If suppose, a rendering request comes while we are busy with the first task 1. in flush_cb then how it will be handled, please comment.

By accident flush_cb was called for all areas. So it’s a bug and lv_disp_flush_is_last(drv) == true is the workaround.

You should switch first. Else you will copy to the active frame buffer,

It’s managed by LVGL if you pass 2 buffers to lv_draw_buf_t.

LVGL won’t render anything until you free the buffer by calling lv_disp_flush_ready().

Hi @kisvegabor,
I’m developing on imx RT1176 nxp eval board, I’ve managed to compile the demo project (lv_demo_widgets) with the latest lvgl revision from github. I setup doublebuffering (2 framebuffers), full_refresh=0 and direct_mode=1, so far it seems to work, but the rendering has a weird behaviour, like you can see in the attached images where there is the screen as it appears after pressing the “logout” button and after moving the slider. My screen is 720x1280 RGB565.

Am I missing something in the flush display callback? here to code I used:

static void DEMO_FlushDisplay(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    g_dc.ops->setFrameBuffer(&g_dc, 0, (void*) color_p);

	lv_disp_t *disp = _lv_refr_get_disp_refreshing();

    if(lv_disp_flush_is_last(disp->driver))
    {
		uint8_t *fb;
		//fb is the framebuffer to syncronize
		if((uint8_t*)color_p==(uint8_t*)&s_frameBuffer[0])
			fb = (uint8_t*)&s_frameBuffer[1];
		else
			fb = (uint8_t*)&s_frameBuffer[0];

		for (int i = 0; i < disp->inv_p; i++)
		{
			if(disp->inv_area_joined[i])
				continue;

			//copy area from color_p to fb
			copy_area_to_fb(fb, &(disp->inv_areas[i]), color_p);
		}
    }

    lv_disp_flush_ready(disp->driver);
}	

after pressing “logout”


look at the “invite” button truncated
after moving the slider

look at the “hardworking” switch which is now all grey (like “team player”)

thank you

You need full_refresh=1 and direct_mode=0 if you have “normal” double buffering.

Hi @kisvegabor,
what I try to achive is full_refresh=0 and direct_mode=1, like you suggested in a previous post:

many thanks for the reply,
gianluca

Hi @gianlucacornacchia ,

My hardware will be different to yours but I am doing the same thing from a hardware/software functional point of view.

You haven’t shared all of your relevant code but here is my approach, hopefully it will show something to help:

void gui_update_thread(void *p) {

	cpu0_globals->spawn_stat |= GUI_RUN;
	// Initialise VGA Hardware
	set_vga_prams( VGA_1440X900_60HZ_CVTR );
	// Initialise GUI
	lv_init();
	lv_theme_default_init(cpu0_globals->gui.disp, shmem_p->personality == OTG_IDU ? confp->sys.IDU_gui_colour :
	  confp->sys.ODU_gui_colour, lv_palette_main(LV_PALETTE_PURPLE), (((shmem_p->personality == OTG_IDU) ? confp->sys.IDU_style :
	  confp->sys.ODU_style) ? 0 : 1), LV_FONT_DEFAULT);
	lv_disp_drv_init((lv_disp_drv_t*)&cpu0_globals->gui.disp_drv);
	lv_disp_draw_buf_init(&cpu0_globals->gui.disp_buf, (void*)LV_VDB_ADR, (void*)LV_VDB2_ADR, (LV_HOR_RES_MAX*LV_VER_RES_MAX));
	cpu0_globals->gui.disp_drv.flush_cb = vga_disp_flush;
	cpu0_globals->gui.disp_drv.hor_res = LV_HOR_RES_MAX;                 /*Set the horizontal resolution in pixels*/
	cpu0_globals->gui.disp_drv.ver_res = LV_VER_RES_MAX;                 /*Set the vertical resolution in pixels*/
	cpu0_globals->gui.disp_drv.draw_buf = &cpu0_globals->gui.disp_buf;
	cpu0_globals->gui.disp_drv.full_refresh = pdFALSE;
	cpu0_globals->gui.disp_drv.direct_mode = pdTRUE;
	cpu0_globals->gui.disp = lv_disp_drv_register((lv_disp_drv_t*)&cpu0_globals->gui.disp_drv);
	lv_disp_set_bg_opa(NULL, LV_OPA_TRANSP);
	startup_gui_create();
	lv_timer_create((lv_timer_cb_t)process_msg_q, 10, NULL);	// Check for GUI thread messages every 10ms
	while(1) {
		lv_task_handler();
		vTaskDelay(pdMS_TO_TICKS(4));
	}
}

void vga_irq_handler( void *p ) {

	vga->vga_fbuf_addr = cpu0_globals->gui.dma_src;
	cpu0_globals->gui.buf_switched = pdTRUE;
	XScuGic_Disable(&xInterruptController, XPAR_FABRIC_VGA_IP_0_FRM_CPT_IRQ_INTR);
	lv_disp_flush_ready((lv_disp_drv_t*)&cpu0_globals->gui.disp_drv);
}

static void update_dual_buf( lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *colour_p ) {

	lv_disp_t*	disp = _lv_refr_get_disp_refreshing();
	lv_coord_t	y, hres = lv_disp_get_hor_res(disp);
    uint16_t	a;
    lv_color_t	*buf_cpy;

    if( colour_p == disp_drv->draw_buf->buf1)
        buf_cpy = disp_drv->draw_buf->buf2;
    else
        buf_cpy = disp_drv->draw_buf->buf1;

    for(a = 0; a < disp->inv_p; a++) {
    	if(disp->inv_area_joined[a]) continue;
        lv_coord_t w = lv_area_get_width(&disp->inv_areas[a]);
        for(y = disp->inv_areas[a].y1; y <= disp->inv_areas[a].y2 && y < disp_drv->ver_res; y++) {
            memcpy(buf_cpy+(y * hres + disp->inv_areas[a].x1), colour_p+(y * hres + disp->inv_areas[a].x1), w * sizeof(lv_color_t));
        }
    }
}

void vga_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *colour_p) {

	static uint8_t	first_call = 1;

	if( first_call ) {
		cpu0_globals->gui.dma_src = (uint32_t)colour_p;
		first_call =  0;
		vga->total_pixels &= ~DMA_FIFO_RST; 	// Release Reset
		vga->total_pixels |= DMA_FRAME_READY;	// Start Proceedings
		vga->irq_reg = VGA_IRQ_EN;				// Enable DMA complete interrupt
	}
	if( lv_disp_flush_is_last( disp_drv ) ) {
		cpu0_globals->gui.dma_src = (uint32_t)colour_p;
		cpu0_globals->gui.buf_switched = pdFALSE;
		XScuGic_Enable(&xInterruptController, XPAR_FABRIC_VGA_IP_0_FRM_CPT_IRQ_INTR);
		while(!cpu0_globals->gui.buf_switched);// vTaskDelay(1);
		update_dual_buf(disp_drv, area, colour_p);
	}

	lv_disp_flush_ready( disp_drv );
}

Also it may be worth checking your artefacts aren’t being caused by an unflushed cache.

Kind Regards,

Pete

Hi @pete-pjb,
sorry for late answer but I’ve been busy in the last days. Thank you for sharing your code, but as far as I see the update code works the same as mine. I suspect the issue maybe in the semaphore handling. In the next days I will upload my code to show the complete implementation.

regards,
Luca

Hi Luca ( @gianlucacornacchia ) ,

Thanks for letting me know.

I have also been helping another developer here with this issue. It might be worth you keeping an eye on this thread also. I agree you should definitely engineer the semaphore out of the driver. It is not good to have blocking code in the LVGL flush mechanism this is definitely a source of performance issues in my opinion. :slight_smile:

Kind Regards,

Pete

Hi @pete-pjb,
I’ve implemented the code following your suggestions in the other answer, here the code:

void lv_port_disp_init(void)
{
    memset(s_frameBuffer, 0, sizeof(s_frameBuffer));
    lv_disp_draw_buf_init(&disp_buf, s_frameBuffer[0], s_frameBuffer[1], LCD_WIDTH * LCD_HEIGHT);

    status_t status;
    dc_fb_info_t fbInfo;

#if LV_USE_GPU_NXP_VG_LITE
    /* Initialize GPU. */
    BOARD_PrepareVGLiteController();
#endif

    /*-------------------------
     * Initialize your display
     * -----------------------*/
    BOARD_PrepareDisplayController();

    status = g_dc.ops->init(&g_dc);
    if (kStatus_Success != status)
    {
        assert(0);
    }

    g_dc.ops->getLayerDefaultConfig(&g_dc, 0, &fbInfo);
    fbInfo.pixelFormat = DEMO_BUFFER_PIXEL_FORMAT;
    fbInfo.width       = DEMO_BUFFER_WIDTH;
    fbInfo.height      = DEMO_BUFFER_HEIGHT;
    fbInfo.startX      = DEMO_BUFFER_START_X;
    fbInfo.startY      = DEMO_BUFFER_START_Y;
    fbInfo.strideBytes = DEMO_BUFFER_STRIDE_BYTE;
    g_dc.ops->setLayerConfig(&g_dc, 0, &fbInfo);
    g_dc.ops->setCallback(&g_dc, 0, DEMO_BufferSwitchOffCallback, &disp_drv);

#if defined(SDK_OS_FREE_RTOS)
    s_transferDone = xSemaphoreCreateBinary();
    if (NULL == s_transferDone)
    {
        PRINTF("Frame semaphore create failed\r\n");
        assert(0);
    }
#else
    s_transferDone = false;

    /* lvgl starts render in frame buffer 0, so show frame buffer 1 first. */
    g_dc.ops->setFrameBuffer(&g_dc, 0, (void *)s_frameBuffer[1]);

    /* Wait for frame buffer sent to display controller video memory. */
    if ((g_dc.ops->getProperty(&g_dc) & kDC_FB_ReserveFrameBuffer) == 0)
    {
#if defined(SDK_OS_FREE_RTOS)
        if (xSemaphoreTake(s_transferDone, portMAX_DELAY) != pdTRUE)
        {
            PRINTF("Wait semaphore error: s_transferDone\r\n");
            assert(0);
        }
#else
        while (false == s_transferDone)
        {
        }
#endif
    }

    g_dc.ops->enableLayer(&g_dc, 0);

    /*-----------------------------------
     * Register the display in LittlevGL
     *----------------------------------*/

    lv_disp_drv_init(&disp_drv); /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = LCD_WIDTH;
    disp_drv.ver_res = LCD_HEIGHT;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = DEMO_FlushDisplay;
    disp_drv.wait_cb = DEMO_WaitFlush;

#if (LV_USE_GPU_NXP_VG_LITE || LV_USE_GPU_NXP_PXP)
    disp_drv.clean_dcache_cb = DEMO_CleanInvalidateCache;
#endif

    /*Set a display buffer*/
    disp_drv.draw_buf = &disp_buf;

    disp_drv.full_refresh = 0;
    disp_drv.direct_mode = 1;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);

#if LV_USE_GPU_NXP_VG_LITE
    if (vg_lite_init(64, 64) != VG_LITE_SUCCESS)
    {
        PRINTF("VGLite init error. STOP.");
        vg_lite_close();
        assert(0);
    }
#endif
}

static void DEMO_BufferSwitchOffCallback(void *param, void *switchOffBuffer)
{
    lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)param;

    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);

#if defined(SDK_OS_FREE_RTOS)
    BaseType_t taskAwake = pdFALSE;

    xSemaphoreGiveFromISR(s_transferDone, &taskAwake);
    portYIELD_FROM_ISR(taskAwake);
#else
    s_transferDone = true;
#endif
}

#if (LV_USE_GPU_NXP_VG_LITE || LV_USE_GPU_NXP_PXP)
static void DEMO_CleanInvalidateCache(lv_disp_drv_t *disp_drv)
{
#if __CORTEX_M == 4
    L1CACHE_CleanInvalidateSystemCache();
#else
    SCB_CleanInvalidateDCache();
#endif
}
#endif

static void DEMO_WaitFlush(lv_disp_drv_t *disp_drv)
{
    if (xSemaphoreTake(s_transferDone, portMAX_DELAY) != pdTRUE)
    {
        PRINTF("Display flush failed\r\n");
        assert(0);
    }
}

static void DEMO_UpdateDualBuffer( lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *colour_p )
{

	lv_disp_t*	disp = _lv_refr_get_disp_refreshing();
	lv_coord_t	y, hres = lv_disp_get_hor_res(disp);
    uint16_t	a;
    lv_color_t	*buf_cpy;

    if( colour_p == disp_drv->draw_buf->buf1)
        buf_cpy = disp_drv->draw_buf->buf2;
    else
        buf_cpy = disp_drv->draw_buf->buf1;

    for(a = 0; a < disp->inv_p; a++) {
    	if(disp->inv_area_joined[a]) continue;  /* Only copy areas which aren't part of another area */
        lv_coord_t w = lv_area_get_width(&disp->inv_areas[a]);
        for(y = disp->inv_areas[a].y1; y <= disp->inv_areas[a].y2 && y < disp_drv->ver_res; y++) {
            memcpy(buf_cpy+(y * hres + disp->inv_areas[a].x1), colour_p+(y * hres + disp->inv_areas[a].x1), w * sizeof(lv_color_t));
        }
    }
}

static void DEMO_FlushDisplay(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    /*
     * Before new frame flushing, clear previous frame flush done status.
     */
    (void)xSemaphoreTake(s_transferDone, 0);

/*CHANGE 4*/
	if( lv_disp_flush_is_last( disp_drv ) ) {
		DCACHE_CleanInvalidateByRange((uint32_t)color_p, DEMO_FB_SIZE);
		g_dc.ops->setFrameBuffer(&g_dc, 0, (void *)color_p);
		DEMO_UpdateDualBuffer(disp_drv, area, color_p);
	    //s_framePending = true;
	}
	else
		lv_disp_flush_ready( disp_drv );
}

without the lv_disp_flush_ready() call in change 4 the application locks.

in the following video the results, as you can see the rendering has many defects:
video (valid for 1 week)

What can be the cause?

thank you,
gianluca

Hi @gianlucacornacchia ,

It looks like you are using a slightly different CPU to the other thread can you confirm exactly which hardware/dev board you are using, or log into your NXP account and grab the link to share the SDK you created for your NXP IDE and post it so I can download it?

Kind Regards,

Pete

Hi @pete-pjb,
I’m using the nxp MIMXRT1170-EVK board with rt1176, sdk 2.11.1.

I’ve updated the lvgl sources, I’m using rev 1ab9aa53125c8db4b6067bd3495a0a345e3a6a23 from official repo.

thank you,
regards

Hi @gianlucacornacchia ,

Having gone onto NXP’s website and created a SDK 2.11.1 build and downloaded it, I have compared your code to the original and the only obvious issue I can see is there appears to be a #endif pre-processor missing which may cause a problem as the dual buffer needs to be flipped at the beginning before processing starts with out the flip the buffer being drawn is likely the one being updated which I suspect would cause the type of problem you are seeing so I would try modifying your
lv_port_disp_init() as follows:

void lv_port_disp_init(void)
{
    memset(s_frameBuffer, 0, sizeof(s_frameBuffer));
    lv_disp_draw_buf_init(&disp_buf, s_frameBuffer[0], s_frameBuffer[1], LCD_WIDTH * LCD_HEIGHT);

    status_t status;
    dc_fb_info_t fbInfo;

#if LV_USE_GPU_NXP_VG_LITE
    /* Initialize GPU. */
    BOARD_PrepareVGLiteController();
#endif

    /*-------------------------
     * Initialize your display
     * -----------------------*/
    BOARD_PrepareDisplayController();

    status = g_dc.ops->init(&g_dc);
    if (kStatus_Success != status)
    {
        assert(0);
    }

    g_dc.ops->getLayerDefaultConfig(&g_dc, 0, &fbInfo);
    fbInfo.pixelFormat = DEMO_BUFFER_PIXEL_FORMAT;
    fbInfo.width       = DEMO_BUFFER_WIDTH;
    fbInfo.height      = DEMO_BUFFER_HEIGHT;
    fbInfo.startX      = DEMO_BUFFER_START_X;
    fbInfo.startY      = DEMO_BUFFER_START_Y;
    fbInfo.strideBytes = DEMO_BUFFER_STRIDE_BYTE;
    g_dc.ops->setLayerConfig(&g_dc, 0, &fbInfo);
    g_dc.ops->setCallback(&g_dc, 0, DEMO_BufferSwitchOffCallback, &disp_drv);

#if defined(SDK_OS_FREE_RTOS)
    s_transferDone = xSemaphoreCreateBinary();
    if (NULL == s_transferDone)
    {
        PRINTF("Frame semaphore create failed\r\n");
        assert(0);
    }
#else
    s_transferDone = false;
#endif                     /*********** THIS LINE ADDED!!! ***************/
    /* lvgl starts render in frame buffer 0, so show frame buffer 1 first. */
    g_dc.ops->setFrameBuffer(&g_dc, 0, (void *)s_frameBuffer[1]);

    /* Wait for frame buffer sent to display controller video memory. */
    if ((g_dc.ops->getProperty(&g_dc) & kDC_FB_ReserveFrameBuffer) == 0)
    {
#if defined(SDK_OS_FREE_RTOS)
        if (xSemaphoreTake(s_transferDone, portMAX_DELAY) != pdTRUE)
        {
            PRINTF("Wait semaphore error: s_transferDone\r\n");
            assert(0);
        }
#else
        while (false == s_transferDone)
        {
        }
#endif
    }

    g_dc.ops->enableLayer(&g_dc, 0);

    /*-----------------------------------
     * Register the display in LittlevGL
     *----------------------------------*/

    lv_disp_drv_init(&disp_drv); /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = LCD_WIDTH;
    disp_drv.ver_res = LCD_HEIGHT;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = DEMO_FlushDisplay;
    disp_drv.wait_cb = DEMO_WaitFlush;

#if (LV_USE_GPU_NXP_VG_LITE || LV_USE_GPU_NXP_PXP)
    disp_drv.clean_dcache_cb = DEMO_CleanInvalidateCache;
#endif

    /*Set a display buffer*/
    disp_drv.draw_buf = &disp_buf;

    disp_drv.full_refresh = 0;
    disp_drv.direct_mode = 1;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);

#if LV_USE_GPU_NXP_VG_LITE
    if (vg_lite_init(64, 64) != VG_LITE_SUCCESS)
    {
        PRINTF("VGLite init error. STOP.");
        vg_lite_close();
        assert(0);
    }
#endif
}

I hope that is the answer!

Let me know how it goes.

Kind Regards,

Pete

Hi @pete-pjb,
I think I have found the issue, when vglite is enabled there are rendering issues, no matter if pxp is enabled. Disabling vglite everything works as expected (with or without pxp).

Maybe there’s a bug when both vglite and directmode are enabled?

thanks for reviewing my code.

regards,
gianluca

Hi @gianlucacornacchia ,

I’m glad you’ve found the issue. :slight_smile:
Presumably someone at NXP maintains the PXP and VGLITE code for the LVGL integration? It might be worth contacting NXP support to point out the issue?

Kind Regards,

Pete

Hi @gianlucacornacchia ,

I noticed this Github thread has a lot of activity associated with the development of the NXP drivers it might be worth speaking with the developers associated with that thread…

Hope that helps.

Kind Regards,

Pete

Hi @pete-pjb,
I’ve seen the thread and I think I will file new bug report for this issue.

thank you

Regards,
Gianluca

1 Like

We are working on low-power wearable devices and want to integrate the LVGL UX framework… onto our device… we want to take professional help from your side. If you can provide an official channel for this kind of conversation… please help.

Hi,

We are doing consulting, UI design and UI implementation services. Please write me at gabor@lvgl.io.

Hi @pete-pjb @kisvegabor, this is my test:

Target:

  • disp: 800X480
  • ram: <120K
  • psram: 8M

lvgl version:
8.3.3

Question:
In demo 1, may be the implementation B is the best, but in demo 2, it’s the worst. so I need know which implementation is best on my device?

note:
Implementation B: It is implemented according to @pete-pjb code
Implementation C: in flush_cb, copy data to 800X480X2 psram.