Mercurial > MadButterfly
diff src/redraw_man.c @ 314:6c350fc92ae3
Cache rednering result is now workable.
- Know issues
- For unknow issue, CAIRO_OPERATOR_CLEAR will not be limited by clipping.
Image will be cleaned in a strange way. Maybe, it is a bug of Cairo.
author | Thinker K.F. Li <thinker@branda.to> |
---|---|
date | Thu, 05 Mar 2009 00:54:42 +0800 |
parents | 13ce87b6dbf5 |
children | d0f8642d3508 |
line wrap: on
line diff
--- a/src/redraw_man.c Thu Mar 05 00:54:42 2009 +0800 +++ b/src/redraw_man.c Thu Mar 05 00:54:42 2009 +0800 @@ -1,6 +1,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <math.h> #include <cairo.h> #include "mb_types.h" #include "mb_shapes.h" @@ -31,8 +32,7 @@ #define sh_detach_coord(sh) do { (sh)->coord = NULL; } while(0) #define rdman_is_dirty(rdman) \ ((rdman)->dirty_coords.num != 0 || \ - (rdman)->dirty_geos.num != 0 || \ - (rdman)->dirty_areas.num != 0) + (rdman)->dirty_geos.num != 0) #define OK 0 #define ERR -1 @@ -132,15 +132,27 @@ static int add_dirty_coord(redraw_man_t *rdman, coord_t *coord) { coord->flags |= COF_DIRTY; ADD_DATA(coords, dirty_coords, coord); + return OK; } static int add_dirty_geo(redraw_man_t *rdman, geo_t *geo) { geo->flags |= GEF_DIRTY; ADD_DATA(geos, dirty_geos, geo); + return OK; } -static int add_dirty_area(redraw_man_t *rdman, area_t *area) { - ADD_DATA(areas, dirty_areas, area); +static int add_dirty_area(redraw_man_t *rdman, coord_t *coord, area_t *area) { + int r; + + rdman->n_dirty_areas++; + r = areas_add(_coord_get_dirty_areas(coord), area); + return r == 0? OK: ERR; +} + +static int add_zeroing_coord(redraw_man_t *rdman, coord_t *coord) { + coord_set_zeroing(coord); + ADD_DATA(coords, zeroing_coords, coord); + return OK; } static int add_free_obj(redraw_man_t *rdman, void *obj, @@ -188,15 +200,11 @@ poses[1][1] = area->y + area->h;; } -static cairo_t *new_canvas(redraw_man_t *rdman) { +static cairo_t *canvas_new(int w, int h) { #ifndef UNITTEST + cairo_surface_t *surface; cairo_t *cr; - cairo_surface_t *surface, *cr_surface; - int w, h; - - cr_surface = cairo_get_target(rdman->cr); - w = cairo_image_surface_get_width(cr_surface); - h = cairo_image_surface_get_height(cr_surface); + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); cr = cairo_create(surface); @@ -207,12 +215,25 @@ #endif } -static void free_canvas(cairo_t *canvas) { +static void canvas_free(cairo_t *canvas) { #ifndef UNITTEST cairo_destroy(canvas); #endif } +static void canvas_get_size(cairo_t *canvas, int *w, int *h) { +#ifndef UNITTEST + cairo_surface_t *surface; + + surface = cairo_get_target(canvas); + *w = cairo_image_surface_get_width(surface); + *h = cairo_image_surface_get_height(surface); +#else + *w = 0; + *h = 0; +#endif +} + static int geo_off_in_coord(geo_t *geo, coord_t *coord) { int off = 0; geo_t *vgeo; @@ -246,6 +267,28 @@ coord->num_members--; } +static coord_canvas_info_t *coord_canvas_info_new(redraw_man_t *rdman, + coord_t *coord, + cairo_t *canvas) { + coord_canvas_info_t *info; + + info = (coord_canvas_info_t *)elmpool_elm_alloc(rdman->coord_canvas_pool); + if(info == NULL) + return info; + + info->owner = coord; + info->canvas = canvas; + DARRAY_INIT(&info->dirty_areas); + + return info; +} + +static void coord_canvas_info_free(redraw_man_t *rdman, + coord_canvas_info_t *info) { + DARRAY_DESTROY(&info->dirty_areas); + elmpool_elm_free(rdman->coord_canvas_pool, info); +} + static void mouse_event_root_dummy(event_t *evt, void *arg) { } @@ -257,6 +300,11 @@ memset(rdman, 0, sizeof(redraw_man_t)); + DARRAY_INIT(&rdman->dirty_coords); + DARRAY_INIT(&rdman->dirty_geos); + DARRAY_INIT(&rdman->gen_geos); + DARRAY_INIT(&rdman->zeroing_coords); + rdman->geo_pool = elmpool_new(sizeof(geo_t), 128); rdman->coord_pool = elmpool_new(sizeof(coord_t), 16); rdman->shnode_pool = elmpool_new(sizeof(shnode_t), 16); @@ -264,9 +312,10 @@ rdman->subject_pool = elmpool_new(sizeof(subject_t), 32); rdman->paint_color_pool = elmpool_new(_paint_color_size, 64); rdman->pent_pool = elmpool_new(sizeof(mb_prop_entry_t), 128); + rdman->coord_canvas_pool = elmpool_new(sizeof(coord_canvas_info_t), 16); if(!(rdman->geo_pool && rdman->coord_pool && rdman->shnode_pool && rdman->observer_pool && rdman->subject_pool && - rdman->paint_color_pool)) + rdman->paint_color_pool && rdman->coord_canvas_pool)) goto err; rdman->ob_factory.subject_alloc = ob_subject_alloc; @@ -283,7 +332,7 @@ goto err; addrm_ob = subject_add_observer(rdman->addrm_monitor, - addrm_monitor_hdlr, NULL); + addrm_monitor_hdlr, rdman); if(addrm_ob == NULL) goto err; @@ -299,7 +348,8 @@ rdman->root_coord, OBJT_COORD); rdman->root_coord->flags |= COF_OWN_CANVAS; - rdman->root_coord->canvas = cr; + rdman->root_coord->canvas_info = + coord_canvas_info_new(rdman, rdman->root_coord, cr); rdman->root_coord->opacity = 1; rdman->cr = cr; @@ -334,6 +384,12 @@ elmpool_free(rdman->paint_color_pool); if(rdman->pent_pool) elmpool_free(rdman->pent_pool); + if(rdman->coord_canvas_pool) + elmpool_free(rdman->coord_canvas_pool); + DARRAY_DESTROY(&rdman->dirty_coords); + DARRAY_DESTROY(&rdman->dirty_geos); + DARRAY_DESTROY(&rdman->gen_geos); + DARRAY_DESTROY(&rdman->zeroing_coords); return ERR; } @@ -375,7 +431,9 @@ } if(saved_shape != NULL) rdman_shape_free(rdman, saved_shape); - + + coord_canvas_info_free(rdman, rdman->root_coord->canvas_info); + elmpool_free(rdman->coord_pool); elmpool_free(rdman->geo_pool); elmpool_free(rdman->shnode_pool); @@ -383,11 +441,12 @@ elmpool_free(rdman->subject_pool); elmpool_free(rdman->paint_color_pool); elmpool_free(rdman->pent_pool); + elmpool_free(rdman->coord_canvas_pool); DARRAY_DESTROY(&rdman->dirty_coords); DARRAY_DESTROY(&rdman->dirty_geos); - DARRAY_DESTROY(&rdman->dirty_areas); DARRAY_DESTROY(&rdman->gen_geos); + DARRAY_DESTROY(&rdman->zeroing_coords); } @@ -558,7 +617,7 @@ /*! \note default opacity == 1 */ coord->opacity = 1; if(parent) - coord->canvas = parent->canvas; + coord->canvas_info = parent->canvas_info; rdman->n_coords++; coord->order = ++rdman->next_coord_order; @@ -636,9 +695,11 @@ if(cm_cnt || rdman_is_dirty(rdman)) return rdman_coord_free_postponse(rdman, coord); - /* Free canvas (\ref redraw) */ - if(coord->flags & COF_OWN_CANVAS) - free_canvas(coord->canvas); + /* Free canvas and canvas_info (\ref redraw) */ + if(coord->flags & COF_OWN_CANVAS) { + canvas_free(_coord_get_canvas(coord)); + coord_canvas_info_free(rdman, coord->canvas_info); + } RM_CHILD(parent, coord); subject_free(coord->mouse_event); @@ -732,6 +793,11 @@ } add_dirty_coord(rdman, child); + + if(child->flags & COF_OWN_CANVAS) { + preorder_coord_skip_subtree(child); + continue; + } } return OK; @@ -813,45 +879,46 @@ sh_show(shape); } -/*! \brief Setup canvas for the coord. +/*! \brief Setup canvas_info for the coord. * * Own a canvas or inherit it from parent. * \sa * - \ref redraw */ -static void setup_canvas(redraw_man_t *rdman, coord_t *coord) { +static void setup_canvas_info(redraw_man_t *rdman, coord_t *coord) { if(coord->parent == NULL) return; - if(coord->opacity != 1) { + if(coord->opacity != 1 || coord_is_cached(coord)) { if(!(coord->flags & COF_OWN_CANVAS)) { - coord->canvas = new_canvas(rdman); + /* canvas is assigned latter, in zeroing_coord() */ + coord->canvas_info = coord_canvas_info_new(rdman, coord, NULL); coord->flags |= COF_OWN_CANVAS; } } else { if(coord->flags & COF_OWN_CANVAS) { - free_canvas(coord->canvas); + canvas_free(_coord_get_canvas(coord)); + coord_canvas_info_free(rdman, coord->canvas_info); coord->flags &= ~COF_OWN_CANVAS; } - coord->canvas = coord->parent->canvas; + /* This must here to keep coords that do not own canvas + * can always point to right canvas_info. Since, they + * don't know when will parent change it's canvas_info. + */ + coord->canvas_info = coord->parent->canvas_info; } } -static int clean_coord(redraw_man_t *rdman, coord_t *coord) { +static int coord_clean_members_n_compute_area(coord_t *coord) { geo_t *geo; /*! \note poses is shared by invokings, it is not support reentrying. */ static co_aix (*poses)[2]; static int max_poses = 0; int cnt, pos_cnt; - - setup_canvas(rdman, coord); - - compute_aggr_of_coord(coord); - + /* Clean member shapes. */ cnt = 0; FORMEMBERS(coord, geo) { - SWAP(geo->cur_area, geo->last_area, area_t *); clean_shape(geo->shape); cnt++; } @@ -871,9 +938,153 @@ pos_cnt += 2; } - SWAP(coord->cur_area, coord->last_area, area_t *); area_init(coord->cur_area, pos_cnt, poses); + return OK; +} + +/*! \brief Shift space of coord to align left-top of minimum covering. + * + * Align left-top of minimum rectangle covering occupied area of + * sub-graphic to origin of the space. + */ +static +void zeroing_coord(redraw_man_t *rdman, coord_t *coord) { + coord_t *cur; + area_t *area; + co_aix min_x, min_y; + co_aix max_x, max_y; + co_aix x, y; + int w, h; + int c_w, c_h; + cairo_t *canvas; + co_aix *aggr; + co_aix poses[2][2]; + + if(coord->parent == NULL) /*! \note Should not zeroing root coord */ + abort(); + if(!(coord_is_zeroing(coord))) + abort(); + + coord_clear_zeroing(coord); + + /* + * Compute minimum overing area of sub-graphic + */ + area = coord_get_area(coord); + min_x = area->x; + min_y = area->y; + max_x = min_x + area->w; + max_y = min_y + area->h; + + FOR_COORDS_PREORDER(coord, cur) { + area = coord_get_area(cur); + if(area->x < min_x) + min_x = area->x; + if(area->y < min_y) + min_y = area->y; + + x = area->x + area->w; + y = area->y + area->h; + + if(x > max_x) + max_x = x; + if(y > max_y) + max_y = y; + if(cur->flags & COF_OWN_CANVAS) + preorder_coord_skip_subtree(cur); + } + + w = max_x - min_x; + h = max_y - min_y; + + /* + * Setup area of the coord + */ + aggr = coord_get_aggr_matrix(coord); + x = y = 0; + coord_trans_pos(coord->parent, &x, &y); + poses[0][0] = x; + poses[0][1] = y; + x = w; + y = h; + coord_trans_pos(coord->parent, &x, &y); + poses[1][0] = x; + poses[1][1] = y; + + area_init(coord_get_area(coord), 2, poses); + + canvas = _coord_get_canvas(coord); + if(canvas) + canvas_get_size(canvas, &c_w, &c_h); + else + c_w = c_h = 0; + + if(!coord_get_flags(coord, COF_JUST_CLEAN) && + min_x >= 0 && min_y >= 0 && max_x <= c_w && max_y <= c_h) + /* Canvas fully cover sub-graphic. */ + return; + + /* + * Adjust matrics of descendants to align left-top corner of + * minimum covering area with origin of space defined by + * zeroing coord. + */ + FOR_COORDS_PREORDER(coord, cur) { + aggr = coord_get_aggr_matrix(cur); + aggr[3] -= min_x; + aggr[5] -= min_y; + if(coord_get_flags(cur, COF_OWN_CANVAS)) { + /* + * Coords, zeroing, is zeroed in preorder of tree. + * So, they are zeroed after ancesters with correctly + * coord_t::aggr_matrix of parent coord to zeroing. + */ + preorder_coord_skip_subtree(cur); + area = coord_get_area(cur); + area->x -= min_x; + area->y -= min_y; + } else + coord_clean_members_n_compute_area(cur); + } + + /* + * Setup canvas + */ + if(canvas == NULL || w > c_w || h > c_w) { + if(canvas) + canvas_free(canvas); + canvas = canvas_new(w, h); + _coord_set_canvas(coord, canvas); + } + + area = &coord->canvas_info->cached_dirty_area; + area->x = 0; + area->y = 0; + area->w = w; + area->h = h; + DARRAY_CLEAN(_coord_get_dirty_areas(coord)); + add_dirty_area(rdman, coord, area); +} + +/*! \brief Clean dirty coords. + * + * \note coords their opacity != 1 are also traded as cached ones. + */ +static int clean_coord(redraw_man_t *rdman, coord_t *coord) { + int r; + + setup_canvas_info(rdman, coord); + + if(coord->flags & COF_OWN_CANVAS) + compute_aggr_of_cached_coord(coord); + else + compute_aggr_of_coord(coord); + + r = coord_clean_members_n_compute_area(coord); + if(r != OK) + return ERR; + coord->flags &= ~COF_DIRTY; return OK; @@ -900,10 +1111,9 @@ if(r != OK) return ERR; /* These two steps can be avoided for drawing all. */ - add_dirty_area(rdman, &coord->areas[0]); - add_dirty_area(rdman, &coord->areas[1]); + add_dirty_area(rdman, coord, &coord->areas[0]); + add_dirty_area(rdman, coord, &coord->areas[1]); } - rdman->dirty_coords.num = 0; } return OK; } @@ -913,6 +1123,7 @@ int n_dirty_geos; geo_t **dirty_geos; geo_t *visit_geo; + coord_t *coord; n_dirty_geos = rdman->dirty_geos.num; if(n_dirty_geos > 0) { @@ -922,28 +1133,303 @@ if(!(visit_geo->flags & GEF_DIRTY)) continue; - SWAP(visit_geo->cur_area, visit_geo->last_area, area_t *); clean_shape(visit_geo->shape); - add_dirty_area(rdman, visit_geo->cur_area); - add_dirty_area(rdman, visit_geo->last_area); + coord = geo_get_coord(visit_geo); + add_dirty_area(rdman, coord, visit_geo->cur_area); + add_dirty_area(rdman, coord, visit_geo->last_area); + } + } + + return OK; +} + +/*! \brief Add canvas owner of dirty geos to coord_t::zeroing_coords. + * + * All possible coords that need a zeroing have at least one dirty geo. + */ +static int add_rdman_zeroing_coords(redraw_man_t *rdman) { + int i; + int n_dirty_geos; + geo_t **dirty_geos, *geo; + int n_dirty_coords; + coord_t **dirty_coords, *coord; + + n_dirty_geos = rdman->dirty_geos.num; + dirty_geos = rdman->dirty_geos.ds; + for(i = 0; i < n_dirty_geos; i++) { + geo = dirty_geos[i]; + coord = geo_get_coord(geo)->canvas_info->owner; + while(!coord_get_flags(coord, COF_MUST_ZEROING | COF_TEMP_MARK)) { + coord_set_flags(coord, COF_TEMP_MARK); + if(coord_is_root(coord)) + break; + coord = coord->parent->canvas_info->owner; + } + } + + n_dirty_coords = rdman->dirty_coords.num; + dirty_coords = rdman->dirty_coords.ds; + for(i = 0; i < n_dirty_coords; i++) { + coord = dirty_coords[i]->canvas_info->owner; + while(!coord_get_flags(coord, COF_MUST_ZEROING | COF_TEMP_MARK)) { + coord_set_flags(coord, COF_TEMP_MARK); + if(coord_is_root(coord)) + break; + coord = coord->parent->canvas_info->owner; + } + } + + FOR_COORDS_PREORDER(rdman->root_coord, coord) { + if(!coord_get_flags(coord, COF_TEMP_MARK)) { + preorder_coord_skip_subtree(coord); + continue; } - rdman->dirty_geos.num = 0; - } + add_zeroing_coord(rdman, coord); + coord_clear_flags(coord, COF_TEMP_MARK); + } + + return OK; +} + +/*! \brief Zeroing coords in redraw_man_t::zeroing_coords. + * + * \note redraw_man_t::zeroing_coords must in ascent partial order of tree. + */ +static int zeroing_rdman_coords(redraw_man_t *rdman) { + int i; + coords_t *all_zeroing; + coord_t *coord; + + all_zeroing = &rdman->zeroing_coords; + for(i = all_zeroing->num - 1; i >= 0; i--) { + coord = all_zeroing->ds[i]; + if(coord_is_root(coord)) + continue; + zeroing_coord(rdman, coord); + } + + return OK; +} + +/* \brief Compute matrix from cached canvas to parent device space. + */ +static void compute_cached_2_pdev_matrix(coord_t *coord, + co_aix canvas2pdev_matrix[6]) { + coord_t *parent; + co_aix *aggr; + co_aix *matrix, *paggr; + co_aix scale_x, scale_y; + co_aix shift_x, shift_y; + co_aix canvas2p[6]; + + aggr = coord_get_aggr_matrix(coord); + matrix = coord->matrix; + parent = coord->parent; + paggr = coord_get_aggr_matrix(parent); + + scale_x = matrix[0] / aggr[0]; + scale_y = matrix[3] / aggr[3]; + shift_x = matrix[2] - scale_x * aggr[2]; + shift_y = matrix[5] - scale_y * aggr[5]; + + canvas2p[0] = scale_x; + canvas2p[1] = 0; + canvas2p[2] = shift_x; + canvas2p[3] = 0; + canvas2p[4] = scale_y; + canvas2p[5] = shift_y; + + matrix_mul(paggr, canvas2p, canvas2pdev_matrix); +} + +/*! \brief Add aggregated dirty areas to ancestor. + * + * Dirty areas are aggregated into two areas. It assumes that even or odd + * ones are old areas or new areas repsective. So, all even ones are + * aggregated in an area, and odd ones are in another. + */ +static void add_aggr_dirty_areas_to_ancestor(redraw_man_t *rdman, + coord_t *coord) { + int i; + int n_areas; + co_aix poses0[2][2], poses1[2][2]; + co_aix reverse[6]; + co_aix canvas2pdev_matrix[6]; + area_t **areas, *area; + area_t *area0, *area1; + coord_t *parent, *pcached_coord; + + n_areas = _coord_get_dirty_areas(coord)->num; + areas = _coord_get_dirty_areas(coord)->ds; + if(n_areas == 0) + abort(); /* should not happen! */ + + area0 = coord->canvas_info->aggr_dirty_areas; + area1 = area0 + 1; + + for(i = 0; i < n_areas; i++) { + area = areas[i]; + if(area->w != 0 || area->h != 0) + break; + } + + if(i < n_areas) { + area = areas[i++]; + poses0[0][0] = area->x; + poses0[0][1] = area->y; + poses0[1][0] = area->x + area->w; + poses0[1][1] = area->y + area->h; + } else { + poses0[0][0] = 0; + poses0[0][1] = 0; + poses0[1][0] = 0; + poses0[1][1] = 0; + } + + if(i < n_areas) { + area = areas[i++]; + poses1[0][0] = area->x; + poses1[0][1] = area->y; + poses1[1][0] = area->x + area->w; + poses1[1][1] = area->y + area->h; + } else { + poses1[0][0] = 0; + poses1[0][1] = 0; + poses1[1][0] = 0; + poses1[1][1] = 0; + } + + for(; i < n_areas - 1;) { + /* Even areas */ + area = areas[i++]; + if(area->w != 0 || area->h != 0) { + poses0[0][0] = MIN(poses0[0][0], area->x); + poses0[0][1] = MIN(poses0[0][1], area->y); + poses0[1][0] = MAX(poses0[1][0], area->x + area->w); + poses0[1][1] = MAX(poses0[1][1], area->y + area->h); + } + /* Odd areas */ + area = areas[i++]; + if(area->w != 0 || area->h != 0) { + poses1[0][0] = MIN(poses1[0][0], area->x); + poses1[0][1] = MIN(poses1[0][1], area->y); + poses1[1][0] = MAX(poses1[1][0], area->x + area->w); + poses1[1][1] = MAX(poses1[1][1], area->y + area->h); + } + } + + if(i < n_areas) { + area = areas[i]; + if(area->w != 0 || area->h != 0) { + poses0[0][0] = MIN(poses0[0][0], area->x); + poses0[0][1] = MIN(poses0[0][1], area->y); + poses0[1][0] = MAX(poses0[1][0], area->x + area->w); + poses0[1][1] = MAX(poses0[1][1], area->y + area->h); + } + } + + parent = coord->parent; + pcached_coord = parent->canvas_info->owner; + + compute_cached_2_pdev_matrix(coord, canvas2pdev_matrix); + + matrix_trans_pos(canvas2pdev_matrix, poses0[0], poses0[0] + 1); + matrix_trans_pos(canvas2pdev_matrix, poses0[1], poses0[1] + 1); + area_init(area0, 2, poses0); + add_dirty_area(rdman, pcached_coord, area0); + + matrix_trans_pos(canvas2pdev_matrix, poses1[0], poses1[0] + 1); + matrix_trans_pos(canvas2pdev_matrix, poses1[1], poses1[1] + 1); + area_init(area1, 2, poses1); + if(area1->w != 0 || area1->h != 0) + add_dirty_area(rdman, pcached_coord, area1); + + if(coord_get_flags(coord, COF_JUST_CLEAN) && + !coord_get_flags(pcached_coord, COF_JUST_CLEAN)) + add_dirty_area(rdman, pcached_coord, coord->last_area); +} + +static int add_rdman_aggr_dirty_areas(redraw_man_t *rdman) { + int i; + int n_zeroing; + coord_t **zeroings; + coord_t *coord; + + n_zeroing = rdman->zeroing_coords.num; + zeroings = rdman->zeroing_coords.ds; + for(i = n_zeroing - 1; i >= 0; i--) { + coord = zeroings[i]; + if(!coord_is_root(coord)) + add_aggr_dirty_areas_to_ancestor(rdman, coord); + } + + return OK; +} + +static int add_rdman_cached_dirty_areas(redraw_man_t *rdman) { + int i; + coord_t *coord, **dirty_coords; + int n_dirty_coords; + + n_dirty_coords = rdman->dirty_coords.num; + dirty_coords = rdman->dirty_coords.ds; + for(i = 0; i < n_dirty_coords; i++) { + coord = dirty_coords[i]; + if(coord_get_flags(coord, COF_OWN_CANVAS)) { + add_dirty_area(rdman, coord, coord->cur_area); + add_dirty_area(rdman, coord, coord->last_area); + } + } return OK; } static int clean_rdman_dirties(redraw_man_t *rdman) { int r; + int i; + coord_t **coords; + geo_t **geos; + + coords = rdman->dirty_coords.ds; + for(i = 0; i < rdman->dirty_coords.num; i++) + if(coords[i]->flags & COF_DIRTY) + SWAP(coords[i]->cur_area, coords[i]->last_area, area_t *); + + geos = rdman->dirty_geos.ds; + for(i = 0; i < rdman->dirty_geos.num; i++) + if(geos[i]->flags & GEF_DIRTY) + SWAP(geos[i]->cur_area, geos[i]->last_area, area_t *); r = clean_rdman_coords(rdman); if(r != OK) return ERR; + + for(i = 0; i < rdman->dirty_coords.num; i++) + coord_set_flags(rdman->dirty_coords.ds[i], COF_JUST_CLEAN); r = clean_rdman_geos(rdman); if(r != OK) return ERR; + r = add_rdman_zeroing_coords(rdman); + if(r != OK) + return ERR; + + r = zeroing_rdman_coords(rdman); + if(r != OK) + return ERR; + + r = add_rdman_aggr_dirty_areas(rdman); + if(r != OK) + return ERR; + + r = add_rdman_cached_dirty_areas(rdman); + if(r != OK) + return ERR; + + for(i = 0; i < rdman->dirty_coords.num; i++) + coord_clear_flags(rdman->dirty_coords.ds[i], COF_JUST_CLEAN); + return OK; } @@ -1028,22 +1514,42 @@ } #ifndef UNITTEST +static void clear_canvas(canvas_t *canvas) { + cairo_operator_t old_op; + +#if 1 + old_op = cairo_get_operator(canvas); + cairo_set_operator(canvas, CAIRO_OPERATOR_CLEAR); + cairo_paint(canvas); + cairo_set_operator(canvas, old_op); +#else + cairo_set_source_rgba(canvas, 0, 0, 0, 0); + cairo_paint(canvas); +#endif +} + static void clean_canvas(cairo_t *cr, co_aix w, co_aix h) { + cairo_operator_t saved_op; + + saved_op = cairo_get_operator(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + /*! \todo clean to background color. */ - cairo_set_source_rgb(cr, 1, 1, 1); -#if 1 + cairo_set_source_rgba(cr, 1, 1, 1, 1); + /* For some unknown reasons, cairo_paint() can not erease * painted graphic cleanly. So, cairo_fill() are used to * replace it. */ cairo_rectangle(cr, 0, 0, w, h); cairo_fill(cr); -#else - cairo_paint(cr); -#endif + + cairo_set_operator(cr, saved_op); } static void clean_canvas_black(cairo_t *cr, co_aix w, co_aix h) { + clear_canvas(cr); + /*! \todo clean to background color. */ cairo_set_source_rgba(cr, 0, 0, 0, 0); cairo_paint(cr); @@ -1061,16 +1567,21 @@ cairo_clip(cr); } -static void reset_clip(redraw_man_t *rdman) { - cairo_reset_clip(rdman->backend); +static void reset_clip(canvas_t *cr) { + cairo_reset_clip(cr); } static void copy_cr_2_backend(redraw_man_t *rdman, int n_dirty_areas, area_t **dirty_areas) { + cairo_operator_t saved_op; + if(n_dirty_areas) make_clip(rdman->backend, n_dirty_areas, dirty_areas); + saved_op = cairo_get_operator(rdman->backend); + cairo_set_operator(rdman->backend, CAIRO_OPERATOR_SOURCE); cairo_paint(rdman->backend); + cairo_set_operator(rdman->backend, saved_op); } #else /* UNITTEST */ static void clean_canvas(cairo_t *cr, co_aix w, co_aix h) { @@ -1079,7 +1590,7 @@ static void clean_canvas_black(cairo_t *cr, co_aix w, co_aix h) { } -static void reset_clip(redraw_man_t *rdman) { +static void reset_clip(canvas_t *cr) { } static void copy_cr_2_backend(redraw_man_t *rdman, int n_dirty_areas, @@ -1087,54 +1598,88 @@ } #endif /* UNITTEST */ -static int is_geo_in_areas(geo_t *geo, +static int is_area_in_areas(area_t *area, int n_areas, area_t **areas) { int i; for(i = 0; i < n_areas; i++) { - if(areas_are_overlay(geo->cur_area, areas[i])) + if(areas_are_overlay(area, areas[i])) return 1; } return 0; } -static void update_canvas_2_parent(redraw_man_t *rdman, coord_t *coord) { +static int is_geo_in_areas(geo_t *geo, + int n_areas, + area_t **areas) { + return is_area_in_areas(geo->cur_area, n_areas, areas); +} + +static void update_cached_canvas_2_parent(redraw_man_t *rdman, + coord_t *coord) { cairo_t *pcanvas, *canvas; cairo_surface_t *surface; + cairo_pattern_t *pattern; + cairo_matrix_t cr_matrix; + co_aix reverse[6]; + co_aix canvas2pdev_matrix[6]; - if(coord == rdman->root_coord) + if(coord_is_root(coord)) return; - canvas = coord->canvas; - pcanvas = coord->parent->canvas; + compute_cached_2_pdev_matrix(coord, canvas2pdev_matrix); + compute_reverse(canvas2pdev_matrix, reverse); + + cr_matrix.xx = reverse[0]; + cr_matrix.xy = reverse[1]; + cr_matrix.x0 = reverse[2]; + cr_matrix.yx = reverse[3]; + cr_matrix.yy = reverse[4]; + cr_matrix.y0 = reverse[5]; + + canvas = _coord_get_canvas(coord); + pcanvas = _coord_get_canvas(coord->parent); surface = cairo_get_target(canvas); - cairo_set_source_surface(pcanvas, surface, 0, 0); + pattern = cairo_pattern_create_for_surface(surface); + cairo_pattern_set_matrix(pattern, &cr_matrix); + cairo_set_source(pcanvas, pattern); cairo_paint_with_alpha(pcanvas, coord->opacity); } -static int draw_coord_shapes_in_areas(redraw_man_t *rdman, - coord_t *coord, - int n_areas, - area_t **areas) { +static int draw_coord_shapes_in_dirty_areas(redraw_man_t *rdman, + coord_t *coord) { int dirty = 0; int r; + area_t **areas; + int n_areas; + cairo_t *canvas; geo_t *member; coord_t *child; - cairo_t *canvas; int mem_idx; if(coord->flags & COF_HIDDEN) return OK; - - canvas = coord->canvas; + + areas = _coord_get_dirty_areas(coord)->ds; + n_areas = _coord_get_dirty_areas(coord)->num; + canvas = _coord_get_canvas(coord); + member = FIRST_MEMBER(coord); mem_idx = 0; child = FIRST_CHILD(coord); while(child != NULL || member != NULL) { if(child && child->before_pmem == mem_idx) { - r = draw_coord_shapes_in_areas(rdman, child, n_areas, areas); - dirty |= r; + if(child->flags & COF_OWN_CANVAS) { + if(!(child->flags & COF_HIDDEN) && + is_area_in_areas(coord_get_area(child), n_areas, areas)) { + update_cached_canvas_2_parent(rdman, child); + dirty = 1; + } + } else { + r = draw_coord_shapes_in_dirty_areas(rdman, child); + dirty |= r; + } child = NEXT_CHILD(child); } else { ASSERT(member != NULL); @@ -1149,18 +1694,45 @@ } } - if(dirty && coord->flags & COF_OWN_CANVAS) { - update_canvas_2_parent(rdman, coord); - clean_canvas_black(coord->canvas, rdman->w, rdman->h); - } - return dirty; } -static void draw_shapes_in_areas(redraw_man_t *rdman, - int n_areas, - area_t **areas) { - draw_coord_shapes_in_areas(rdman, rdman->root_coord, n_areas, areas); +static int draw_dirty_cached_coord(redraw_man_t *rdman, + coord_t *coord) { + area_t **areas, *area; + int n_areas; + cairo_t *canvas; + int i; + int r; + + areas = _coord_get_dirty_areas(coord)->ds; + n_areas = _coord_get_dirty_areas(coord)->num; + + for(i = 0; i < n_areas; i++) { + area = areas[i]; + area->x = floorf(area->x); + area->y = floorf(area->y); + area->w = ceilf(area->w); + area->h = ceilf(area->h); + } + + canvas = _coord_get_canvas(coord); + make_clip(canvas, n_areas, areas); + clear_canvas(canvas); + + r = draw_coord_shapes_in_dirty_areas(rdman, coord); + + reset_clip(canvas); +} + +static void draw_shapes_in_dirty_areas(redraw_man_t *rdman) { + int i; + coord_t *coord; + + for(i = rdman->zeroing_coords.num - 1; i >= 0; i--) { + coord = rdman->zeroing_coords.ds[i]; + draw_dirty_cached_coord(rdman, coord); + } } @@ -1198,29 +1770,43 @@ */ int rdman_redraw_changed(redraw_man_t *rdman) { int r; - int n_dirty_areas; - area_t **dirty_areas; event_t event; subject_t *redraw; - + int i; + coord_t *coord, **coords; + int n_areas; + area_t **areas; + r = clean_rdman_dirties(rdman); if(r != OK) return ERR; - n_dirty_areas = rdman->dirty_areas.num; - dirty_areas = rdman->dirty_areas.ds; - if(n_dirty_areas > 0) { + if(rdman->n_dirty_areas > 0) { /*! \brief Draw shapes in preorder of coord tree and support opacity * rules. */ - clean_canvas(rdman->cr, rdman->w, rdman->h); - draw_shapes_in_areas(rdman, n_dirty_areas, dirty_areas); - copy_cr_2_backend(rdman, rdman->dirty_areas.num, - rdman->dirty_areas.ds); - reset_clip(rdman); + draw_shapes_in_dirty_areas(rdman); + n_areas = _coord_get_dirty_areas(rdman->root_coord)->num; + areas = _coord_get_dirty_areas(rdman->root_coord)->ds; + copy_cr_2_backend(rdman, n_areas, areas); + reset_clip(rdman->backend); + for(i = 0; i < rdman->zeroing_coords.num; i++) { + coord = rdman->zeroing_coords.ds[i]; + DARRAY_CLEAN(_coord_get_dirty_areas(coord)); + } + rdman->n_dirty_areas = 0; } - rdman->dirty_areas.num = 0; + coords = rdman->zeroing_coords.ds; + for(i = 0; i < rdman->zeroing_coords.num; i++) { + coord = coords[i]; + coord_clear_flags(coord, COF_MUST_ZEROING); + } + + DARRAY_CLEAN(&rdman->dirty_coords); + DARRAY_CLEAN(&rdman->dirty_geos); + DARRAY_CLEAN(&rdman->zeroing_coords); + /* Free postponsed removing */ free_free_objs(rdman); @@ -1256,6 +1842,212 @@ * - rdman_redraw_all() * - rdman_redraw_changed() * - draw_shapes_in_areas() + * + * \section img_cache Image Cache + * It costs time to redraw every component in a complete graphic. + * Image cache try to cache result of prviously rendering, and reusing it + * to avoid wasting CPU time on repeatitive and redundant rendering. + * + * \ref COF_FAST_CACHE and \ref COF_PRECISE_CACHE are used to tag a + * coord that it's + * rendering result is cached in fast way or precise way. With fast cache, + * MB renders descendants of a coord in once, and reuse the result until it + * being dirty. With precise cache, it alike fast cache, but it also + * performs rendering when an ancester of the coord transform it to larger + * size, in width or height. + * + * coord_t::aggr_matrix of a cached coord is computed from aggr_matrix of + * parent. But, it does not use one from parent directly. parent one is + * transformed as + * \code + * cache_scale_x = sqrt(p_matrix[0]**2 + p_matrix[3]**2); + * cache_scale_y = sqrt(p_matrix[1]**2 + p_matrix[4]**2); + * cache_p_matrix[0] = cache_scale_x; + * cache_p_matrix[1] = 0; + * cache_p_matrix[2] = range_shift_x; + * cache_p_matrix[3] = 0; + * cache_p_matrix[4] = cache_scale_y; + * cache_p_matrix[5] = range_shift_y; + * \endcode + * where p_matrix is parent one, and cache_p_matrix is one derived from + * parent one. coord_t::aggr_matrix of a cached coord is + * \code + * aggr_matrix = cache_p_matrix * matrix + * \endcode + * where matrix is the transform being installed on the cached coord. + * range_shift_x and range_shift_y are defined above. + * + * cache_p_matrix rescales sub-graphic to an appropriately size + * (cache_scale_x, cache_scale_y) and aligns left-top of the minimum + * rectangle (range_shift_x, range_shift_y) that cover the area occupied + * by sub-graphic with origin of the space. + * + * The sub-graphic should be rendered on space defined by cache_p_matrix of + * cached one. But rendering result are transformed to the space defined + * by parent with following matrix. + * \code + * draw_matrix = reverse(p_matrix * reverse(cache_p_matrix)) + * \endcode + * With Cairo, draw_matrix is applied on source surface (canvas) + * to draw image to parent's surface (canvas). draw_matrix is a function + * map points from parent space to the space of cached one. + * + * Cached coords are marked for changing transformation of ancestors only if + * following condition is true. + * \code + * cache_scale_x < sqrt(p_matrix[0]**2 + p_matrix[3]**2) || + * cache_scale_y < sqrt(p_matrix[1]**2 + p_matrix[4]**2) + * \endcode + * where p_matrix is latest aggr_matrix of parent after changing + * transformation, and where cache_scale_* are ones mention above and computed + * before changing transformation of ancestors. + * + * Cache_scale_* can be recovered by following instructions. + * \code + * cache_scale_x = aggr_matrix[0] / matrix[0]; + * cache_scale_y = aggr_matrix[4] / matrix[4]; + * \endcode + * + * \section cache_area Area of cached coord + * - *_transform of shapes works as normal + * - areas of descendants of cached coord are in space defined + * by aggr_matrix of cached coord. + * - descendants are marked with \ref COF_ANCESTOR_CACHE + * + * Since *_transform of shapes compute area with aggr_matrix that is + * derived from aggr_matrix of a cached ancestor, area of + * \ref COF_ANCESTOR_CACHE ones should be transformed to device space in + * find_shape_at_pos() with following statement. + * \code + * area_matrix = p_matrix * reverse(cache_p_matrix) + * \endcode + * where cache_p_matrix and p_matrix are corresponding matrix of + * cached ancestor. We can also perform transforming in reversed + * direction to transform point to space defined by aggr_matrix of cached + * coord. + * + * Since it is costly to transform area of \ref COF_ANCESTOR_CACHE ones to + * device space if more than one ancestor are cached, no ancestor of + * cached coord can be set to cached. + * + * \section cached_bounding Bounding box of cached coord and descendants + * Bounding box of a cached coord and it's descendants is the range that + * cached coord and descendants are rendered on canvas. It is also called + * cached-bounding. + * + * range_shift_x and range_shift_y are computed by initailizing cache_p_matrix + * with range_shift_x == range_shift_y == 0 at first. cache_p_matrix is + * used to compute aggr_matrix and cached-bounding in turn. Then, + * range_shift_x and range_shift_y are initialized to negative of + * x-axis and y-axis, repectively, of left-top of cached-bounding. Then, + * aggr_matrix of cached coord and descendants are updated by + * following statements. + * \code + * aggr_matrix[2] += range_shift_x; + * aggr_matrix[5] += range_shift_y; + * \endcode + * The statements shift the spaces to make cached-bounding + * aligned to origin of coordinate system. + * The purpose of range_shift_* is to reduce size of canvas used to cache + * rendering result. The canvas are shrink to size the same as bounding + * box. + * + * \section cache_redraw How cache and redraw work together? + * When a coord and descedants are cached, the coord is flaged with + * COF_FAST_CACHE or COF_PRECISE_CACHE. When a coord is marked dirty, all + * descendants are also marked dirty by rdman except descendants of cached + * ones. But, cached ones are also marked dirty as normal ones. The + * reason to mark cached ones is giving them a chance to update their + * area. + * + * For precise cached descendants, above rule has an exception. They should + * also be marked dirty if cached coord should be rendered in a larger + * resize factor to get better output. + * + * coord_t::aggr_matrix and cached-bounding of cached coord must be computed + * in the way described in \ref cached_bounding. Propagating range_shift_* + * to descendants must skip cached ones and their descendants. + * Range_shift_* are computed after updating descendants. So, procedure + * of clean descendants of a cached one must performed in two phases. + * One for computing areas of descendants and one for propagating + * range_shift_*. + * + * A cached coord or/and descendants are dirty only for cached coord or + * descendants being marked dirty by application. Once a cached coord or + * descendant is marked dirty, all descendants of marked one are also + * marked. redraw_man_t::dirty_areas collects areas, in device space, + * that should be updated. All shapes overlaid with any area in + * redraw_man_t::dirty_areas should be redraw. Since descendants of cached + * coord compute their areas in spaces other than device space. + * Separated lists should be maintained for each cached coord and it's + * descendants. + * + * \section cache_imp Implementation of Cache + * Both cached coords and coords that opacity != 1 need a canvas to + * draw descendants on. Both cases are traded in the same way. + * Every of them own a canvas_info to describe canvas and related + * information. aggr_matrix of descendants must be adjusted to make + * left-top of range just at origin of canvas. It can save space by setting + * just large enough to hold rendering result of descendants. The process + * of adjusting is zeroing. + * + * Following is rules. + * - zeroing on a cached coord is performed by adjust coord_t::aggr_matrix + * of the cached coord and descendnats. + * - Clean coords works just like before without change. + * - in preorder + * - never perform zeroing on root_coord. + * - zeroing on cached coords marked with \ref COF_MUST_ZEROING. + * - when clean a descendant that moves out-side of it's canvas, + * respective cached coord is marked with \ref COF_MUST_ZEROING. + * - zeroing is performed immediately after clean coords. + * - zeroing will not propagate acrossing boundary of cached coord. + * - It will be stopped at descendants which are cached coords. + * - coord_t::cur_area and coord_t::aggr_matrix of cached coords + * must be ajdusted. + * - the area of a cached coord is defined in parent space. + * - areas of descendants are defined in space defined by aggr_matrix of + * cached coord. + * - parent know the area in where cached coord and descendnats will + * be draw. + * - cached coords keep their private dirty area list. + * - private dirty areas of a cached coord are transformed and added to + * parent cached coord. + * - aggregates areas before adding to parent. + * - canvas of a cached coord is updated if + * - descendants are dirty, or + * - it-self is dirty. + * - change of a canvas must copy to canvas of parent space. + * - a cached is updated if canvas of descendant cached coord is updated. + * - updating canvas is performed by redraw dirty areas. + * - since dirty areas of cached ones would be aggregated and added to + * parent, parent cached coord would copy it from cache of descedants. + * - descendant cached coords must be updated before ancestor cached coords. + * - add dirty areas to parent immediately after updating canvas. + * - Making dirty coords is not propagated through cached ones. + * - cached ones are also made dirty, but stop after that. + * + * Steps: + * - SWAP coord_t::cur_area of dirty coords. + * - SWAP geo_t::cur_area of dirty geos. + * - clean coords + * - coord_t::aggr_matrix of cached coord is not the same as non-cached. + * - see \ref img_cache + * - clean geos + * - Add canvas owner of dirty geos to redraw_man_t::zeroing_coords + * - Cached ancestors of redraw_man_t::dirty_geos + * - Cached ancestors of redraw_man_t::dirty_coords + * - Cached ancestors of zeroed ones should also be zeroed. + * - zeroing + * - Add more dirty areas if canvas should be fully redrawed. + * - From leaf to root. + * - add aggregated dirty areas from descendant cached coords to ancestors. + * - Must include old area of cached coords if it is just clean and + * parent cached one is not just clean. + * - Just clean is a coord cleaned in last time of cleaning coords. + * - draw dirty areas + * - areas are rounded to N at first. + * - from leaf to root. */ int rdman_redraw_all(redraw_man_t *rdman) { @@ -1274,7 +2066,7 @@ area.w = 1024; area.h = 1024; #endif - add_dirty_area(rdman, &area); + add_dirty_area(rdman, rdman->root_coord, &area); r = rdman_redraw_changed(rdman); if(r != OK) @@ -1292,13 +2084,15 @@ area.y = y; area.w = w; area.h = h; - add_dirty_area(rdman, &area); + add_dirty_area(rdman, rdman->root_coord, &area); r = rdman_redraw_changed(rdman); return r; } +/*! \brief Helping function to travel descendant shapes of a coord. + */ geo_t *rdman_geos(redraw_man_t *rdman, geo_t *last) { geo_t *next; coord_t *coord;