UVGUI屏幕刷新原理

UVGUI屏幕刷新原理

@firestaradmin 2020年12月9日17:39:32

我还在寻找回家的路。长路漫漫,唯剑作伴。 ——亚索

一、数据结构

核心结构体为 ug_disp_t,UVGUI 支持同时驱动多个屏幕,每个屏幕对应一个disp 结构体,disp 定义如下:

/**
 * Display structure.
 * @note `ug_disp_drv_t` should be the first member of the structure.
 */
typedef struct _disp_t {
    
    /**< Driver to the display*/
    ug_disp_drv_t driver;

    
    /**< A task which periodically checks the dirty areas and refreshes them*/
    ug_task_t * refr_task;

    
    /** Screens of the display*/
    ug_ll_t scr_ll;
    struct _ug_obj_t * act_scr;   /**< Currently active screen on this display */
    struct _ug_obj_t * prev_scr;  /**< Previous screen. Used during screen animations */

    ug_color_t bg_color;          /**< Default display color when screens are transparent*/
    const void * bg_img;       /**< An image source to display as wallpaper*/

    ug_area_t inv_areas[UG_INV_BUF_SIZE];
    uint8_t inv_area_joined[UG_INV_BUF_SIZE];
    uint32_t inv_p : 10;    /* invalid parts */

    /*Miscellaneous data*/
    uint8_t disp_index;     /* Used to select/change which physical screen to display. */

} ug_disp_t;

其中刷屏算法核心关键成员为:

  • scr_ll:屏幕列表,一个物理屏幕可以有多个屏幕列表去绘制,也可理解为窗口或者页面。
  • inv_areas[]:不合法区域数组,储存着需要重新绘制的区域。
  • inv_area_joined[]:不合法区域的合并标志,为0 则表示该区域未合并。
  • inc_p:不合法区域的个数。

UG_INV_BUF_SIZE 定义了最大的不合法区域数,如果超过限制,则会重新绘制整个屏幕。


每创建一个对象,就会根据对象的区域大小,将其储存到inv_areas数组中,并且inv_p 数加一。


二、核心算法

算法流程:

_ug_disp_refr_task()	/* 屏幕刷新任务 */
    -> ug_refr_join_area();		/* 合并非法区域 */
    -> ug_refr_areas();			/* 刷新区域 */
		-> ug_refr_area();
			->ug_refr_area_part();
				->ug_refr_obj_and_children();
					->ug_refr_obj();
				->ug_refr_vdb_flush();	/* 在非真实像素显存大小模式,每次刷新都要刷屏 */

ug_refr_join_area()

此函数功能为 合并有相同部分的区域。 函数如下:

/**
 * Join the areas which has got common parts
 */
static void ug_refr_join_area(void)
{
    uint32_t join_from;
    uint32_t join_in;
    ug_area_t joined_area;

    for(join_in = 0; join_in < disp_refr->inv_p; join_in++) {  
        /* if the area is already been joined , skip. */
        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(_ug_area_is_on(&disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]) == false) {
                continue;
            }

            _ug_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(ug_area_get_size(&joined_area) < (ug_area_get_size(&disp_refr->inv_areas[join_in]) +
                                                 ug_area_get_size(&disp_refr->inv_areas[join_from]))) {
                ug_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;
            }
        }
    }
}

参数说明:

  • join_in:要合并进去的区域编号,即其他区域符合要求会合并到这个区域。
  • join_from:要被合并的区域编号,即这个区域会被合并到join_in 这个区域。
  • joined_area:合并后的区域。

函数有循环嵌套。

第一个大循环会遍历每个inv 区域,如果该区域已被合并则跳过该区域,然后进入第二个循环,再次遍历非合法区域,如果区域是自己,或者已经被合并了则跳过。

然后判断,两个区域是否有相同部分,即是否有接壤,如果有 则将两个区域合并的区域储存至joined_area,然后再判断合并后的区域大小是否比之前两个区域的大小加在一起更小,如果更小则会将合并后的区域储存到 inv_area[join_in] 里,并且将inv_area_joined[join_form] 置1,来指示该区域已被合并。


ug_refr_areas()

此函数刷新所有非合法区域,即刷新 inv_area_joined[] 标志未被置 1 (即未被合并) 的 inv_areas[] 储存的区域。代码如下:

/**
 * Refresh the joined areas
 */
static void ug_refr_areas(void)
{
    px_num = 0;

    if(disp_refr->inv_p == 0) return;

    /*Find the last area which will be drawn*/
    int32_t i;
    int32_t last_i = 0;
    for(i = disp_refr->inv_p - 1; i >= 0; i--) {
        if(disp_refr->inv_area_joined[i] == 0) {
            last_i = i;
            break;
        }
    }

    disp_refr->driver.buffer->last_area = 0;
    disp_refr->driver.buffer->last_part = 0;

    for(i = 0; i < disp_refr->inv_p; i++) {
        /*Refresh the unjoined areas*/
        if(disp_refr->inv_area_joined[i] == 0) {

            if(i == last_i) disp_refr->driver.buffer->last_area = 1;
            disp_refr->driver.buffer->last_part = 0;
            ug_refr_area(&disp_refr->inv_areas[i]);

            //if(disp_refr->driver.monitor_cb) px_num += ug_area_get_size(&disp_refr->inv_areas[i]);
        }
    }
}

此函数会找到符合条件的区域并调用子函数 ug_refr_area() 来刷新。


ug_refr_area()

此函数代码如下:

/**
 * Refresh an area if there is Virtual Display Buffer
 * @param area_p  pointer to an area to refresh
 */
static void ug_refr_area(const ug_area_t * area_p)
{
    /*True double buffering: there are two screen sized buffers. Just redraw directly into a * buffer */
    if(ug_disp_is_true_double_buf(disp_refr)) {
        ug_disp_buf_t * vdb = ug_disp_get_buf(disp_refr);
        vdb->area.x1        = 0;
        vdb->area.x2        = ug_disp_get_hor_res(disp_refr) - 1;
        vdb->area.y1        = 0;
        vdb->area.y2        = ug_disp_get_ver_res(disp_refr) - 1;
        disp_refr->driver.buffer->last_part = 1;
        ug_refr_area_part(area_p);
    }
    /*The buffer is smaller: refresh the area in parts*/
    else {
        ug_disp_buf_t * vdb = ug_disp_get_buf(disp_refr);

        /* Calculate the max row value */
        ug_coord_t w = ug_area_get_width(area_p);
        ug_coord_t h = ug_area_get_height(area_p);
        ug_coord_t y2 = area_p->y2 >= ug_disp_get_ver_res(disp_refr) ? ug_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;

        /*Always use the full row*/
        ug_coord_t row;
        ug_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;
            if(y2 == row_last) disp_refr->driver.buffer->last_part = 1;
            ug_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;

            disp_refr->driver.buffer->last_part = 1;
            ug_refr_area_part(area_p);
        }
    }
}

此函数会根据你设置的显存大小和刷新的区域大小进行判断,如果你的显存大小是真实屏幕的像素大小,就一次刷新完,如果显存比屏幕小,就会一部分一部分刷新,直至刷新完毕。计算好虚拟显存的区域后,会调用ug_refr_area_part() 函数来刷新。


ug_refr_area_part()

此函数会刷新在当前虚拟显存上的一块区域。代码如下:

/**
 * 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 ug_refr_area_part(const ug_area_t * area_p)
{
    ug_disp_buf_t * vdb = ug_disp_get_buf(disp_refr);

    /*In non double buffered mode, before rendering the next part wait until the previous image is
     * flushed*/
    if(ug_disp_is_double_buf(disp_refr) == false) {
        while(vdb->flushing) {
            if(disp_refr->driver.wait_cb) disp_refr->driver.wait_cb(&disp_refr->driver);
        }
    }

    ug_obj_t * top_act_scr = NULL;
    ug_obj_t * top_prev_scr = NULL;

    /*Get the new mask from the original area and the act. VDB
     It will be a part of 'area_p'*/
    ug_area_t start_mask;
    _ug_area_intersect(&start_mask, area_p, &vdb->area);

    /*Get the most top object which is not covered by others*/
    top_act_scr = ug_refr_get_top_obj(&start_mask, ug_disp_get_actscr(disp_refr));
    if(disp_refr->prev_scr) {
        top_prev_scr = ug_refr_get_top_obj(&start_mask, disp_refr->prev_scr);
    }

    /*Draw a display background if there is no top object*/
    if(top_act_scr == NULL && top_prev_scr == NULL) {
        if(disp_refr->bg_img) {
            //TODO: ug_draw_img(&a, &start_mask, disp_refr->bg_img, &dsc);
        } else {
            ug_draw_rect_dsc_t dsc;
            ug_draw_rect_dsc_init(&dsc);
            dsc.bg_color = disp_refr->bg_color;
            ug_draw_rect(&start_mask, &start_mask, &dsc);

        }
    }
    /*Refresh the previous screen if any*/
    if(disp_refr->prev_scr) {
        /*Get the most top object which is not covered by others*/
        if(top_prev_scr == NULL) {
            top_prev_scr = disp_refr->prev_scr;
        }
        /*Do the refreshing from the top object*/
        ug_refr_obj_and_children(top_prev_scr, &start_mask);

    }


    if(top_act_scr == NULL) {
         top_act_scr = disp_refr->act_scr;
     }
    /*Do the refreshing from the top object*/
    ug_refr_obj_and_children(top_act_scr, &start_mask);

    /* In true double buffered mode flush only once when all areas were rendered.
     * In normal mode flush after every area */
    if(ug_disp_is_true_double_buf(disp_refr) == false) {
        ug_refr_vdb_flush();
    }
}

在只有一块显存的模式下,如果正在刷屏传输中,则会等待传输完成。

然后会获取当前区域的最TOP 的对象,从它开始刷新。

何谓最TOP的对象呢?就是这个区域内最底层的对象,它会从屏幕这个obj 开始寻找 子obj ,找到一个完全覆盖区域的子对象。

也就是说,当屏幕中有两个正方体 A 和 B,B 是 A的儿子。A比B小并在B里面,寻找的时候就会返回B。如果A和B一样大,那么B就会遮挡住A,所以返回的时候就是返回A的儿子B。

最后会调用ug_refr_obj_and_children() 来刷新。

ug_refr_obj_and_children()

函数代码如下:


/**
 * 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 ug_refr_obj_and_children(ug_obj_t * top_p, const ug_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 = ug_disp_get_actscr(disp_refr);
    if(top_p == NULL) return;  /*Shouldn't happen*/

    /*Refresh the top object and its children*/
    ug_refr_obj(top_p, mask_p);

    /*Draw the 'younger' sibling objects because they can be on top_obj */
    ug_obj_t * par;
    ug_obj_t * border_p = top_p;

    par = ug_obj_get_parent(top_p);

    /*Do until not reach the screen*/
    while(par != NULL) {
        /*object before border_p has to be redrawn*/
        ug_obj_t * i = _ug_ll_get_prev(&(par->child_ll), border_p);

        while(i != NULL) {
            /*Refresh the objects*/
            ug_refr_obj(i, mask_p);
            i = _ug_ll_get_prev(&(par->child_ll), i);
        }

        /*Call the post draw design function of the parents of the to object*/
        if(par->design_cb) par->design_cb(par, mask_p, UG_DESIGN_DRAW_POST);

        /*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 = ug_obj_get_parent(par);
    }
}

/**
 * Refresh an object and 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 ug_refr_obj(ug_obj_t * obj, const ug_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 */
    ug_area_t obj_mask;
    ug_area_t obj_ext_mask;
    ug_area_t obj_area;
    ug_coord_t ext_size = obj->ext_draw_pad;
    ug_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 = _ug_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 */
        if(obj->design_cb) obj->design_cb(obj, &obj_ext_mask, UG_DESIGN_DRAW_MAIN);

        /*Create a new 'obj_mask' without 'ext_size' because the children can't be visible there*/
        ug_obj_get_coords(obj, &obj_area);
        union_ok = _ug_area_intersect(&obj_mask, mask_ori_p, &obj_area);
        if(union_ok != false) {
            ug_area_t mask_child; /*Mask from obj and its child*/
            ug_obj_t * child_p;
            ug_area_t child_area;
            _UG_LL_READ_BACK(obj->child_ll, child_p) {
                ug_obj_get_coords(child_p, &child_area);
                ext_size = child_p->ext_draw_pad;
                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 = _ug_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*/
                    ug_refr_obj(child_p, &mask_child);
                }
            }
        }

        /* If all the children are redrawn make 'post draw' design */
        if(obj->design_cb) obj->design_cb(obj, &obj_ext_mask, UG_DESIGN_DRAW_POST);
    }
}