diff --git a/caca/caca_internals.h b/caca/caca_internals.h index c5d3c96..1bc8b60 100644 --- a/caca/caca_internals.h +++ b/caca/caca_internals.h @@ -23,7 +23,7 @@ typedef struct caca_figfont caca_figfont_t; #if !defined(_DOXYGEN_SKIP_ME) # define EVENTBUF_LEN 10 -# define MAX_DIRTY_COUNT 1 +# define MAX_DIRTY_COUNT 8 #endif struct caca_frame @@ -63,9 +63,9 @@ struct caca_canvas int ndirty; struct { - int xmin, xmax, ymin, ymax; + int xmin, ymin, xmax, ymax; } - dirty[MAX_DIRTY_COUNT]; + dirty[MAX_DIRTY_COUNT + 1]; /* Shortcut to the active frame information */ int width, height; diff --git a/caca/dirty.c b/caca/dirty.c index cffe1f5..9e04c07 100644 --- a/caca/dirty.c +++ b/caca/dirty.c @@ -28,11 +28,14 @@ #if !defined(__KERNEL__) # include +# include #endif #include "caca.h" #include "caca_internals.h" +static void merge_new_rect(caca_canvas_t *cv, int n); + /** \brief Get the number of dirty rectangles in the canvas. * * Get the number of dirty rectangles in a canvas. Dirty rectangles are @@ -90,7 +93,7 @@ int caca_get_dirty_rect(caca_canvas_t *cv, int r, *width = cv->dirty[r].xmax - cv->dirty[r].xmin + 1; *height = cv->dirty[r].ymax - cv->dirty[r].ymin + 1; - debug("dirty #%i: %ix%i at (%i,%i)\n", r, *width, *height, *x, *y); + debug("dirty #%i: %ix%i at (%i,%i)", r, *width, *height, *x, *y); return 0; } @@ -117,7 +120,7 @@ int caca_get_dirty_rect(caca_canvas_t *cv, int r, */ int caca_add_dirty_rect(caca_canvas_t *cv, int x, int y, int width, int height) { - debug("new dirty: %ix%i at (%i,%i)\n", width, height, x, y); + debug("new dirty: %ix%i at (%i,%i)", width, height, x, y); /* Clip arguments to canvas */ if(x < 0) { width += x; x = 0; } @@ -137,29 +140,17 @@ int caca_add_dirty_rect(caca_canvas_t *cv, int x, int y, int width, int height) return -1; } - /* Add dirty rectangle to list. Current strategy: if there is room - * for rectangles, just append it to the list. Otherwise, merge the - * new rectangle with the first in the list. */ - if(cv->ndirty < MAX_DIRTY_COUNT) - { - cv->dirty[cv->ndirty].xmin = x; - cv->dirty[cv->ndirty].xmax = x + width - 1; - cv->dirty[cv->ndirty].ymin = y; - cv->dirty[cv->ndirty].ymax = y + height - 1; - cv->ndirty++; - } - else - { - /* FIXME We may get overlapping rectangles like this */ - if(x < cv->dirty[0].xmin) - cv->dirty[0].xmin = x; - if(x + width - 1 > cv->dirty[0].xmax) - cv->dirty[0].xmax = x + width - 1; - if(y < cv->dirty[0].ymin) - cv->dirty[0].ymin = y; - if(y + height - 1 > cv->dirty[0].ymax) - cv->dirty[0].ymax = y + height - 1; - } + /* Add the new rectangle to the list; it works even if cv->ndirty + * is MAX_DIRTY_COUNT because there's an extra cell in the array. */ + cv->dirty[cv->ndirty].xmin = x; + cv->dirty[cv->ndirty].ymin = y; + cv->dirty[cv->ndirty].xmax = x + width - 1; + cv->dirty[cv->ndirty].ymax = y + height - 1; + cv->ndirty++; + + /* Try to merge the new rectangle with existing ones. This also ensures + * that cv->ndirty is brought back below MAX_DIRTY_COUNT. */ + merge_new_rect(cv, cv->ndirty - 1); return 0; } @@ -230,6 +221,100 @@ int caca_clear_dirty_rect_list(caca_canvas_t *cv) * XXX: the following functions are local. */ +static inline int int_min(int a, int b) { return a < b ? a : b; } +static inline int int_max(int a, int b) { return a > b ? a : b; } + +/* Merge a newly added rectangle, if necessary. */ +static void merge_new_rect(caca_canvas_t *cv, int n) +{ + int wasted[MAX_DIRTY_COUNT + 1]; + int i, sn, best, best_score; + + best = -1; + best_score = cv->width * cv->height; + + sn = (cv->dirty[n].xmax - cv->dirty[n].xmin + 1) + * (cv->dirty[n].ymax - cv->dirty[n].ymin + 1); + + /* Check whether the new rectangle can be merged with an existing one. */ + for(i = 0; i < cv->ndirty; i++) + { + int si, sf, xmin, ymin, xmax, ymax; + + if(i == n) + continue; + + xmin = int_min(cv->dirty[i].xmin, cv->dirty[n].xmin); + ymin = int_min(cv->dirty[i].ymin, cv->dirty[n].ymin); + xmax = int_max(cv->dirty[i].xmax, cv->dirty[n].xmax); + ymax = int_max(cv->dirty[i].ymax, cv->dirty[n].ymax); + + sf = (xmax - xmin + 1) * (ymax - ymin + 1); + + /* Shortcut: if the current rectangle is inside the new rectangle, + * we remove the current rectangle and continue trying merges. */ + if(sf == sn) + { + memmove(&cv->dirty[i], &cv->dirty[i + 1], + (cv->ndirty - i) * sizeof(cv->dirty[0])); + cv->ndirty--; + + if(i < n) + n--; + else + i--; + + continue; + } + + si = (cv->dirty[i].xmax - cv->dirty[i].xmin + 1) + * (cv->dirty[i].ymax - cv->dirty[i].ymin + 1); + + /* Shortcut: if the new rectangle is inside the current rectangle, + * we get rid of the new rectangle and bail out. */ + if(sf == si) + { + cv->ndirty--; + memmove(&cv->dirty[n], &cv->dirty[n + 1], + (cv->ndirty - n) * sizeof(cv->dirty[0])); + return; + } + + /* We store approximately how many bytes were wasted. FIXME: this is + * not an exact computation, we need to be more precise. */ + wasted[i] = sf - si - sn; + + if(wasted[i] < best_score) + { + best = i; + best_score = wasted[i]; + } + } + + /* FIXME: we only try to merge the current rectangle, ignoring + * potentially better merges. */ + + /* If no acceptable score was found and the dirty rectangle list is + * not full, we bail out. */ + if(best_score > 0 && cv->ndirty < MAX_DIRTY_COUNT) + return; + + /* Otherwise, merge the rectangle with the best candidate */ + cv->dirty[best].xmin = int_min(cv->dirty[best].xmin, cv->dirty[n].xmin); + cv->dirty[best].ymin = int_min(cv->dirty[best].ymin, cv->dirty[n].ymin); + cv->dirty[best].xmax = int_max(cv->dirty[best].xmax, cv->dirty[n].xmax); + cv->dirty[best].ymax = int_max(cv->dirty[best].ymax, cv->dirty[n].ymax); + + memmove(&cv->dirty[n], &cv->dirty[n + 1], + (cv->ndirty - n) * sizeof(cv->dirty[0])); + cv->ndirty--; + + if(best < n) + merge_new_rect(cv, best); + else + merge_new_rect(cv, best - 1); +} + /* Clip all dirty rectangles in case they're larger than the canvas */ void _caca_clip_dirty_rect_list(caca_canvas_t *cv) {