feat(display): add display sync cb for custom frame buffer synchronization logic (#9342) (#9389)
Arduino Lint / lint (push) Has been cancelled
Build Examples with C++ Compiler / build-examples (push) Has been cancelled
MicroPython CI / Build esp32 port (push) Has been cancelled
MicroPython CI / Build rp2 port (push) Has been cancelled
MicroPython CI / Build stm32 port (push) Has been cancelled
MicroPython CI / Build unix port (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_NORMAL_8BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_SDL - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build ESP IDF ESP32S3 (push) Has been cancelled
C/C++ CI / Run tests with 32bit build (push) Has been cancelled
C/C++ CI / Run tests with 64bit build (push) Has been cancelled
BOM Check / bom-check (push) Has been cancelled
Verify that lv_conf_internal.h matches repository state / verify-conf-internal (push) Has been cancelled
Verify the widget property name / verify-property-name (push) Has been cancelled
Verify code formatting / verify-formatting (push) Has been cancelled
Compare file templates with file names / template-check (push) Has been cancelled
Build docs / build-and-deploy (push) Has been cancelled
Test API JSON generator / Test API JSON (push) Has been cancelled
Install LVGL using CMake / build-examples (push) Has been cancelled
Check Makefile / Build using Makefile (push) Has been cancelled
Check Makefile for UEFI / Build using Makefile for UEFI (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/benchmark_results_comment/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/filter_docker_logs/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/serialize_results/test.sh) (push) Has been cancelled
Hardware Performance Test / Hardware Performance Benchmark (push) Has been cancelled
Hardware Performance Test / HW Benchmark - Save PR Number (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_32B - Ubuntu (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_64B - Ubuntu (push) Has been cancelled
Port repo release update / run-release-branch-updater (push) Has been cancelled
Verify Font License / verify-font-license (push) Has been cancelled
Verify Kconfig / verify-kconfig (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 32b - lv_conf_perf32b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 64b - lv_conf_perf64b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Save PR Number (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Brandon Holland
2026-03-02 23:07:44 -08:00
committed by GitHub
parent 0f8b3616af
commit 4170bcb1ee
8 changed files with 209 additions and 37 deletions
+3
View File
@@ -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
+101 -33
View File
@@ -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;
}
+26
View File
@@ -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;
+40 -1
View File
@@ -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);
/**
+22 -3
View File
@@ -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;
/*---------------------
+9
View File
@@ -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
+4
View File
@@ -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);
+4
View File
@@ -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,