mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-29 22:56:58 +08:00
feat(display): add triple buffer support (#8158)
Signed-off-by: yushuailong1 <yushuailong1@xiaomi.com>
This commit is contained in:
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user