feat(display): add triple buffer support (#8158)

Signed-off-by: yushuailong1 <yushuailong1@xiaomi.com>
This commit is contained in:
yushuailong
2025-05-08 22:31:36 +08:00
committed by GitHub
parent 558409ad28
commit 77a1a14d37
7 changed files with 166 additions and 6 deletions
@@ -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 can continue drawing. Doing so allows *rendering* and *refreshing* the
display to become parallel operations. 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: .. _flush_callback:
+21 -1
View File
@@ -497,7 +497,22 @@ static void refr_sync_areas(void)
/*The buffers are already swapped. /*The buffers are already swapped.
*So the active buffer is the off screen buffer where LVGL will render*/ *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 * 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 hor_res = lv_display_get_horizontal_resolution(disp_refr);
uint32_t ver_res = lv_display_get_vertical_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 #endif
lv_draw_buf_copy(off_screen, sync_area, on_screen, sync_area); 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*/ /*Clear sync areas*/
@@ -1325,6 +1342,9 @@ static void draw_buf_flush(lv_display_t * disp)
if(disp->buf_act == disp->buf_1) { if(disp->buf_act == disp->buf_1) {
disp->buf_act = disp->buf_2; 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 { else {
disp->buf_act = disp->buf_1; disp->buf_act = disp->buf_1;
} }
+13
View File
@@ -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; 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, void lv_display_set_buffers(lv_display_t * disp, void * buf1, void * buf2, uint32_t buf_size,
lv_display_render_mode_t render_mode) 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; disp->layer_head->color_format = color_format;
if(disp->buf_1) disp->buf_1->header.cf = 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_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); 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); 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_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; return buf_size;
} }
+7
View File
@@ -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); 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 * Set display render mode
* @param disp pointer to a display * @param disp pointer to a display
+1
View File
@@ -65,6 +65,7 @@ struct _lv_display_t {
*--------------------*/ *--------------------*/
lv_draw_buf_t * buf_1; lv_draw_buf_t * buf_1;
lv_draw_buf_t * buf_2; lv_draw_buf_t * buf_2;
lv_draw_buf_t * buf_3;
/** Internal, used by the library*/ /** Internal, used by the library*/
lv_draw_buf_t * buf_act; lv_draw_buf_t * buf_act;
+80 -5
View File
@@ -39,11 +39,14 @@ typedef struct {
void * mem; void * mem;
void * mem2; void * mem2;
void * mem3;
void * mem_off_screen; void * mem_off_screen;
uint32_t mem2_yoffset; uint32_t mem2_yoffset;
uint32_t mem3_yoffset;
lv_draw_buf_t buf1; lv_draw_buf_t buf1;
lv_draw_buf_t buf2; lv_draw_buf_t buf2;
lv_draw_buf_t buf3;
} lv_nuttx_fb_t; } 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 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_get_pinfo(int fd, struct fb_planeinfo_s * pinfo);
static int fbdev_init_mem2(lv_nuttx_fb_t * dsc); 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_refr_timer_cb(lv_timer_t * tmr);
static void display_release_cb(lv_event_t * e); static void display_release_cb(lv_event_t * e);
#if defined(CONFIG_FB_UPDATE) #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); lv_draw_buf_init(&dsc->buf1, w, h, color_format, stride, dsc->mem, data_size);
/* Check buffer mode */ /* 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(double_buffer) {
if((ret = fbdev_init_mem2(dsc)) < 0) { if((ret = fbdev_init_mem2(dsc)) < 0) {
goto errout; 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_draw_buf_init(&dsc->buf2, w, h, color_format, stride, dsc->mem2, data_size);
lv_display_set_draw_buffers(disp, &dsc->buf1, &dsc->buf2); 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 { else {
dsc->mem_off_screen = malloc(data_size); 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) #if defined(CONFIG_FB_UPDATE)
/*May be some direct update command is required*/ /*May be some direct update command is required*/
int yoffset = disp->buf_act == disp->buf_1 ? int yoffset = 0;
0 : dsc->mem2_yoffset; 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 */ /* Join the areas to update */
lv_area_t final_inv_area; lv_area_t final_inv_area;
lv_memzero(&final_inv_area, sizeof(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) { if(disp->buf_act == disp->buf_1) {
dsc->pinfo.yoffset = 0; dsc->pinfo.yoffset = 0;
} }
else { else if(disp->buf_act == disp->buf_2) {
dsc->pinfo.yoffset = dsc->mem2_yoffset; 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) { if(ioctl(dsc->fd, FBIOPAN_DISPLAY, (unsigned long)((uintptr_t) & (dsc->pinfo))) < 0) {
LV_LOG_ERROR("ioctl(FBIOPAN_DISPLAY) failed: %d", errno); 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; 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) static void display_release_cb(lv_event_t * e)
{ {
lv_display_t * disp = (lv_display_t *) lv_event_get_user_data(e); lv_display_t * disp = (lv_display_t *) lv_event_get_user_data(e);
+36
View File
@@ -169,4 +169,40 @@ void test_display_matrix_rotation(void)
#endif #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 #endif