From 336f24127ab9032cd7b2ac67f25f785cf3b64033 Mon Sep 17 00:00:00 2001 From: Brandon Holland <97706724+bhspyder@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:23:50 -0700 Subject: [PATCH] feat(disp): add double buffered direct-mode efficient sync algorithm (v8.3) (#4497) Co-authored-by: Gabor Kiss-Vamosi --- src/core/lv_refr.c | 87 ++++++++++++++++++++++++++++++++++++++++++- src/hal/lv_hal_disp.c | 3 ++ src/hal/lv_hal_disp.h | 4 ++ src/misc/lv_area.c | 72 +++++++++++++++++++++++++++++++++++ src/misc/lv_area.h | 9 +++++ 5 files changed, 174 insertions(+), 1 deletion(-) diff --git a/src/core/lv_refr.c b/src/core/lv_refr.c index ec83e0f09..37a0e5c63 100644 --- a/src/core/lv_refr.c +++ b/src/core/lv_refr.c @@ -53,6 +53,7 @@ typedef struct { **********************/ static void lv_refr_join_area(void); static void refr_invalid_areas(void); +static void refr_sync_areas(void); static void refr_area(const lv_area_t * area_p); static void refr_area_part(lv_draw_ctx_t * draw_ctx); static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj); @@ -320,12 +321,24 @@ void _lv_disp_refr_timer(lv_timer_t * tmr) } lv_refr_join_area(); - + refr_sync_areas(); refr_invalid_areas(); /*If refresh happened ...*/ if(disp_refr->inv_p != 0) { + /*Copy invalid areas for sync next refresh*/ + if(disp_refr->driver->direct_mode) { + uint16_t i; + for(i = 0; i < disp_refr->inv_p; i++) { + if(disp_refr->inv_area_joined[i]) + continue; + + lv_area_t * sync_area = _lv_ll_ins_tail(&disp_refr->sync_areas); + *sync_area = disp_refr->inv_areas[i]; + } + } + /*Clean up*/ lv_memset_00(disp_refr->inv_areas, sizeof(disp_refr->inv_areas)); lv_memset_00(disp_refr->inv_area_joined, sizeof(disp_refr->inv_area_joined)); @@ -495,6 +508,78 @@ static void lv_refr_join_area(void) } } +/** + * Refresh the sync areas + */ +static void refr_sync_areas(void) +{ + /*Do not sync if not direct mode*/ + if(!disp_refr->driver->direct_mode) return; + + /*Do not sync if not double buffered*/ + if(disp_refr->driver->draw_buf->buf2 == NULL) return; + + /*Do not sync if no sync areas*/ + if(_lv_ll_is_empty(&disp_refr->sync_areas)) return; + + /*The buffers are already swapped. + *So the active buffer is the off screen buffer where LVGL will render*/ + void * buf_off_screen = disp_refr->driver->draw_buf->buf_act; + void * buf_on_screen = disp_refr->driver->draw_buf->buf_act == disp_refr->driver->draw_buf->buf1 + ? disp_refr->driver->draw_buf->buf2 + : disp_refr->driver->draw_buf->buf1; + + /*Get stride for buffer copy*/ + lv_coord_t stride = lv_disp_get_hor_res(disp_refr); + + /*Iterate through invalidated areas to see if sync area should be copied*/ + lv_area_t res[4] = {0}; + int8_t res_c, j; + uint32_t i; + lv_area_t * sync_area, *new_area, *next_area; + for(i = 0; i < disp_refr->inv_p; i++) { + /*Skip joined areas*/ + if(disp_refr->inv_area_joined[i]) continue; + + /*Iterate over sync areas*/ + sync_area = _lv_ll_get_head(&disp_refr->sync_areas); + while(sync_area != NULL) { + /*Get next sync area*/ + next_area = _lv_ll_get_next(&disp_refr->sync_areas, sync_area); + + /*Remove intersect of redraw area from sync area and get remaining areas*/ + res_c = _lv_area_diff(res, sync_area, &disp_refr->inv_areas[i]); + + /*New sub areas created after removing intersect*/ + if(res_c != -1) { + /*Replace old sync area with new areas*/ + for(j = 0; j < res_c; j++) { + new_area = _lv_ll_ins_prev(&disp_refr->sync_areas, sync_area); + *new_area = res[j]; + } + _lv_ll_remove(&disp_refr->sync_areas, sync_area); + lv_mem_free(sync_area); + } + + /*Move on to next sync area*/ + sync_area = next_area; + } + } + + /*Copy sync areas (if any remaining)*/ + for(sync_area = _lv_ll_get_head(&disp_refr->sync_areas); sync_area != NULL; + sync_area = _lv_ll_get_next(&disp_refr->sync_areas, sync_area)) { + disp_refr->driver->draw_ctx->buffer_copy( + disp_refr->driver->draw_ctx, + buf_off_screen, stride, sync_area, + buf_on_screen, stride, sync_area + ); + } + + /*Clear sync areas*/ + _lv_ll_clear(&disp_refr->sync_areas); +} + /** * Refresh the joined areas */ diff --git a/src/hal/lv_hal_disp.c b/src/hal/lv_hal_disp.c index 087882915..6f1c9626d 100644 --- a/src/hal/lv_hal_disp.c +++ b/src/hal/lv_hal_disp.c @@ -186,6 +186,8 @@ lv_disp_t * lv_disp_drv_register(lv_disp_drv_t * driver) disp->inv_en_cnt = 1; + _lv_ll_init(&disp->sync_areas, sizeof(lv_area_t)); + lv_disp_t * disp_def_tmp = disp_def; disp_def = disp; /*Temporarily change the default screen to create the default screens on the new display*/ @@ -316,6 +318,7 @@ void lv_disp_remove(lv_disp_t * disp) } _lv_ll_remove(&LV_GC_ROOT(_lv_disp_ll), disp); + _lv_ll_clear(&disp->sync_areas); if(disp->refr_timer) lv_timer_del(disp->refr_timer); lv_mem_free(disp); diff --git a/src/hal/lv_hal_disp.h b/src/hal/lv_hal_disp.h index 93a6b4614..d5203a0b1 100644 --- a/src/hal/lv_hal_disp.h +++ b/src/hal/lv_hal_disp.h @@ -23,6 +23,7 @@ extern "C" { #include "../misc/lv_area.h" #include "../misc/lv_ll.h" #include "../misc/lv_timer.h" +#include "../misc/lv_ll.h" /********************* * DEFINES @@ -187,6 +188,9 @@ typedef struct _lv_disp_t { uint16_t inv_p; int32_t inv_en_cnt; + /** Double buffer sync areas */ + lv_ll_t sync_areas; + /*Miscellaneous data*/ uint32_t last_activity_time; /**< Last time when there was activity on this display*/ } lv_disp_t; diff --git a/src/misc/lv_area.c b/src/misc/lv_area.c index c0221f7ed..493b70880 100644 --- a/src/misc/lv_area.c +++ b/src/misc/lv_area.c @@ -143,6 +143,78 @@ bool _lv_area_intersect(lv_area_t * res_p, const lv_area_t * a1_p, const lv_area return union_ok; } +/** + * Get resulting sub areas after removing the common parts of two areas from the first area + * @param res_p pointer to an array of areas with a count of 4, the resulting areas will be stored here + * @param a1_p pointer to the first area + * @param a2_p pointer to the second area + * @return number of results or -1 if no intersect + */ +int8_t _lv_area_diff(lv_area_t * res_p, const lv_area_t * a1_p, const lv_area_t * a2_p) +{ + /*Areas have no common parts*/ + if(!_lv_area_is_on(a1_p, a2_p)) return -1; + + /*No remaining areas after removing common parts*/ + if(_lv_area_is_in(a1_p, a2_p, 0)) return 0; + + /*Result counter*/ + int8_t res_c = 0; + + /*Get required information*/ + lv_area_t n; + lv_coord_t a1_w = lv_area_get_width(a1_p) - 1; + lv_coord_t a1_h = lv_area_get_height(a1_p) - 1; + + /*Compute top rectangle*/ + lv_coord_t th = a2_p->y1 - a1_p->y1; + if(th > 0) { + n.x1 = a1_p->x1; + n.y1 = a1_p->y1; + n.x2 = a1_p->x2; + n.y2 = a1_p->y1 + th; + res_p[res_c++] = n; + } + + /*Compute the bottom rectangle*/ + lv_coord_t bh = a1_h - (a2_p->y2 - a1_p->y1); + if(bh > 0 && a2_p->y2 < a1_p->y2) { + n.x1 = a1_p->x1; + n.y1 = a2_p->y2; + n.x2 = a1_p->x2; + n.y2 = a2_p->y2 + bh; + res_p[res_c++] = n; + } + + /*Compute side height*/ + lv_coord_t y1 = a2_p->y1 > a1_p->y1 ? a2_p->y1 : a1_p->y1; + lv_coord_t y2 = a2_p->y2 < a1_p->y2 ? a2_p->y2 : a1_p->y2; + lv_coord_t sh = y2 - y1; + + /*Compute the left rectangle*/ + lv_coord_t lw = a2_p->x1 - a1_p->x1; + if(lw > 0 && sh > 0) { + n.x1 = a1_p->x1; + n.y1 = y1; + n.x2 = a1_p->x1 + lw; + n.y2 = y1 + sh; + res_p[res_c++] = n; + } + + /*Compute the right rectangle*/ + lv_coord_t rw = a1_w - (a2_p->x2 - a1_p->x1); + if(rw > 0) { + n.x1 = a2_p->x2; + n.y1 = y1; + n.x2 = a2_p->x2 + rw; + n.y2 = y1 + sh; + res_p[res_c++] = n; + } + + //Return number of results + return res_c; +} + /** * Join two areas into a third which involves the other two * @param res_p pointer to an area, the result will be stored here diff --git a/src/misc/lv_area.h b/src/misc/lv_area.h index 137931a29..48dc80744 100644 --- a/src/misc/lv_area.h +++ b/src/misc/lv_area.h @@ -177,6 +177,15 @@ void lv_area_move(lv_area_t * area, lv_coord_t x_ofs, lv_coord_t y_ofs); */ bool _lv_area_intersect(lv_area_t * res_p, const lv_area_t * a1_p, const lv_area_t * a2_p); +/** + * Get resulting sub areas after removing the common parts of two areas from the first area + * @param res_p pointer to an array of areas with a count of 4, the resulting areas will be stored here + * @param a1_p pointer to the first area + * @param a2_p pointer to the second area + * @return number of results (max 4) or -1 if no intersect + */ +int8_t _lv_area_diff(lv_area_t * res_p, const lv_area_t * a1_p, const lv_area_t * a2_p); + /** * Join two areas into a third which involves the other two * @param res_p pointer to an area, the result will be stored here