diff --git a/docs/src/details/main-modules/display/setup.rst b/docs/src/details/main-modules/display/setup.rst index 109a53d113..e152ef27c8 100644 --- a/docs/src/details/main-modules/display/setup.rst +++ b/docs/src/details/main-modules/display/setup.rst @@ -116,6 +116,14 @@ other hardware should be used to transfer data to the display so the MCU can continue drawing. Doing so allows *rendering* and *refreshing* the display to become parallel operations. +Three Buffers +------------- + +Triple buffering enhances parallelism between rendering and data transfer compared +to double buffering. When one buffer has completed rendering and another is actively +undergoing DMA transfer, the third buffer enables immediate rendering of the next frame, +eliminating CPU/GPU idle time caused by waiting for DMA completion. +The third buffer is configured using the :cpp:func:`lv_display_set_3rd_draw_buffer` function. .. _flush_callback: diff --git a/src/core/lv_refr.c b/src/core/lv_refr.c index 33da47e530..5ddfecb518 100644 --- a/src/core/lv_refr.c +++ b/src/core/lv_refr.c @@ -497,7 +497,22 @@ static void refr_sync_areas(void) /*The buffers are already swapped. *So the active buffer is the off screen buffer where LVGL will render*/ lv_draw_buf_t * off_screen = disp_refr->buf_act; - lv_draw_buf_t * on_screen = disp_refr->buf_act == disp_refr->buf_1 ? disp_refr->buf_2 : disp_refr->buf_1; + /*Triple buffer sync buffer for off-screen2 updates.*/ + lv_draw_buf_t * off_screen2; + lv_draw_buf_t * on_screen; + + if(disp_refr->buf_act == disp_refr->buf_1) { + off_screen2 = disp_refr->buf_2; + on_screen = disp_refr->buf_3 ? disp_refr->buf_3 : disp_refr->buf_2; + } + else if(disp_refr->buf_act == disp_refr->buf_2) { + off_screen2 = disp_refr->buf_3 ? disp_refr->buf_3 : disp_refr->buf_1; + on_screen = disp_refr->buf_1; + } + else { + off_screen2 = disp_refr->buf_1; + on_screen = disp_refr->buf_2; + } uint32_t hor_res = lv_display_get_horizontal_resolution(disp_refr); uint32_t ver_res = lv_display_get_vertical_resolution(disp_refr); @@ -551,6 +566,8 @@ static void refr_sync_areas(void) } #endif lv_draw_buf_copy(off_screen, sync_area, on_screen, sync_area); + if(off_screen2 != on_screen) + lv_draw_buf_copy(off_screen2, sync_area, on_screen, sync_area); } /*Clear sync areas*/ @@ -1325,6 +1342,9 @@ static void draw_buf_flush(lv_display_t * disp) if(disp->buf_act == disp->buf_1) { disp->buf_act = disp->buf_2; } + else if(disp->buf_act == disp->buf_2) { + disp->buf_act = disp->buf_3 ? disp->buf_3 : disp->buf_1; + } else { disp->buf_act = disp->buf_1; } diff --git a/src/display/lv_display.c b/src/display/lv_display.c index 57129f141c..01ca536425 100644 --- a/src/display/lv_display.c +++ b/src/display/lv_display.c @@ -441,6 +441,17 @@ void lv_display_set_draw_buffers(lv_display_t * disp, lv_draw_buf_t * buf1, lv_d disp->buf_act = disp->buf_1; } +void lv_display_set_3rd_draw_buffer(lv_display_t * disp, lv_draw_buf_t * buf3) +{ + if(disp == NULL) disp = lv_display_get_default(); + if(disp == NULL) return; + + LV_ASSERT_MSG(disp->buf_1 != NULL, "buf1 is null"); + LV_ASSERT_MSG(disp->buf_2 != NULL, "buf2 is null"); + + disp->buf_3 = buf3; +} + void lv_display_set_buffers(lv_display_t * disp, void * buf1, void * buf2, uint32_t buf_size, lv_display_render_mode_t render_mode) { @@ -528,6 +539,7 @@ void lv_display_set_color_format(lv_display_t * disp, lv_color_format_t color_fo disp->layer_head->color_format = color_format; if(disp->buf_1) disp->buf_1->header.cf = color_format; if(disp->buf_2) disp->buf_2->header.cf = color_format; + if(disp->buf_3) disp->buf_3->header.cf = color_format; lv_display_send_event(disp, LV_EVENT_COLOR_FORMAT_CHANGED, NULL); } @@ -1149,6 +1161,7 @@ uint32_t lv_display_get_invalidated_draw_buf_size(lv_display_t * disp, uint32_t LV_ASSERT(disp->buf_1 && disp->buf_1->data_size >= buf_size); if(disp->buf_2) LV_ASSERT(disp->buf_2->data_size >= buf_size); + if(disp->buf_3) LV_ASSERT(disp->buf_3->data_size >= buf_size); return buf_size; } diff --git a/src/display/lv_display.h b/src/display/lv_display.h index cded4bf4ad..ddcd1ee059 100644 --- a/src/display/lv_display.h +++ b/src/display/lv_display.h @@ -292,6 +292,13 @@ void lv_display_set_buffers_with_stride(lv_display_t * disp, void * buf1, void * */ void lv_display_set_draw_buffers(lv_display_t * disp, lv_draw_buf_t * buf1, lv_draw_buf_t * buf2); +/** + * Set the third draw buffer for a display. + * @param disp pointer to a display + * @param buf3 third buffer + */ +void lv_display_set_3rd_draw_buffer(lv_display_t * disp, lv_draw_buf_t * buf3); + /** * Set display render mode * @param disp pointer to a display diff --git a/src/display/lv_display_private.h b/src/display/lv_display_private.h index 5b67b820e6..8417d061cb 100644 --- a/src/display/lv_display_private.h +++ b/src/display/lv_display_private.h @@ -65,6 +65,7 @@ struct _lv_display_t { *--------------------*/ lv_draw_buf_t * buf_1; lv_draw_buf_t * buf_2; + lv_draw_buf_t * buf_3; /** Internal, used by the library*/ lv_draw_buf_t * buf_act; diff --git a/src/drivers/nuttx/lv_nuttx_fbdev.c b/src/drivers/nuttx/lv_nuttx_fbdev.c index 272f1fb6e6..1057c3bee2 100644 --- a/src/drivers/nuttx/lv_nuttx_fbdev.c +++ b/src/drivers/nuttx/lv_nuttx_fbdev.c @@ -39,11 +39,14 @@ typedef struct { void * mem; void * mem2; + void * mem3; void * mem_off_screen; uint32_t mem2_yoffset; + uint32_t mem3_yoffset; lv_draw_buf_t buf1; lv_draw_buf_t buf2; + lv_draw_buf_t buf3; } lv_nuttx_fb_t; /********************** @@ -54,6 +57,7 @@ static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * colo static lv_color_format_t fb_fmt_to_color_format(int fmt); static int fbdev_get_pinfo(int fd, struct fb_planeinfo_s * pinfo); static int fbdev_init_mem2(lv_nuttx_fb_t * dsc); +static int fbdev_init_mem3(lv_nuttx_fb_t * dsc); static void display_refr_timer_cb(lv_timer_t * tmr); static void display_release_cb(lv_event_t * e); #if defined(CONFIG_FB_UPDATE) @@ -143,7 +147,7 @@ int lv_nuttx_fbdev_set_file(lv_display_t * disp, const char * file) lv_draw_buf_init(&dsc->buf1, w, h, color_format, stride, dsc->mem, data_size); /* Check buffer mode */ - bool double_buffer = dsc->pinfo.yres_virtual == (dsc->vinfo.yres * 2); + bool double_buffer = dsc->pinfo.yres_virtual >= (dsc->vinfo.yres * 2); if(double_buffer) { if((ret = fbdev_init_mem2(dsc)) < 0) { goto errout; @@ -151,6 +155,16 @@ int lv_nuttx_fbdev_set_file(lv_display_t * disp, const char * file) lv_draw_buf_init(&dsc->buf2, w, h, color_format, stride, dsc->mem2, data_size); lv_display_set_draw_buffers(disp, &dsc->buf1, &dsc->buf2); + + bool triple_buffer = dsc->pinfo.yres_virtual >= (dsc->vinfo.yres * 3); + if(triple_buffer) { + if((ret = fbdev_init_mem3(dsc)) < 0) { + goto errout; + } + + lv_draw_buf_init(&dsc->buf3, w, h, color_format, stride, dsc->mem3, data_size); + lv_display_set_3rd_draw_buffer(disp, &dsc->buf3); + } } else { dsc->mem_off_screen = malloc(data_size); @@ -258,9 +272,13 @@ static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * colo #if defined(CONFIG_FB_UPDATE) /*May be some direct update command is required*/ - int yoffset = disp->buf_act == disp->buf_1 ? - 0 : dsc->mem2_yoffset; - + int yoffset = 0; + if(disp->buf_act == disp->buf_2) { + yoffset = dsc->mem2_yoffset; + } + else if(disp->buf_act == disp->buf_3) { + yoffset = dsc->mem3_yoffset; + } /* Join the areas to update */ lv_area_t final_inv_area; lv_memzero(&final_inv_area, sizeof(final_inv_area)); @@ -282,9 +300,12 @@ static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * colo if(disp->buf_act == disp->buf_1) { dsc->pinfo.yoffset = 0; } - else { + else if(disp->buf_act == disp->buf_2) { dsc->pinfo.yoffset = dsc->mem2_yoffset; } + else if(disp->buf_act == disp->buf_3) { + dsc->pinfo.yoffset = dsc->mem3_yoffset; + } if(ioctl(dsc->fd, FBIOPAN_DISPLAY, (unsigned long)((uintptr_t) & (dsc->pinfo))) < 0) { LV_LOG_ERROR("ioctl(FBIOPAN_DISPLAY) failed: %d", errno); @@ -395,6 +416,60 @@ static int fbdev_init_mem2(lv_nuttx_fb_t * dsc) return 0; } +static int fbdev_init_mem3(lv_nuttx_fb_t * dsc) +{ + uintptr_t buf_offset; + struct fb_planeinfo_s pinfo; + int ret; + + lv_memzero(&pinfo, sizeof(pinfo)); + + /* Get display[2] planeinfo */ + + pinfo.display = dsc->pinfo.display + 2; + + if((ret = fbdev_get_pinfo(dsc->fd, &pinfo)) < 0) { + return ret; + } + + /* Check bpp */ + + if(pinfo.bpp != dsc->pinfo.bpp) { + LV_LOG_WARN("mem3 is incorrect"); + return -EINVAL; + } + + /* Check the buffer address offset, + * It needs to be divisible by pinfo.stride + */ + + buf_offset = pinfo.fbmem - dsc->mem; + + if((buf_offset % dsc->pinfo.stride) != 0) { + LV_LOG_WARN("It is detected that buf_offset(%" PRIuPTR ") " + "and stride(%d) are not divisible, please ensure " + "that the driver handles the address offset by itself.", + buf_offset, dsc->pinfo.stride); + } + + /* Calculate the address and yoffset of mem3 */ + + if(buf_offset == 0) { + dsc->mem3_yoffset = dsc->vinfo.yres * 2; + dsc->mem3 = pinfo.fbmem + dsc->mem3_yoffset * pinfo.stride; + LV_LOG_USER("Use consecutive mem3 = %p, yoffset = %" LV_PRIu32, + dsc->mem3, dsc->mem3_yoffset); + } + else { + dsc->mem3_yoffset = buf_offset / dsc->pinfo.stride; + dsc->mem3 = pinfo.fbmem; + LV_LOG_USER("Use non-consecutive mem3 = %p, yoffset = %" LV_PRIu32, + dsc->mem3, dsc->mem3_yoffset); + } + + return 0; +} + static void display_release_cb(lv_event_t * e) { lv_display_t * disp = (lv_display_t *) lv_event_get_user_data(e); diff --git a/tests/src/test_cases/test_display.c b/tests/src/test_cases/test_display.c index 2439a7ce0d..9fafd48d59 100644 --- a/tests/src/test_cases/test_display.c +++ b/tests/src/test_cases/test_display.c @@ -169,4 +169,40 @@ void test_display_matrix_rotation(void) #endif } +static void dummy_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p) +{ + LV_UNUSED(area); + LV_UNUSED(color_p); + lv_display_flush_ready(disp); +} + +void test_display_triple_buffer(void) +{ + lv_display_t * disp = lv_display_create(480, 320); + lv_display_set_flush_cb(disp, dummy_flush_cb); + lv_draw_buf_t * buf1 = lv_draw_buf_create(480, 320, LV_COLOR_FORMAT_NATIVE, 0); + lv_draw_buf_t * buf2 = lv_draw_buf_create(480, 320, LV_COLOR_FORMAT_NATIVE, 0); + lv_draw_buf_t * buf3 = lv_draw_buf_create(480, 320, LV_COLOR_FORMAT_NATIVE, 0); + lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT); + lv_display_set_draw_buffers(disp, buf1, buf2); + lv_display_set_3rd_draw_buffer(disp, buf3); + + lv_obj_invalidate(lv_display_get_screen_active(disp)); + lv_display_refr_timer(lv_display_get_refr_timer(disp)); + TEST_ASSERT_EQUAL(lv_display_get_buf_active(disp), buf2); + + lv_obj_invalidate(lv_display_get_screen_active(disp)); + lv_display_refr_timer(lv_display_get_refr_timer(disp)); + TEST_ASSERT_EQUAL(lv_display_get_buf_active(disp), buf3); + + lv_obj_invalidate(lv_display_get_screen_active(disp)); + lv_display_refr_timer(lv_display_get_refr_timer(disp)); + TEST_ASSERT_EQUAL(lv_display_get_buf_active(disp), buf1); + + lv_display_delete(disp); + lv_draw_buf_destroy(buf1); + lv_draw_buf_destroy(buf2); + lv_draw_buf_destroy(buf3); +} + #endif