343 lines
7.8 KiB
C
343 lines
7.8 KiB
C
/* Copyright 1995 - 1997 by Abacus Research and
|
|
* Development, Inc. All rights reserved.
|
|
*/
|
|
|
|
#if !defined (OMIT_RCSID_STRINGS)
|
|
char ROMlib_rcsid_dirtyrect[] = "$Id: dirtyrect.c 63 2004-12-24 18:19:43Z ctm $";
|
|
#endif
|
|
|
|
#include "rsys/common.h"
|
|
#include "rsys/vdriver.h"
|
|
#include "rsys/dirtyrect.h"
|
|
#include "rsys/quick.h"
|
|
#include "rsys/cquick.h"
|
|
#include "rsys/prefs.h"
|
|
#include "rsys/flags.h"
|
|
#include "rsys/autorefresh.h"
|
|
#include "OSEvent.h"
|
|
|
|
#if !defined (MAX_DIRTY_RECTS)
|
|
# define MAX_DIRTY_RECTS 5
|
|
#endif
|
|
|
|
|
|
int num_dirty_rects = 0;
|
|
static vdriver_rect_t dirty_rect[MAX_DIRTY_RECTS];
|
|
|
|
|
|
/* We will glom a new rectangle into an existing one if it adds no more
|
|
* than this many pixels.
|
|
*/
|
|
#define ACCEPTABLE_PIXELS_ADDED 8000
|
|
|
|
|
|
static inline boolean_t
|
|
rects_overlap_p (int top, int left, int bottom, int right,
|
|
const vdriver_rect_t *r)
|
|
{
|
|
return (top < r->bottom
|
|
&& r->top < bottom
|
|
&& left < r->right
|
|
&& r->left < right);
|
|
}
|
|
|
|
|
|
/* This routine is only valid for non-overlapping rectangles! */
|
|
static inline unsigned long
|
|
area_added (int top, int left, int bottom, int right, int r1_area,
|
|
const vdriver_rect_t *r2)
|
|
{
|
|
int t, l, b, r, new_area, r2_area;
|
|
|
|
t = MIN (top, r2->top);
|
|
l = MIN (left, r2->left);
|
|
b = MAX (bottom, r2->bottom);
|
|
r = MAX (right, r2->right);
|
|
|
|
/* This works because we know the rects don't overlap. */
|
|
new_area = (r - l) * (b - t);
|
|
r2_area = (r2->right - r2->left) * (r2->bottom - r2->top);
|
|
|
|
return new_area - (r1_area + r2_area);
|
|
}
|
|
|
|
|
|
static inline void
|
|
union_rect (int top, int left, int bottom, int right, vdriver_rect_t *r)
|
|
{
|
|
if (top < r->top)
|
|
r->top = top;
|
|
if (left < r->left)
|
|
r->left = left;
|
|
if (bottom > r->bottom)
|
|
r->bottom = bottom;
|
|
if (right > r->right)
|
|
r->right = right;
|
|
}
|
|
|
|
|
|
/* This function adds the specified rectangle to the dirty rect list.
|
|
* If this rectangle is very close to another rectangle already in the
|
|
* rect list, they may be glommed together into one larger rectangle.
|
|
* Since we don't allow overlapping rects in the dirty rect list, if
|
|
* this rect overlaps any rects already in the list, they will be
|
|
* glommed together into a larger rectange. Since that larger
|
|
* rectangle might suddenly overlap other rectangles already in the
|
|
* list, that glommed rectangle must get reinserted.
|
|
*/
|
|
void
|
|
dirty_rect_accrue (int top, int left, int bottom, int right)
|
|
{
|
|
unsigned long best_area_added;
|
|
int ndr, i, best;
|
|
boolean_t done;
|
|
|
|
if (bottom <= top || right <= left
|
|
|| ROMlib_refresh
|
|
|| !ROMlib_shadow_screen_p)
|
|
return;
|
|
|
|
/* Note that Executor has touched the screen. */
|
|
note_executor_changed_screen (top, bottom);
|
|
|
|
ndr = num_dirty_rects;
|
|
|
|
/* Quickly handle the common case of no dirty rects. */
|
|
if (ndr == 0)
|
|
{
|
|
dirty_rect[0].top = top;
|
|
dirty_rect[0].left = left;
|
|
dirty_rect[0].bottom = bottom;
|
|
dirty_rect[0].right = right;
|
|
num_dirty_rects = 1;
|
|
return;
|
|
}
|
|
|
|
/* Otherwise, glom away! */
|
|
for (done = FALSE; !done; )
|
|
{
|
|
int new_area;
|
|
|
|
new_area = (right - left) * (bottom - top);
|
|
|
|
/* Figure out which union adds the least new area. */
|
|
best_area_added = ~0UL;
|
|
best = 0;
|
|
for (i = ndr - 1; i >= 0; i--)
|
|
{
|
|
unsigned long added;
|
|
|
|
/* We don't allow overlapping rectangles, so if this overlaps
|
|
* _any_ of them, then we glom them together.
|
|
*/
|
|
if (rects_overlap_p (top, left, bottom, right, &dirty_rect[i]))
|
|
{
|
|
best_area_added = 0;
|
|
best = i;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* We know that they don't overlap. */
|
|
added = area_added (top, left, bottom, right, new_area,
|
|
&dirty_rect[i]);
|
|
if (added < best_area_added)
|
|
{
|
|
best_area_added = added;
|
|
best = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add this rect to the dirty list, either by glomming together
|
|
* with an existing rect, or by adding a new rect.
|
|
*/
|
|
if (best_area_added <= ACCEPTABLE_PIXELS_ADDED
|
|
|| ndr == MAX_DIRTY_RECTS)
|
|
{
|
|
if (ndr == 1)
|
|
{
|
|
union_rect (top, left, bottom, right, &dirty_rect[0]);
|
|
done = TRUE;
|
|
}
|
|
else
|
|
{
|
|
boolean_t old_rect_grows_p;
|
|
vdriver_rect_t *d = &dirty_rect[best];
|
|
|
|
/* We now re-insert the glommed rectangle into the list, to
|
|
* see if it overlaps anyone else.
|
|
*/
|
|
|
|
if (d->top <= top)
|
|
{
|
|
top = d->top;
|
|
old_rect_grows_p = FALSE;
|
|
}
|
|
else
|
|
old_rect_grows_p = TRUE;
|
|
|
|
if (d->left <= left)
|
|
left = d->left;
|
|
else
|
|
old_rect_grows_p = TRUE;
|
|
|
|
if (d->bottom >= bottom)
|
|
bottom = d->bottom;
|
|
else
|
|
old_rect_grows_p = TRUE;
|
|
|
|
if (d->right >= right)
|
|
right = d->right;
|
|
else
|
|
old_rect_grows_p = TRUE;
|
|
|
|
/* If this new rect is not entirely subsumed by the rect
|
|
* it interesects, then we delete the subsumed rect,
|
|
* and insert the union of the two rectangles into
|
|
* our rect list. Otherwise, we're done, because the
|
|
* new rectangle doesn't add any new information.
|
|
*/
|
|
if (old_rect_grows_p)
|
|
*d = dirty_rect[--ndr];
|
|
else
|
|
done = TRUE;
|
|
}
|
|
}
|
|
else /* No glomming required. */
|
|
{
|
|
vdriver_rect_t *n = &dirty_rect[ndr];
|
|
|
|
/* Just add a new rectangle to the list. */
|
|
n->top = top;
|
|
n->left = left;
|
|
n->bottom = bottom;
|
|
n->right = right;
|
|
++ndr;
|
|
done = TRUE;
|
|
}
|
|
}
|
|
|
|
num_dirty_rects = ndr;
|
|
}
|
|
|
|
|
|
/* Returns TRUE iff the specified rect is already encompassed by
|
|
* the dirty rect list.
|
|
*/
|
|
boolean_t
|
|
dirty_rect_subsumed_p (int top, int left, int bottom, int right)
|
|
{
|
|
int i;
|
|
|
|
for (i = num_dirty_rects - 1; i >= 0; i--)
|
|
{
|
|
const vdriver_rect_t *r = &dirty_rect[i];
|
|
if (r->top <= top && r->left <= left
|
|
&& r->bottom >= bottom && r->right >= right)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* Note: the process of aligning these rectangles can make some of them
|
|
* overlap slightly in strange cases.
|
|
*/
|
|
static inline void
|
|
clip_and_align_dirty_rects (void)
|
|
{
|
|
vdriver_rect_t *r;
|
|
int i;
|
|
#if VDRIVER_DIRTY_RECT_BYTE_ALIGNMENT
|
|
int log2_bpp;
|
|
#endif
|
|
|
|
#if VDRIVER_DIRTY_RECT_BYTE_ALIGNMENT
|
|
log2_bpp = vdriver_log2_bpp;
|
|
#endif
|
|
|
|
/* Loop over all rects and canonicalize them. */
|
|
for (i = num_dirty_rects, r = &dirty_rect[0]; --i >= 0; r++)
|
|
{
|
|
int left, right;
|
|
|
|
if (r->top < 0)
|
|
r->top = 0;
|
|
if (r->bottom > vdriver_height)
|
|
r->bottom = vdriver_height;
|
|
|
|
/* Pin left and round it down. */
|
|
left = r->left;
|
|
if (left < 0)
|
|
left = 0;
|
|
#if VDRIVER_DIRTY_RECT_BYTE_ALIGNMENT
|
|
#define ALIGN_BITS (VDRIVER_DIRTY_RECT_BYTE_ALIGNMENT * 8UL - 1)
|
|
else
|
|
left &= ~(ALIGN_BITS >> log2_bpp);
|
|
#endif
|
|
r->left = left;
|
|
|
|
/* Pin right and round it up. */
|
|
right = r->right;
|
|
if (right > vdriver_width)
|
|
right = vdriver_width;
|
|
#if VDRIVER_DIRTY_RECT_BYTE_ALIGNMENT
|
|
right = ((((right << log2_bpp) + ALIGN_BITS) & ~ALIGN_BITS)
|
|
>> log2_bpp);
|
|
#endif
|
|
r->right = right;
|
|
}
|
|
}
|
|
|
|
|
|
static inline void
|
|
sort_dirty_rects_by_top (void)
|
|
{
|
|
#if defined (VDRIVER_SORT_DIRTY_RECTS_BY_TOP)
|
|
int i, j;
|
|
|
|
/* Bubble sort is fine for only a small number of rectangles. */
|
|
for (i = num_dirty_rects - 1; i > 0; i--)
|
|
{
|
|
int cur_top = dirty_rect[0].top;
|
|
|
|
for (j = 0; j < i; j++)
|
|
{
|
|
int next_top = dirty_rect[j + 1].top;
|
|
if (cur_top > next_top)
|
|
{
|
|
vdriver_rect_t tmp;
|
|
tmp = dirty_rect[j];
|
|
dirty_rect[j] = dirty_rect[j + 1];
|
|
dirty_rect[j + 1] = tmp;
|
|
}
|
|
else
|
|
cur_top = next_top;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void
|
|
dirty_rect_update_screen (void)
|
|
{
|
|
if (num_dirty_rects)
|
|
{
|
|
vdriver_rect_t dirty_rect_copy[MAX_DIRTY_RECTS];
|
|
int ndr;
|
|
|
|
clip_and_align_dirty_rects ();
|
|
sort_dirty_rects_by_top ();
|
|
ndr = num_dirty_rects;
|
|
num_dirty_rects = 0;
|
|
|
|
/* Copy rects to a local copy so we are reentrant. */
|
|
memcpy (&dirty_rect_copy[0], &dirty_rect[0], ndr * sizeof dirty_rect[0]);
|
|
|
|
vdriver_update_screen_rects (ndr, &dirty_rect_copy[0], FALSE);
|
|
}
|
|
}
|