feat(render): basic of tiled rendering (#6761)

This commit is contained in:
Gabor Kiss-Vamosi
2024-09-27 11:50:27 +02:00
committed by GitHub
parent 911c7e8e72
commit 0daebca18b
10 changed files with 266 additions and 112 deletions

View File

@@ -239,6 +239,43 @@ In full and direct modes, the buffer size should be large enough for the whole s
As LVGL can not handle fractional width make sure to round the horizontal resolution to 8-
(For example 90 to 96)
Tiled Rendering
---------------
When multiple CPU cores are available and a large area needs to be redrawn, LVGL must identify independent areas that can be rendered in parallel.
For example, if there are 4 CPU cores, one core can draw the screen's background while the other 3 must wait until it is finished. If there are 2 buttons on the screen, those 2 buttons can be rendered in parallel, but 2 cores will still remain idle.
Due to dependencies among different areas, CPU cores cannot always be fully utilized.
To address this, LVGL can divide large areas that need to be updated into smaller tiles. These tiles are independent, making it easier to find areas that can be rendered concurrently.
Specifically, if there are 4 tiles and 4 cores, there will always be an independent area for each core within one of the tiles.
The maximum number of tiles can be set using the function :cpp:func:`lv_display_set_tile_cnt(disp, cnt)`. The default value is :cpp:expr:`LV_DRAW_SW_DRAW_UNIT_CNT` (or 1 if software rendering is not enabled).
Small areas are not further divided into smaller tiles because the overhead of spinning up 4 cores would outweigh the benefits.
The ideal tile size is calculated as ``ideal_tile_size = draw_buf_size / tile_cnt``. For example, in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` mode on an 800x480 screen, the display buffer is 800x480 = 375k pixels. If there are 4 tiles, the ideal tile size is approximately 93k pixels. Based on this, core utilization is as follows:
- 30k pixels: 1 core
- 90k pixels: 1 core
- 95k pixels: 2 cores (above 93k pixels, 2 cores are used)
- 150k pixels: 2 cores
- 200k pixels: 3 cores (above 186k pixels, 3 cores are used)
- 300k pixels: 4 cores (above 279k pixels, 4 cores are used)
- 375k pixels: 4 cores
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT`, the screen-sized draw buffer is divided by the tile count to determine the ideal tile sizes. If smaller areas are refreshed, it may result in fewer cores being used.
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_FULL`, the maximum number of tiles is always created as the entire screen is refreshed.
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL`, the partial buffer is divided into tiles. For example, if the draw buffer is 1/10th the size of the screen and there are 2 tiles, then 1/20th + 1/20th of the screen area will be rendered at once.
Tiled rendering only affects the rendering process, and the ``flush_cb`` is called once for each invalidated area. Therefore, tiling is not visible from the flushing point of view.
User data
---------

View File

@@ -43,7 +43,7 @@ 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_layer_t * layer);
static void refr_configured_layer(lv_layer_t * layer);
static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj);
static void refr_obj_and_children(lv_layer_t * layer, lv_obj_t * top_obj);
static void refr_obj(lv_layer_t * layer, lv_obj_t * obj);
@@ -581,11 +581,50 @@ static void refr_invalid_areas(void)
for(i = 0; i < (int32_t)disp_refr->inv_p; i++) {
/*Refresh the unjoined areas*/
if(disp_refr->inv_area_joined[i] == 0) {
if(disp_refr->inv_area_joined[i]) continue;
if(i == last_i) disp_refr->last_area = 1;
disp_refr->last_part = 0;
lv_area_t inv_a = disp_refr->inv_areas[i];
if(disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_PARTIAL) {
/*Calculate the max row num*/
int32_t w = lv_area_get_width(&inv_a);
int32_t h = lv_area_get_height(&inv_a);
int32_t max_row = get_max_row(disp_refr, w, h);
int32_t row;
int32_t row_last = 0;
lv_area_t sub_area;
sub_area.x1 = inv_a.x1;
sub_area.x2 = inv_a.x2;
for(row = inv_a.y1; row + max_row - 1 <= inv_a.y2; row += max_row) {
/*Calc. the next y coordinates of draw_buf*/
sub_area.y1 = row;
sub_area.y2 = row + max_row - 1;
if(sub_area.y2 > inv_a.y2) sub_area.y2 = inv_a.y2;
row_last = sub_area.y2;
if(inv_a.y2 == row_last) disp_refr->last_part = 1;
refr_area(&sub_area);
draw_buf_flush(disp_refr);
}
/*If the last y coordinates are not handled yet ...*/
if(inv_a.y2 != row_last) {
/*Calc. the next y coordinates of draw_buf*/
sub_area.y1 = row;
sub_area.y2 = inv_a.y2;
disp_refr->last_part = 1;
refr_area(&sub_area);
draw_buf_flush(disp_refr);
}
}
else if(disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_FULL ||
disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_DIRECT) {
disp_refr->last_part = 1;
refr_area(&disp_refr->inv_areas[i]);
draw_buf_flush(disp_refr);
}
}
@@ -618,91 +657,106 @@ static void refr_area(const lv_area_t * area_p)
LV_PROFILER_REFR_BEGIN;
lv_layer_t * layer = disp_refr->layer_head;
layer->draw_buf = disp_refr->buf_act;
layer->_clip_area = *area_p;
layer->phy_clip_area = *area_p;
#if LV_DRAW_TRANSFORM_USE_MATRIX
lv_matrix_identity(&layer->matrix);
#endif
if(disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_FULL) {
/*In full mode the area is always the full screen, so the buffer area to it too*/
layer->buf_area = *area_p;
layer_reshape_draw_buf(layer, layer->draw_buf->header.stride);
/*With full refresh just redraw directly into the buffer*/
/*In direct mode draw directly on the absolute coordinates of the buffer*/
if(disp_refr->render_mode != LV_DISPLAY_RENDER_MODE_PARTIAL) {
}
else if(disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_PARTIAL) {
/*In partial mode render this area to the buffer*/
layer->buf_area = *area_p;
layer_reshape_draw_buf(layer, LV_STRIDE_AUTO);
}
else if(disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_DIRECT) {
/*In direct mode the the buffer area is always the whole screen*/
layer->buf_area.x1 = 0;
layer->buf_area.y1 = 0;
layer->buf_area.x2 = lv_display_get_horizontal_resolution(disp_refr) - 1;
layer->buf_area.y2 = lv_display_get_vertical_resolution(disp_refr) - 1;
lv_area_t disp_area;
lv_area_set(&disp_area, 0, 0, lv_display_get_horizontal_resolution(disp_refr) - 1,
lv_display_get_vertical_resolution(disp_refr) - 1);
layer_reshape_draw_buf(layer, layer->draw_buf->header.stride);
}
if(disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_FULL) {
disp_refr->last_part = 1;
layer_reshape_draw_buf(layer, layer->draw_buf->header.stride);
layer->_clip_area = disp_area;
layer->phy_clip_area = disp_area;
refr_area_part(layer);
}
else if(disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_DIRECT) {
disp_refr->last_part = disp_refr->last_area;
layer_reshape_draw_buf(layer, layer->draw_buf->header.stride);
layer->_clip_area = *area_p;
layer->phy_clip_area = *area_p;
refr_area_part(layer);
/*Try to divide the area to smaller tiles*/
uint32_t tile_cnt = 1;
int32_t tile_h = lv_area_get_height(area_p);
if(LV_COLOR_FORMAT_IS_INDEXED(layer->color_format) == false) {
/* Assume that the the buffer size (can be screen sized or smaller in case of partial mode)
* and max tile size are the optimal scenario. From this calculate the ideal tile size
* and set the tile count and tile height accordingly.
*/
uint32_t max_tile_cnt = disp_refr->tile_cnt;
uint32_t total_buf_size = layer->draw_buf->data_size;
uint32_t ideal_tile_size = total_buf_size / max_tile_cnt;
uint32_t area_buf_size = lv_area_get_size(area_p) * lv_color_format_get_size(layer->color_format);
tile_cnt = (area_buf_size + (ideal_tile_size - 1)) / ideal_tile_size; /*Round up*/
tile_h = lv_area_get_height(area_p) / tile_cnt;
}
/* Don't draw to the layers buffer of the display but create smaller dummy layers which are using the
* display's layer buffer. These will be the tiles. By using tiles it's more likely that there will
* be independent areas for each draw unit. */
lv_layer_t * tile_layers = lv_malloc(tile_cnt * sizeof(lv_layer_t));
LV_ASSERT_MALLOC(tile_layers);
if(tile_layers == NULL) {
disp_refr->refreshed_area = *area_p;
LV_PROFILER_REFR_END;
return;
}
uint32_t i;
for(i = 0; i < tile_cnt; i++) {
lv_area_t tile_area;
lv_area_set(&tile_area, area_p->x1, area_p->y1 + i * tile_h,
area_p->x2, area_p->y1 + (i + 1) * tile_h - 1);
/*Normal refresh: draw the area in parts*/
/*Calculate the max row num*/
int32_t w = lv_area_get_width(area_p);
int32_t h = lv_area_get_height(area_p);
int32_t y2 = area_p->y2 >= lv_display_get_vertical_resolution(disp_refr) ?
lv_display_get_vertical_resolution(disp_refr) - 1 : area_p->y2;
int32_t max_row = get_max_row(disp_refr, w, h);
int32_t row;
int32_t row_last = 0;
lv_area_t sub_area;
for(row = area_p->y1; row + max_row - 1 <= y2; row += max_row) {
/*Calc. the next y coordinates of draw_buf*/
sub_area.x1 = area_p->x1;
sub_area.x2 = area_p->x2;
sub_area.y1 = row;
sub_area.y2 = row + max_row - 1;
layer->draw_buf = disp_refr->buf_act;
layer->buf_area = sub_area;
layer->_clip_area = sub_area;
layer->phy_clip_area = sub_area;
layer_reshape_draw_buf(layer, LV_STRIDE_AUTO);
if(sub_area.y2 > y2) sub_area.y2 = y2;
row_last = sub_area.y2;
if(y2 == row_last) disp_refr->last_part = 1;
refr_area_part(layer);
if(i == tile_cnt - 1) {
tile_area.y2 = area_p->y2;
}
/*If the last y coordinates are not handled yet ...*/
if(y2 != row_last) {
/*Calc. the next y coordinates of draw_buf*/
sub_area.x1 = area_p->x1;
sub_area.x2 = area_p->x2;
sub_area.y1 = row;
sub_area.y2 = y2;
layer->draw_buf = disp_refr->buf_act;
layer->buf_area = sub_area;
layer->_clip_area = sub_area;
layer->phy_clip_area = sub_area;
layer_reshape_draw_buf(layer, LV_STRIDE_AUTO);
disp_refr->last_part = 1;
refr_area_part(layer);
lv_layer_t * tile_layer = &tile_layers[i];
lv_draw_layer_init(tile_layer, NULL, layer->color_format, &tile_area);
tile_layer->buf_area = layer->buf_area; /*the buffer is still large*/
tile_layer->draw_buf = layer->draw_buf;
refr_configured_layer(tile_layer);
}
/*Wait until all tiles are ready and destroy remove them*/
for(i = 0; i < tile_cnt; i++) {
lv_layer_t * tile_layer = &tile_layers[i];
while(tile_layer->draw_task_head) {
lv_draw_dispatch_wait_for_request();
lv_draw_dispatch();
}
lv_layer_t * layer_i = disp_refr->layer_head;
while(layer_i) {
if(layer_i->next == tile_layer) {
layer_i->next = tile_layer->next;
break;
}
layer_i = layer_i->next;
}
if(disp_refr->layer_deinit) disp_refr->layer_deinit(disp_refr, tile_layer);
}
lv_free(tile_layers);
disp_refr->refreshed_area = *area_p;
LV_PROFILER_REFR_END;
}
static void refr_area_part(lv_layer_t * layer)
static void refr_configured_layer(lv_layer_t * layer)
{
LV_PROFILER_REFR_BEGIN;
disp_refr->refreshed_area = layer->_clip_area;
#if LV_DRAW_TRANSFORM_USE_MATRIX
lv_matrix_identity(&layer->matrix);
#endif
/* In single buffered mode wait here until the buffer is freed.
* Else we would draw into the buffer while it's still being transferred to the display*/
@@ -711,13 +765,7 @@ static void refr_area_part(lv_layer_t * layer)
}
/*If the screen is transparent initialize it when the flushing is ready*/
if(lv_color_format_has_alpha(disp_refr->color_format)) {
lv_area_t a = disp_refr->refreshed_area;
if(disp_refr->render_mode == LV_DISPLAY_RENDER_MODE_PARTIAL) {
/*The area always starts at 0;0*/
lv_area_move(&a, -disp_refr->refreshed_area.x1, -disp_refr->refreshed_area.y1);
}
lv_draw_buf_clear(layer->draw_buf, &a);
lv_draw_buf_clear(layer->draw_buf, &layer->_clip_area);
}
lv_obj_t * top_act_scr = NULL;
@@ -759,7 +807,6 @@ static void refr_area_part(lv_layer_t * layer)
refr_obj_and_children(layer, lv_display_get_layer_top(disp_refr));
refr_obj_and_children(layer, lv_display_get_layer_sys(disp_refr));
draw_buf_flush(disp_refr);
LV_PROFILER_REFR_END;
}

View File

@@ -77,6 +77,13 @@ lv_display_t * lv_display_create(int32_t hor_res, int32_t ver_res)
disp->dpi = LV_DPI_DEF;
disp->color_format = LV_COLOR_FORMAT_NATIVE;
#if defined(LV_DRAW_SW_DRAW_UNIT_CNT)
disp->tile_cnt = LV_DRAW_SW_DRAW_UNIT_CNT;
#else
disp->tile_cnt = 1;
#endif
disp->layer_head = lv_malloc_zeroed(sizeof(lv_layer_t));
LV_ASSERT_MALLOC(disp->layer_head);
if(disp->layer_head == NULL) return NULL;
@@ -488,6 +495,24 @@ lv_color_format_t lv_display_get_color_format(lv_display_t * disp)
return disp->color_format;
}
void lv_display_set_tile_cnt(lv_display_t * disp, uint32_t tile_cnt)
{
LV_ASSERT_FORMAT_MSG(tile_cnt < 256, "tile_cnt must be smaller than 256 (%d was used)", tile_cnt);
if(disp == NULL) disp = lv_display_get_default();
if(disp == NULL) return;
disp->tile_cnt = tile_cnt;
}
uint32_t lv_display_get_tile_cnt(lv_display_t * disp)
{
if(disp == NULL) disp = lv_display_get_default();
if(disp == NULL) return 0;
return disp->tile_cnt;
}
void lv_display_set_antialiasing(lv_display_t * disp, bool en)
{
if(disp == NULL) disp = lv_display_get_default();

View File

@@ -293,6 +293,20 @@ void lv_display_set_color_format(lv_display_t * disp, lv_color_format_t color_fo
*/
lv_color_format_t lv_display_get_color_format(lv_display_t * disp);
/**
* Set the number of tiles for parallel rendering.
* @param disp pointer to a display
* @param tile_cnt number of tiles (1 =< tile_cnt < 256)
*/
void lv_display_set_tile_cnt(lv_display_t * disp, uint32_t tile_cnt);
/**
* Get the number of tiles used for parallel rendering
* @param disp pointer to a display
* @return number of tiles
*/
uint32_t lv_display_get_tile_cnt(lv_display_t * disp);
/**
* Enable anti-aliasing for the render engine
* @param disp pointer to a display

View File

@@ -91,6 +91,8 @@ struct _lv_display_t {
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 */
/** 1: The current screen rendering is in progress*/
uint32_t rendering_in_progress : 1;

View File

@@ -398,7 +398,6 @@ uint32_t lv_draw_get_dependent_count(lv_draw_task_t * t_check)
lv_layer_t * lv_draw_layer_create(lv_layer_t * parent_layer, lv_color_format_t color_format, const lv_area_t * area)
{
LV_PROFILER_DRAW_BEGIN;
lv_display_t * disp = lv_refr_get_disp_refreshing();
lv_layer_t * new_layer = lv_malloc_zeroed(sizeof(lv_layer_t));
LV_ASSERT_MALLOC(new_layer);
if(new_layer == NULL) {
@@ -406,29 +405,45 @@ lv_layer_t * lv_draw_layer_create(lv_layer_t * parent_layer, lv_color_format_t c
return NULL;
}
new_layer->parent = parent_layer;
new_layer->_clip_area = *area;
new_layer->buf_area = *area;
new_layer->phy_clip_area = *area;
new_layer->color_format = color_format;
#if LV_DRAW_TRANSFORM_USE_MATRIX
lv_matrix_identity(&new_layer->matrix);
#endif
if(disp->layer_head) {
lv_layer_t * tail = disp->layer_head;
while(tail->next) tail = tail->next;
tail->next = new_layer;
}
else {
disp->layer_head = new_layer;
}
lv_draw_layer_init(new_layer, parent_layer, color_format, area);
LV_PROFILER_DRAW_END;
return new_layer;
}
void lv_draw_layer_init(lv_layer_t * layer, lv_layer_t * parent_layer, lv_color_format_t color_format,
const lv_area_t * area)
{
LV_PROFILER_DRAW_BEGIN;
LV_ASSERT_NULL(layer);
lv_memzero(layer, sizeof(lv_layer_t));
lv_display_t * disp = lv_refr_get_disp_refreshing();
layer->parent = parent_layer;
layer->_clip_area = *area;
layer->buf_area = *area;
layer->phy_clip_area = *area;
layer->color_format = color_format;
#if LV_DRAW_TRANSFORM_USE_MATRIX
lv_matrix_identity(&layer->matrix);
#endif
if(disp->layer_init) disp->layer_init(disp, layer);
if(disp->layer_head) {
lv_layer_t * tail = disp->layer_head;
while(tail->next) tail = tail->next;
tail->next = layer;
}
else {
disp->layer_head = layer;
}
LV_PROFILER_DRAW_END;
}
void * lv_draw_layer_alloc_buf(lv_layer_t * layer)
{
/*If the buffer of the layer is already allocated return it*/

View File

@@ -212,7 +212,7 @@ lv_draw_task_t * lv_draw_get_next_available_task(lv_layer_t * layer, lv_draw_tas
uint32_t lv_draw_get_dependent_count(lv_draw_task_t * t_check);
/**
* Create a new layer on a parent layer
* Create (allocate) a new layer on a parent layer
* @param parent_layer the parent layer to which the layer will be merged when it's rendered
* @param color_format the color format of the layer
* @param area the areas of the layer (absolute coordinates)
@@ -220,6 +220,17 @@ uint32_t lv_draw_get_dependent_count(lv_draw_task_t * t_check);
*/
lv_layer_t * lv_draw_layer_create(lv_layer_t * parent_layer, lv_color_format_t color_format, const lv_area_t * area);
/**
* Initialize a layer which is allocated by the user
* @param layer pointer the layer to initialize (its lifetime needs to be managed by the user)
* @param parent_layer the parent layer to which the layer will be merged when it's rendered
* @param color_format the color format of the layer
* @param area the areas of the layer (absolute coordinates)
* @return the new target_layer or NULL on error
*/
void lv_draw_layer_init(lv_layer_t * layer, lv_layer_t * parent_layer, lv_color_format_t color_format,
const lv_area_t * area);
/**
* Try to allocate a buffer for the layer.
* @param layer pointer to a layer

View File

@@ -553,14 +553,18 @@ static void execute_drawing(lv_draw_sw_unit_t * u)
draw_unit_tmp = draw_unit_tmp->next;
idx++;
}
lv_draw_rect_dsc_t rect_dsc;
lv_draw_rect_dsc_init(&rect_dsc);
rect_dsc.bg_color = lv_palette_main(idx % LV_PALETTE_LAST);
rect_dsc.border_color = rect_dsc.bg_color;
rect_dsc.bg_opa = LV_OPA_10;
rect_dsc.border_opa = LV_OPA_80;
rect_dsc.border_width = 1;
lv_draw_sw_fill((lv_draw_unit_t *)u, &rect_dsc, &draw_area);
lv_draw_fill_dsc_t fill_dsc;
lv_draw_fill_dsc_init(&fill_dsc);
fill_dsc.color = lv_palette_main(idx % LV_PALETTE_LAST);
fill_dsc.opa = LV_OPA_10;
lv_draw_sw_fill((lv_draw_unit_t *)u, &fill_dsc, &draw_area);
lv_draw_border_dsc_t border_dsc;
lv_draw_border_dsc_init(&border_dsc);
border_dsc.color = lv_palette_main(idx % LV_PALETTE_LAST);
border_dsc.opa = LV_OPA_60;
border_dsc.width = 1;
lv_draw_sw_border((lv_draw_unit_t *)u, &border_dsc, &draw_area);
lv_point_t txt_size;
lv_text_get_size(&txt_size, "W", LV_FONT_DEFAULT, 0, 0, 100, LV_TEXT_FLAG_NONE);
@@ -571,9 +575,9 @@ static void execute_drawing(lv_draw_sw_unit_t * u)
txt_area.x2 = draw_area.x1 + txt_size.x - 1;
txt_area.y2 = draw_area.y1 + txt_size.y - 1;
lv_draw_rect_dsc_init(&rect_dsc);
rect_dsc.bg_color = lv_color_white();
lv_draw_sw_fill((lv_draw_unit_t *)u, &rect_dsc, &txt_area);
lv_draw_fill_dsc_init(&fill_dsc);
fill_dsc.color = lv_color_white();
lv_draw_sw_fill((lv_draw_unit_t *)u, &fill_dsc, &txt_area);
char buf[8];
lv_snprintf(buf, sizeof(buf), "%d", idx);

View File

@@ -127,7 +127,7 @@ void lv_draw_sw_layer(lv_draw_unit_t * draw_unit, const lv_draw_image_dsc_t * dr
}
lv_draw_fill_dsc_t fill_dsc;
lv_draw_rect_dsc_init(&fill_dsc);
lv_draw_fill_dsc_init(&fill_dsc);
fill_dsc.color = lv_palette_main(idx % LV_PALETTE_LAST);
fill_dsc.opa = LV_OPA_10;
lv_draw_sw_fill(draw_unit, &fill_dsc, &area_rot);
@@ -135,8 +135,8 @@ void lv_draw_sw_layer(lv_draw_unit_t * draw_unit, const lv_draw_image_dsc_t * dr
lv_draw_border_dsc_t border_dsc;
lv_draw_border_dsc_init(&border_dsc);
border_dsc.color = lv_palette_main(idx % LV_PALETTE_LAST);
border_dsc.opa = LV_OPA_100;
border_dsc.width = 2;
border_dsc.opa = LV_OPA_60;
border_dsc.width = 1;
lv_draw_sw_border(draw_unit, &border_dsc, &area_rot);
lv_point_t txt_size;

View File

@@ -132,7 +132,6 @@ lv_display_t * lv_sdl_window_create(int32_t hor_res, int32_t ver_res)
lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT);
#endif /*LV_USE_DRAW_SDL == 0*/
lv_display_add_event_cb(disp, res_chg_event_cb, LV_EVENT_RESOLUTION_CHANGED, NULL);
/*Process the initial events*/
sdl_event_handler(NULL);