From 4170bcb1ee04719a8e27dd6f7c64d30ba88ead18 Mon Sep 17 00:00:00 2001 From: Brandon Holland <97706724+bhspyder@users.noreply.github.com> Date: Mon, 2 Mar 2026 23:07:44 -0800 Subject: [PATCH] feat(display): add display sync cb for custom frame buffer synchronization logic (#9342) (#9389) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lv_conf_template.h | 3 + src/core/lv_refr.c | 134 +++++++++++++++++++++++-------- src/display/lv_display.c | 26 ++++++ src/display/lv_display.h | 41 +++++++++- src/display/lv_display_private.h | 25 +++++- src/lv_conf_internal.h | 9 +++ src/misc/lv_event.c | 4 + src/misc/lv_event.h | 4 + 8 files changed, 209 insertions(+), 37 deletions(-) diff --git a/lv_conf_template.h b/lv_conf_template.h index 35d34974f5..df6a030760 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -611,6 +611,9 @@ /** Define a custom attribute for `lv_display_flush_ready` function */ #define LV_ATTRIBUTE_FLUSH_READY +/** Define a custom attribute for `lv_display_sync_ready` function */ +#define LV_ATTRIBUTE_SYNC_READY + /** Align VG_LITE buffers on this number of bytes. * @note vglite_src_buf_aligned() uses this value to validate alignment of passed buffer pointers. */ #define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1 diff --git a/src/core/lv_refr.c b/src/core/lv_refr.c index e34dd839ae..14e0748307 100644 --- a/src/core/lv_refr.c +++ b/src/core/lv_refr.c @@ -48,6 +48,8 @@ static uint32_t get_max_row(lv_display_t * disp, int32_t area_w, int32_t area_h) static void draw_buf_flush(lv_display_t * disp); static void call_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map); static void wait_for_flushing(lv_display_t * disp); +static void call_sync_cb(lv_display_t * disp, const lv_area_t * area); +static void wait_for_syncing(lv_display_t * disp); static lv_result_t layer_get_area(lv_layer_t * layer, lv_obj_t * obj, lv_layer_type_t layer_type, lv_area_t * layer_area_out, lv_area_t * obj_draw_size_out); static bool alpha_test_area_on_obj(lv_obj_t * obj, const lv_area_t * area); @@ -419,9 +421,10 @@ void lv_display_refr_timer(lv_timer_t * tmr) refr_invalid_areas(); if(disp_refr->inv_p == 0) goto refr_finish; - /*In double buffered direct mode save the updated areas. + /*In double buffered direct mode or if sync callback is set, save the updated areas. *They will be used on the next call to synchronize the buffers.*/ - if(lv_display_is_double_buffered(disp_refr) && disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_DIRECT) { + if((lv_display_is_double_buffered(disp_refr) && disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_DIRECT) || + disp_refr->sync_cb) { uint32_t i; for(i = 0; i < disp_refr->inv_p; i++) { if(disp_refr->inv_area_joined[i]) @@ -656,11 +659,11 @@ static void lv_refr_join_area(void) */ static void refr_sync_areas(void) { - /*Do not sync if not direct or double buffered*/ - if(disp_refr->render_mode != LV_DISPLAY_RENDER_MODE_DIRECT) return; - - /*Do not sync if not double buffered*/ - if(!lv_display_is_double_buffered(disp_refr)) return; + /*Do not sync if not direct double buffered and no sync callback set*/ + const bool auto_sync = disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_DIRECT && + lv_display_is_double_buffered(disp_refr); + const bool user_sync = disp_refr->sync_cb != NULL; + if(!auto_sync && !user_sync) return; /*Do not sync if no sync areas*/ if(lv_ll_is_empty(&disp_refr->sync_areas)) return; @@ -670,29 +673,6 @@ static void refr_sync_areas(void) /*We need to wait for ready here to not mess up the active screen*/ wait_for_flushing(disp_refr); - /*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; - /*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); - /*Iterate through invalidated areas to see if sync area should be copied*/ uint16_t i; int8_t j; @@ -728,6 +708,33 @@ 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; + /*Triple buffer sync buffer for off-screen2 updates.*/ + lv_draw_buf_t * off_screen2 = NULL; + lv_draw_buf_t * on_screen = NULL; + + /* Only compute buffer relationships when auto_sync (direct double-buffered) is used. + * When only user_sync is active, these pointers are not needed. */ + if(auto_sync) { + 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); + lv_area_t disp_area = {0, 0, (int32_t)hor_res - 1, (int32_t)ver_res - 1}; /*Copy sync areas (if any remaining)*/ for(sync_area = lv_ll_get_head(&disp_refr->sync_areas); sync_area != NULL; @@ -743,9 +750,22 @@ static void refr_sync_areas(void) lv_display_rotate_area(disp_refr, sync_area); } #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); + /*Call sync callback (if set)*/ + if(disp_refr->sync_cb) { + /*Set syncing flags*/ + disp_refr->syncing = true; + disp_refr->syncing_last = lv_ll_get_tail(&disp_refr->sync_areas) == sync_area; + + /*Call sync callback and wait for sync to complete*/ + call_sync_cb(disp_refr, sync_area); + wait_for_syncing(disp_refr); + } + /*Fallback to internal double buffered direct mode sync*/ + else { + 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*/ @@ -1464,3 +1484,51 @@ static void wait_for_flushing(lv_display_t * disp) LV_LOG_TRACE("end"); LV_PROFILER_REFR_END; } + +static void call_sync_cb(lv_display_t * disp, const lv_area_t * area) +{ + LV_PROFILER_REFR_BEGIN; + + /* Apply display offsets to the sync area for consistency with flush_cb */ + lv_area_t offset_area = *area; + offset_area.x1 += disp->offset_x; + offset_area.x2 += disp->offset_x; + offset_area.y1 += disp->offset_y; + offset_area.y2 += disp->offset_y; + + LV_TRACE_REFR("Calling sync_cb on (%d;%d)(%d;%d) area", + (int)offset_area.x1, (int)offset_area.y1, + (int)offset_area.x2, (int)offset_area.y2); + + lv_display_send_event(disp, LV_EVENT_SYNC_START, (void *)&offset_area); + + disp->sync_cb(disp, &offset_area); + + lv_display_send_event(disp, LV_EVENT_SYNC_FINISH, (void *)&offset_area); + + LV_PROFILER_REFR_END; +} + +static void wait_for_syncing(lv_display_t * disp) +{ + LV_PROFILER_REFR_BEGIN; + LV_LOG_TRACE("begin"); + + lv_display_send_event(disp, LV_EVENT_SYNC_WAIT_START, NULL); + + if(disp->sync_wait_cb) { + if(disp->syncing) { + disp->sync_wait_cb(disp); + disp->syncing = 0; + } + } + else { + while(disp->syncing); + } + disp->syncing_last = 0; + + lv_display_send_event(disp, LV_EVENT_SYNC_WAIT_FINISH, NULL); + + LV_LOG_TRACE("end"); + LV_PROFILER_REFR_END; +} diff --git a/src/display/lv_display.c b/src/display/lv_display.c index 63555da779..46f83129c7 100644 --- a/src/display/lv_display.c +++ b/src/display/lv_display.c @@ -580,6 +580,22 @@ void lv_display_set_flush_wait_cb(lv_display_t * disp, lv_display_flush_wait_cb_ disp->flush_wait_cb = wait_cb; } +void lv_display_set_sync_cb(lv_display_t * disp, lv_display_sync_cb_t sync_cb) +{ + if(disp == NULL) disp = lv_display_get_default(); + if(disp == NULL) return; + + disp->sync_cb = sync_cb; +} + +void lv_display_set_sync_wait_cb(lv_display_t * disp, lv_display_sync_wait_cb_t wait_cb) +{ + if(disp == NULL) disp = lv_display_get_default(); + if(disp == NULL) return; + + disp->sync_wait_cb = wait_cb; +} + void lv_display_set_color_format(lv_display_t * disp, lv_color_format_t color_format) { if(disp == NULL) disp = lv_display_get_default(); @@ -663,6 +679,16 @@ LV_ATTRIBUTE_FLUSH_READY bool lv_display_flush_is_last(lv_display_t * disp) return disp->flushing_last; } +LV_ATTRIBUTE_SYNC_READY void lv_display_sync_ready(lv_display_t * disp) +{ + disp->syncing = 0; +} + +LV_ATTRIBUTE_SYNC_READY bool lv_display_sync_is_last(lv_display_t * disp) +{ + return disp->syncing_last; +} + bool lv_display_is_double_buffered(lv_display_t * disp) { return disp->buf_2 != NULL; diff --git a/src/display/lv_display.h b/src/display/lv_display.h index 1b59a8e6c2..0f1b2dce68 100644 --- a/src/display/lv_display.h +++ b/src/display/lv_display.h @@ -80,6 +80,8 @@ typedef enum { typedef void (*lv_display_flush_cb_t)(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map); typedef void (*lv_display_flush_wait_cb_t)(lv_display_t * disp); +typedef void (*lv_display_sync_cb_t)(lv_display_t * disp, const lv_area_t * area); +typedef void (*lv_display_sync_wait_cb_t)(lv_display_t * disp); /********************** * GLOBAL PROTOTYPES @@ -323,6 +325,23 @@ void lv_display_set_flush_cb(lv_display_t * disp, lv_display_flush_cb_t flush_cb */ void lv_display_set_flush_wait_cb(lv_display_t * disp, lv_display_flush_wait_cb_t wait_cb); +/** + * Set the sync callback which will be called to synchronize invalidated areas between frame buffers pre-render. + * @param disp pointer to a display + * @param sync_cb the sync callback (pointer to `area` needing to be synchronized) + */ +void lv_display_set_sync_cb(lv_display_t * disp, lv_display_sync_cb_t sync_cb); + +/** + * Set a callback to be used while LVGL is waiting sync to be finished. + * It can do any complex logic to wait, including semaphores, mutexes, polling flags, etc. + * If not set the `disp->syncing` flag is used which can be cleared with `lv_display_sync_ready()` + * @param disp pointer to a display + * @param wait_cb a callback to call while LVGL is waiting for sync ready. + * If NULL `lv_display_sync_ready()` can be used to signal that syncing is ready. + */ +void lv_display_set_sync_wait_cb(lv_display_t * disp, lv_display_sync_wait_cb_t wait_cb); + /** * Set the color format of the display. * @param disp pointer to a display @@ -380,13 +399,33 @@ LV_ATTRIBUTE_FLUSH_READY void lv_display_flush_ready(lv_display_t * disp); /** * Tell if it's the last area of the refreshing process. - * Can be called from `flush_cb` to execute some special display refreshing if needed when all areas area flushed. + * Can be called from `flush_cb` to execute some special display refreshing if needed when all areas are flushed. * @param disp pointer to display * @return true: it's the last area to flush; * false: there are other areas too which will be refreshed soon */ LV_ATTRIBUTE_FLUSH_READY bool lv_display_flush_is_last(lv_display_t * disp); +/** + * Call from the display driver when the syncing is finished + * @param disp pointer to display whose `sync_cb` was called + */ +LV_ATTRIBUTE_SYNC_READY void lv_display_sync_ready(lv_display_t * disp); + +/** + * Tell if it's the last area of the syncing process. + * Can be called from `sync_cb` to execute some special display refreshing if needed when all areas are synced. + * @param disp pointer to display + * @return true: it's the last area to sync; + * false: there are other areas too which will be synced soon + */ +LV_ATTRIBUTE_SYNC_READY bool lv_display_sync_is_last(lv_display_t * disp); + +/** + * Get display is double buffered. + * @param disp pointer to a display (NULL to use the default display) + * @return return true if display is double buffered + */ bool lv_display_is_double_buffered(lv_display_t * disp); /** diff --git a/src/display/lv_display_private.h b/src/display/lv_display_private.h index 1e4723daed..b498f34682 100644 --- a/src/display/lv_display_private.h +++ b/src/display/lv_display_private.h @@ -93,6 +93,28 @@ struct _lv_display_t { volatile uint32_t last_area : 1; /**< 1: last area is being rendered */ volatile uint32_t last_part : 1; /**< 1: last part of the current area is being rendered */ + /** + * Used to synchronize changes between frame buffers between renders. + * Called for each area needing to be synchronized before rendering next frame. */ + lv_display_sync_cb_t sync_cb; + + /** + * Used to wait while syncing is ready. + * It can do any complex logic to wait, including semaphores, mutexes, polling flags, etc. + * If not set `syncing` flag is used which can be cleared with `lv_display_sync_ready()` */ + lv_display_sync_wait_cb_t sync_wait_cb; + + /** 1: syncing is in progress. (It can't be a bit field because when it's cleared from IRQ + * Read-Modify-Write issue might occur) */ + volatile int syncing; + + /** 1: It was the last chunk to sync. (It can't be a bit field because when it's cleared + * from IRQ Read-Modify-Write issue might occur) */ + volatile int syncing_last; + + /** Sync areas (redrawn during last refresh) */ + lv_ll_t sync_areas; + lv_display_render_mode_t render_mode; uint32_t antialiasing : 1; /**< 1: anti-aliasing is enabled on this display.*/ uint32_t tile_cnt : 8; /**< Divide the display buffer into these number of tiles */ @@ -110,9 +132,6 @@ struct _lv_display_t { uint32_t inv_p; int32_t inv_en_cnt; - /** Double buffer sync areas (redrawn during last refresh) */ - lv_ll_t sync_areas; - lv_draw_buf_t _static_buf1; /**< Used when user pass in a raw buffer as display draw buffer */ lv_draw_buf_t _static_buf2; /*--------------------- diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index e2695bccc1..2ab88c4ddf 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -1734,6 +1734,15 @@ #endif #endif +/** Define a custom attribute for `lv_display_sync_ready` function */ +#ifndef LV_ATTRIBUTE_SYNC_READY + #ifdef CONFIG_LV_ATTRIBUTE_SYNC_READY + #define LV_ATTRIBUTE_SYNC_READY CONFIG_LV_ATTRIBUTE_SYNC_READY + #else + #define LV_ATTRIBUTE_SYNC_READY + #endif +#endif + /** Align VG_LITE buffers on this number of bytes. * @note vglite_src_buf_aligned() uses this value to validate alignment of passed buffer pointers. */ #ifndef LV_ATTRIBUTE_MEM_ALIGN_SIZE diff --git a/src/misc/lv_event.c b/src/misc/lv_event.c index 39c17ffd1e..dc2538d1be 100644 --- a/src/misc/lv_event.c +++ b/src/misc/lv_event.c @@ -410,6 +410,10 @@ const char * lv_event_code_get_name(lv_event_code_t code) ENUM_CASE(EVENT_FLUSH_FINISH); ENUM_CASE(EVENT_FLUSH_WAIT_START); ENUM_CASE(EVENT_FLUSH_WAIT_FINISH); + ENUM_CASE(EVENT_SYNC_START); + ENUM_CASE(EVENT_SYNC_FINISH); + ENUM_CASE(EVENT_SYNC_WAIT_START); + ENUM_CASE(EVENT_SYNC_WAIT_FINISH); ENUM_CASE(EVENT_VSYNC); ENUM_CASE(EVENT_VSYNC_REQUEST); diff --git a/src/misc/lv_event.h b/src/misc/lv_event.h index be5d0f02c4..f9ff4a7c37 100644 --- a/src/misc/lv_event.h +++ b/src/misc/lv_event.h @@ -112,6 +112,10 @@ typedef enum { LV_EVENT_FLUSH_FINISH, /**< Sent after flush callback call has returned. */ LV_EVENT_FLUSH_WAIT_START, /**< Sent before flush wait callback is called. */ LV_EVENT_FLUSH_WAIT_FINISH, /**< Sent after flush wait callback call has returned. */ + LV_EVENT_SYNC_START, /**< Sent before sync callback is called. */ + LV_EVENT_SYNC_FINISH, /**< Sent after sync callback call has returned. */ + LV_EVENT_SYNC_WAIT_START, /**< Sent before sync wait callback is called. */ + LV_EVENT_SYNC_WAIT_FINISH, /**< Sent after sync wait callback call has returned. */ LV_EVENT_UPDATE_LAYOUT_COMPLETED, /**< Sent after layout update completes*/ LV_EVENT_VSYNC,