1
0
mirror of https://github.com/lvgl/lvgl.git synced 2025-01-21 06:53:01 +08:00
lvgl/lv_core/lv_refr.c
2019-03-08 07:54:36 +01:00

556 lines
18 KiB
C

/**
* @file lv_refr.c
*
*/
/*********************
* INCLUDES
*********************/
#include <stddef.h>
#include "lv_refr.h"
#include "lv_disp.h"
#include "../lv_hal/lv_hal_tick.h"
#include "../lv_hal/lv_hal_disp.h"
#include "../lv_misc/lv_task.h"
#include "../lv_misc/lv_mem.h"
#include "../lv_misc/lv_gc.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_refr_task(void * param);
static void lv_refr_join_area(void);
static void lv_refr_areas(void);
static void lv_refr_area(const lv_area_t * area_p);
static void lv_refr_area_part(const lv_area_t * area_p);
static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj);
static void lv_refr_obj_and_children(lv_obj_t * top_p, const lv_area_t * mask_p);
static void lv_refr_obj(lv_obj_t * obj, const lv_area_t * mask_ori_p);
static void lv_refr_vdb_flush(void);
/**********************
* STATIC VARIABLES
**********************/
static uint32_t px_num;
static lv_disp_t * disp_refr; /*Display being refreshed*/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Initialize the screen refresh subsystem
*/
void lv_refr_init(void)
{
lv_task_t * task;
task = lv_task_create(lv_refr_task, LV_REFR_PERIOD, LV_TASK_PRIO_MID, NULL);
lv_task_ready(task); /*Be sure the screen will be refreshed immediately on start up*/
}
/**
* Redraw the invalidated areas now.
* Normally the redrawing is periodically executed in `lv_task_handler` but a long blocking process can
* prevent the call of `lv_task_handler`. In this case if the the GUI is updated in the process (e.g. progress bar)
* this function can be called when the screen should be updated.
*/
void lv_refr_now(void)
{
lv_refr_task(NULL);
}
/**
* Invalidate an area on display to redraw it
* @param area_p pointer to area which should be invalidated (NULL: delete the invalidated areas)
* @param disp pointer to display where the area should be invalidated (NULL can be used if there is only one display)
*/
void lv_inv_area(lv_disp_t * disp, const lv_area_t * area_p)
{
if(!disp) disp = lv_disp_get_default();
if(!disp) return;
/*Clear the invalidate buffer if the parameter is NULL*/
if(area_p == NULL) {
disp->inv_p = 0;
return;
}
lv_area_t scr_area;
scr_area.x1 = 0;
scr_area.y1 = 0;
scr_area.x2 = disp->driver.hor_res - 1;
scr_area.y2 = disp->driver.ver_res - 1;
lv_area_t com_area;
bool suc;
suc = lv_area_intersect(&com_area, area_p, &scr_area);
/*The area is truncated to the screen*/
if(suc != false) {
if(disp->driver.rounder_cb) disp->driver.rounder_cb(&disp_refr->driver, &com_area);
/*Save only if this area is not in one of the saved areas*/
uint16_t i;
for(i = 0; i < disp->inv_p; i++) {
if(lv_area_is_in(&com_area, &disp->inv_areas[i]) != false) return;
}
/*Save the area*/
if(disp->inv_p < LV_INV_BUF_SIZE) {
lv_area_copy(&disp->inv_areas[disp->inv_p], &com_area);
} else {/*If no place for the area add the screen*/
disp->inv_p = 0;
lv_area_copy(&disp->inv_areas[disp->inv_p], &scr_area);
}
disp->inv_p ++;
}
}
/**
* Get the display which is being refreshed
* @return the display being refreshed
*/
lv_disp_t * lv_refr_get_disp_refreshing(void)
{
return disp_refr;
}
/**********************
* STATIC FUNCTIONS
**********************/
/**
* Called periodically to handle the refreshing
* @param param unused
*/
static void lv_refr_task(void * param)
{
(void)param;
LV_LOG_TRACE("lv_refr_task: started");
uint32_t start = lv_tick_get();
LV_LL_READ(LV_GC_ROOT(_lv_disp_ll), disp_refr) {
LV_LOG_TRACE("lv_refr_task: refreshing a display");
lv_refr_join_area();
lv_refr_areas();
/*If refresh happened ...*/
if(disp_refr->inv_p != 0) {
/*In true double buffered mode copy the refreshed areas to the new VDB to keep it up to date*/
if(lv_disp_is_true_double_buf(disp_refr)) {
lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
/*Flush the content of the VDB*/
lv_refr_vdb_flush();
/* With true double buffering the flushing should be only the address change of the current frame buffer.
* Wait until the address change is ready and copy the changed content to the other frame buffer (new active VDB)
* to keep the buffers synchronized*/
while(vdb->flushing);
uint8_t * buf_act = (uint8_t *) vdb->buf_act;
uint8_t * buf_ina = (uint8_t *) vdb->buf_act == vdb->buf1 ? vdb->buf2 : vdb->buf1;
lv_coord_t hres = lv_disp_get_hor_res(disp_refr);
uint16_t a;
for(a = 0; a < disp_refr->inv_p; a++) {
if(disp_refr->inv_area_joined[a] == 0) {
lv_coord_t y;
uint32_t start_offs = (hres * disp_refr->inv_areas[a].y1 + disp_refr->inv_areas[a].x1) * sizeof(lv_color_t);
uint32_t line_length = lv_area_get_width(&disp_refr->inv_areas[a]) * sizeof(lv_color_t);
for(y = disp_refr->inv_areas[a].y1; y <= disp_refr->inv_areas[a].y2; y++) {
memcpy(buf_act + start_offs, buf_ina + start_offs, line_length);
start_offs += hres * sizeof(lv_color_t);
}
}
}
} /*End of true double buffer handling*/
/*Clean up*/
memset(disp_refr->inv_areas, 0, sizeof(disp_refr->inv_areas));
memset(disp_refr->inv_area_joined, 0, sizeof(disp_refr->inv_area_joined));
disp_refr->inv_p = 0;
/*Call monitor cb if present*/
if(disp_refr->driver.monitor_cb) {
printf("%d ms\n", lv_tick_elaps(start));
disp_refr->driver.monitor_cb(&disp_refr->driver, lv_tick_elaps(start), px_num);
}
}
}
LV_LOG_TRACE("lv_refr_task: ready");
}
/**
* Join the areas which has got common parts
*/
static void lv_refr_join_area(void)
{
uint32_t join_from;
uint32_t join_in;
lv_area_t joined_area;
for(join_in = 0; join_in < disp_refr->inv_p; join_in++) {
if(disp_refr->inv_area_joined[join_in] != 0) continue;
/*Check all areas to join them in 'join_in'*/
for(join_from = 0; join_from < disp_refr->inv_p; join_from++) {
/*Handle only unjoined areas and ignore itself*/
if(disp_refr->inv_area_joined[join_from] != 0 || join_in == join_from) {
continue;
}
/*Check if the areas are on each other*/
if(lv_area_is_on(&disp_refr->inv_areas[join_in],
&disp_refr->inv_areas[join_from]) == false) {
continue;
}
lv_area_join(&joined_area, &disp_refr->inv_areas[join_in],
&disp_refr->inv_areas[join_from]);
/*Join two area only if the joined area size is smaller*/
if(lv_area_get_size(&joined_area) <
(lv_area_get_size(&disp_refr->inv_areas[join_in]) + lv_area_get_size(&disp_refr->inv_areas[join_from]))) {
lv_area_copy(&disp_refr->inv_areas[join_in], &joined_area);
/*Mark 'join_form' is joined into 'join_in'*/
disp_refr->inv_area_joined[join_from] = 1;
}
}
}
}
/**
* Refresh the joined areas
*/
static void lv_refr_areas(void)
{
px_num = 0;
uint32_t i;
for(i = 0; i < disp_refr->inv_p; i++) {
/*Refresh the unjoined areas*/
if(disp_refr->inv_area_joined[i] == 0) {
lv_refr_area(&disp_refr->inv_areas[i]);
if(disp_refr->driver.monitor_cb) px_num += lv_area_get_size(&disp_refr->inv_areas[i]);
}
}
}
/**
* Refresh an area if there is Virtual Display Buffer
* @param area_p pointer to an area to refresh
*/
static void lv_refr_area(const lv_area_t * area_p)
{
/*True double buffering: there are two screen sized buffers. Just redraw directly into a buffer*/
if(lv_disp_is_true_double_buf(disp_refr)) {
lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
vdb->area.x1 = 0;
vdb->area.x2 = lv_disp_get_hor_res(disp_refr) - 1;
vdb->area.y1 = 0;
vdb->area.y2 = lv_disp_get_ver_res(disp_refr) - 1;
lv_refr_area_part(area_p);
}
/*The buffer is smaller: refresh the area in parts*/
else {
lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
/*Calculate the max row num*/
lv_coord_t w = lv_area_get_width(area_p);
lv_coord_t h = lv_area_get_height(area_p);
lv_coord_t y2 = area_p->y2 >= lv_disp_get_ver_res(disp_refr) ? y2 = lv_disp_get_ver_res(disp_refr) - 1 : area_p->y2;
int32_t max_row = (uint32_t) vdb->size / w;
if(max_row > h) max_row = h;
/*Round down the lines of VDB if rounding is added*/
if(disp_refr->driver.rounder_cb) {
lv_area_t tmp;
tmp.x1 = 0;
tmp.x2 = 0;
tmp.y1 = 0;
tmp.y2 = max_row;
lv_coord_t y_tmp = max_row;
do {
tmp.y2 = y_tmp;
disp_refr->driver.rounder_cb(&disp_refr->driver, &tmp);
y_tmp --; /*Decrement the number of line until it is rounded to a smaller (or equal) value then the original. */
} while(lv_area_get_height(&tmp) > max_row && y_tmp != 0);
if(y_tmp == 0) {
LV_LOG_WARN("Can't set VDB height using the round function. (Wrong round_cb or to small VDB)");
return;
} else {
max_row = tmp.y2 + 1;
}
}
/*Always use the full row*/
lv_coord_t row;
lv_coord_t row_last = 0;
for(row = area_p->y1; row + max_row - 1 <= y2; row += max_row) {
/*Calc. the next y coordinates of VDB*/
vdb->area.x1 = area_p->x1;
vdb->area.x2 = area_p->x2;
vdb->area.y1 = row;
vdb->area.y2 = row + max_row - 1;
if(vdb->area.y2 > y2) vdb->area.y2 = y2;
row_last = vdb->area.y2;
lv_refr_area_part(area_p);
}
/*If the last y coordinates are not handled yet ...*/
if(y2 != row_last) {
/*Calc. the next y coordinates of VDB*/
vdb->area.x1 = area_p->x1;
vdb->area.x2 = area_p->x2;
vdb->area.y1 = row;
vdb->area.y2 = y2;
/*Refresh this part too*/
lv_refr_area_part(area_p);
}
}
}
/**
* Refresh a part of an area which is on the actual Virtual Display Buffer
* @param area_p pointer to an area to refresh
*/
static void lv_refr_area_part(const lv_area_t * area_p)
{
lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
/*In non double buffered mode, before rendering the next part wait until the previous image is flushed*/
if(lv_disp_is_double_buf(disp_refr) == false) {
while(vdb->flushing);
}
lv_obj_t * top_p;
/*Get the new mask from the original area and the act. VDB
It will be a part of 'area_p'*/
lv_area_t start_mask;
lv_area_intersect(&start_mask, area_p, &vdb->area);
/*Get the most top object which is not covered by others*/
top_p = lv_refr_get_top_obj(&start_mask, lv_disp_get_scr_act(disp_refr));
/*Do the refreshing from the top object*/
lv_refr_obj_and_children(top_p, &start_mask);
/*Also refresh top and sys layer unconditionally*/
lv_refr_obj_and_children(lv_disp_get_layer_top(disp_refr), &start_mask);
lv_refr_obj_and_children(lv_disp_get_layer_sys(disp_refr), &start_mask);
/* In true double buffered mode flush only once when all areas were rendered.
* In normal mode flush after every area */
if(lv_disp_is_true_double_buf(disp_refr) == false) {
lv_refr_vdb_flush();
}
}
/**
* Search the most top object which fully covers an area
* @param area_p pointer to an area
* @param obj the first object to start the searching (typically a screen)
* @return
*/
static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj)
{
lv_obj_t * i;
lv_obj_t * found_p = NULL;
/*If this object is fully cover the draw area check the children too */
if(lv_area_is_in(area_p, &obj->coords) && obj->hidden == 0) {
LV_LL_READ(obj->child_ll, i) {
found_p = lv_refr_get_top_obj(area_p, i);
/*If a children is ok then break*/
if(found_p != NULL) {
break;
}
}
/*If no better children check this object*/
if(found_p == NULL) {
lv_style_t * style = lv_obj_get_style(obj);
if(style->body.opa == LV_OPA_COVER &&
obj->design_cb(obj, area_p, LV_DESIGN_COVER_CHK) != false &&
lv_obj_get_opa_scale(obj) == LV_OPA_COVER) {
found_p = obj;
}
}
}
return found_p;
}
/**
* Make the refreshing from an object. Draw all its children and the youngers too.
* @param top_p pointer to an objects. Start the drawing from it.
* @param mask_p pointer to an area, the objects will be drawn only here
*/
static void lv_refr_obj_and_children(lv_obj_t * top_p, const lv_area_t * mask_p)
{
/* Normally always will be a top_obj (at least the screen)
* but in special cases (e.g. if the screen has alpha) it won't.
* In this case use the screen directly */
if(top_p == NULL) top_p = lv_disp_get_scr_act(disp_refr);
/*Refresh the top object and its children*/
lv_refr_obj(top_p, mask_p);
/*Draw the 'younger' sibling objects because they can be on top_obj */
lv_obj_t * par;
lv_obj_t * i;
lv_obj_t * border_p = top_p;
par = lv_obj_get_parent(top_p);
/*Do until not reach the screen*/
while(par != NULL) {
/*object before border_p has to be redrawn*/
i = lv_ll_get_prev(&(par->child_ll), border_p);
while(i != NULL) {
/*Refresh the objects*/
lv_refr_obj(i, mask_p);
i = lv_ll_get_prev(&(par->child_ll), i);
}
/*The new border will be there last parents,
*so the 'younger' brothers of parent will be refreshed*/
border_p = par;
/*Go a level deeper*/
par = lv_obj_get_parent(par);
}
/*Call the post draw design function of the parents of the to object*/
par = lv_obj_get_parent(top_p);
while(par != NULL) {
par->design_cb(par, mask_p, LV_DESIGN_DRAW_POST);
par = lv_obj_get_parent(par);
}
}
/**
* Refresh an object an all of its children. (Called recursively)
* @param obj pointer to an object to refresh
* @param mask_ori_p pointer to an area, the objects will be drawn only here
*/
static void lv_refr_obj(lv_obj_t * obj, const lv_area_t * mask_ori_p)
{
/*Do not refresh hidden objects*/
if(obj->hidden != 0) return;
bool union_ok; /* Store the return value of area_union */
/* Truncate the original mask to the coordinates of the parent
* because the parent and its children are visible only here */
lv_area_t obj_mask;
lv_area_t obj_ext_mask;
lv_area_t obj_area;
lv_coord_t ext_size = obj->ext_size;
lv_obj_get_coords(obj, &obj_area);
obj_area.x1 -= ext_size;
obj_area.y1 -= ext_size;
obj_area.x2 += ext_size;
obj_area.y2 += ext_size;
union_ok = lv_area_intersect(&obj_ext_mask, mask_ori_p, &obj_area);
/*Draw the parent and its children only if they ore on 'mask_parent'*/
if(union_ok != false) {
/* Redraw the object */
obj->design_cb(obj, &obj_ext_mask, LV_DESIGN_DRAW_MAIN);
//usleep(5 * 1000); /*DEBUG: Wait after every object draw to see the order of drawing*/
/*Create a new 'obj_mask' without 'ext_size' because the children can't be visible there*/
lv_obj_get_coords(obj, &obj_area);
union_ok = lv_area_intersect(&obj_mask, mask_ori_p, &obj_area);
if(union_ok != false) {
lv_area_t mask_child; /*Mask from obj and its child*/
lv_obj_t * child_p;
lv_area_t child_area;
LV_LL_READ_BACK(obj->child_ll, child_p) {
lv_obj_get_coords(child_p, &child_area);
ext_size = child_p->ext_size;
child_area.x1 -= ext_size;
child_area.y1 -= ext_size;
child_area.x2 += ext_size;
child_area.y2 += ext_size;
/* Get the union (common parts) of original mask (from obj)
* and its child */
union_ok = lv_area_intersect(&mask_child, &obj_mask, &child_area);
/*If the parent and the child has common area then refresh the child */
if(union_ok) {
/*Refresh the next children*/
lv_refr_obj(child_p, &mask_child);
}
}
}
/* If all the children are redrawn make 'post draw' design */
obj->design_cb(obj, &obj_ext_mask, LV_DESIGN_DRAW_POST);
}
}
/**
* Flush the content of the VDB
*/
static void lv_refr_vdb_flush(void)
{
lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
/*In double buffered mode wait until the other buffer is flushed before flushing the current one*/
if(lv_disp_is_double_buf(disp_refr)) {
while(vdb->flushing);
}
vdb->flushing = 1;
/*Flush the rendered content to the display*/
lv_disp_t * disp = lv_refr_get_disp_refreshing();
if(disp->driver.flush_cb) disp->driver.flush_cb(&disp->driver, &vdb->area, vdb->buf_act);
if(vdb->buf1 && vdb->buf2) {
if(vdb->buf_act == vdb->buf1) vdb->buf_act = vdb->buf2;
else vdb->buf_act = vdb->buf1;
/*If the screen is transparent initialize it when the new VDB is selected*/
# if LV_COLOR_SCREEN_TRANSP
memset(vdb[vdb_active].buf, 0x00, LV_VDB_SIZE_IN_BYTES);
# endif /*LV_COLOR_SCREEN_TRANSP*/
}
}