293 lines
11 KiB
Markdown
293 lines
11 KiB
Markdown
Also, page flipping wastes a good deal of time waiting for the page to
|
|
flip at the end of the frame. Dirty-rectangle animation never needs to
|
|
wait for anything because partially drawn images are never present in
|
|
display memory. Actually, in one sense, partially drawn images are
|
|
sometimes present because it's possible for a rectangle to be partially
|
|
drawn when the scanning raster beam reaches that part of the screen.
|
|
This causes the rectangle to appear partially drawn for one frame,
|
|
producing a phenomenon I call "shearing." Fortunately, shearing tends
|
|
not to be particularly distracting, especially for fairly small images,
|
|
but it can be a problem when copying large areas. This is one area in
|
|
which dirty-rectangle animation falls short of page flipping, because
|
|
page flipping has perfect display quality, never showing anything other
|
|
than a completely finished frame. Similarly, dirty-rectangle copying may
|
|
take two or more frame times to finish, so even if shearing doesn't
|
|
happen, it's still possible to have the images in the various dirty
|
|
rectangles show up non-simultaneously. In my experience, this latter
|
|
phenomenon is not a serious problem, but do be aware of it.
|
|
|
|
### Dirty Rectangles in Action {#Heading7}
|
|
|
|
Listing 45.1 demonstrates dirty-rectangle animation. This is a very
|
|
simple implementation, in several respects. For one thing, it's written
|
|
entirely in C, and animation fairly cries out for assembly language. For
|
|
another thing, it uses far pointers, which C often handles with less
|
|
than optimal efficiency, especially because I haven't used library
|
|
functions to copy and fill memory. (I did this so the code would work in
|
|
any memory model.) Also, Listing 45.1 doesn't attempt to coalesce
|
|
rectangles so as to perform a minimum number of display-memory accesses;
|
|
instead, it copies each dirty rectangle to the screen, even if it
|
|
overlaps with another rectangle, so some pixels are copied multiple
|
|
times. Listing 45.1 runs pretty well, considering all of its failings;
|
|
on my 486/33, 10 11x11 images animate at a very respectable clip.
|
|
|
|
**LISTING 45.1 L45-1.C**
|
|
|
|
/* Sample simple dirty-rectangle animation program. Doesn't attempt to coalesce
|
|
rectangles to minimize display memory accesses. Not even vaguely optimized!
|
|
Tested with Borland C++ in the small model. */
|
|
|
|
#include <stdlib.h>
|
|
#include <conio.h>
|
|
#include <alloc.h>
|
|
#include <memory.h>
|
|
#include <dos.h>
|
|
|
|
#define SCREEN_WIDTH 320
|
|
#define SCREEN_HEIGHT 200
|
|
#define SCREEN_SEGMENT 0xA000
|
|
|
|
/* Describes a rectangle */
|
|
typedef struct {
|
|
int Top;
|
|
int Left;
|
|
int Right;
|
|
int Bottom;
|
|
} Rectangle;
|
|
|
|
/* Describes an animated object */
|
|
typedef struct {
|
|
int X; /* upper left corner in virtual bitmap */
|
|
int Y;
|
|
int XDirection; /* direction and distance of movement */
|
|
int YDirection;
|
|
} Entity;
|
|
|
|
/* Storage used for dirty rectangles */
|
|
#define MAX_DIRTY_RECTANGLES 100
|
|
int NumDirtyRectangles;
|
|
Rectangle DirtyRectangles[MAX_DIRTY_RECTANGLES];
|
|
|
|
/* If set to 1, ignore dirty rectangle list and copy the whole screen. */
|
|
int DrawWholeScreen = 0;
|
|
|
|
/* Pixels for image we'll animate */
|
|
#define IMAGE_WIDTH 11
|
|
#define IMAGE_HEIGHT 11
|
|
char ImagePixels[] = {
|
|
15,15,15, 9, 9, 9, 9, 9,15,15,15,
|
|
15,15, 9, 9, 9, 9, 9, 9, 9,15,15,
|
|
15, 9, 9,14,14,14,14,14, 9, 9,15,
|
|
9, 9,14,14,14,14,14,14,14, 9, 9,
|
|
9, 9,14,14,14,14,14,14,14, 9, 9,
|
|
9, 9,14,14,14,14,14,14,14, 9, 9,
|
|
9, 9,14,14,14,14,14,14,14, 9, 9,
|
|
9, 9,14,14,14,14,14,14,14, 9, 9,
|
|
15, 9, 9,14,14,14,14,14, 9, 9,15,
|
|
15,15, 9, 9, 9, 9, 9, 9, 9,15,15,
|
|
15,15,15, 9, 9, 9, 9, 9,15,15,15,
|
|
};
|
|
/* animated entities */
|
|
#define NUM_ENTITIES 10
|
|
Entity Entities[NUM_ENTITIES];
|
|
|
|
/* pointer to system buffer into which we'll draw */
|
|
char far *SystemBufferPtr;
|
|
|
|
/* pointer to screen */
|
|
char far *ScreenPtr;
|
|
|
|
void EraseEntities(void);
|
|
void CopyDirtyRectanglesToScreen(void);
|
|
void DrawEntities(void);
|
|
|
|
void main()
|
|
{
|
|
int i, XTemp, YTemp;
|
|
unsigned int TempCount;
|
|
char far *TempPtr;
|
|
union REGS regs;
|
|
/* Allocate memory for the system buffer into which we'll draw */
|
|
if (!(SystemBufferPtr = farmalloc((unsigned int)SCREEN_WIDTH*
|
|
SCREEN_HEIGHT))) {
|
|
printf("Couldn't get memory\n");
|
|
exit(1);
|
|
}
|
|
/* Clear the system buffer */
|
|
TempPtr = SystemBufferPtr;
|
|
for (TempCount = ((unsigned)SCREEN_WIDTH*SCREEN_HEIGHT); TempCount--; ) {
|
|
*TempPtr++ = 0;
|
|
}
|
|
/* Point to the screen */
|
|
ScreenPtr = MK_FP(SCREEN_SEGMENT, 0);
|
|
|
|
/* Set up the entities we'll animate, at random locations */
|
|
randomize();
|
|
for (i = 0; i < NUM_ENTITIES; i++) {
|
|
Entities[i].X = random(SCREEN_WIDTH - IMAGE_WIDTH);
|
|
Entities[i].Y = random(SCREEN_HEIGHT - IMAGE_HEIGHT);
|
|
Entities[i].XDirection = 1;
|
|
Entities[i].YDirection = -1;
|
|
}
|
|
/* Set 320x200 256-color graphics mode */
|
|
regs.x.ax = 0x0013;
|
|
int86(0x10, ®s, ®s);
|
|
|
|
/* Loop and draw until a key is pressed */
|
|
do {
|
|
/* Draw the entities to the system buffer at their current locations,
|
|
updating the dirty rectangle list */
|
|
DrawEntities();
|
|
|
|
/* Draw the dirty rectangles, or the whole system buffer if
|
|
appropriate */
|
|
CopyDirtyRectanglesToScreen();
|
|
|
|
/* Reset the dirty rectangle list to empty */
|
|
NumDirtyRectangles = 0;
|
|
|
|
/* Erase the entities in the system buffer at their old locations,
|
|
updating the dirty rectangle list */
|
|
EraseEntities();
|
|
|
|
/* Move the entities, bouncing off the edges of the screen */
|
|
for (i = 0; i < NUM_ENTITIES; i++) {
|
|
XTemp = Entities[i].X + Entities[i].XDirection;
|
|
YTemp = Entities[i].Y + Entities[i].YDirection;
|
|
if ((XTemp < 0) || ((XTemp + IMAGE_WIDTH) > SCREEN_WIDTH)) {
|
|
Entities[i].XDirection = -Entities[i].XDirection;
|
|
XTemp = Entities[i].X + Entities[i].XDirection;
|
|
}
|
|
if ((YTemp < 0) || ((YTemp + IMAGE_HEIGHT) > SCREEN_HEIGHT)) {
|
|
Entities[i].YDirection = -Entities[i].YDirection;
|
|
YTemp = Entities[i].Y + Entities[i].YDirection;
|
|
}
|
|
Entities[i].X = XTemp;
|
|
Entities[i].Y = YTemp;
|
|
}
|
|
|
|
} while (!kbhit());
|
|
getch(); /* clear the keypress */
|
|
/* Back to text mode */
|
|
regs.x.ax = 0x0003;
|
|
int86(0x10, ®s, ®s);
|
|
}
|
|
/* Draw entities at current locations, updating dirty rectangle list. */
|
|
void DrawEntities()
|
|
{
|
|
int i, j, k;
|
|
char far *RowPtrBuffer;
|
|
char far *TempPtrBuffer;
|
|
char far *TempPtrImage;
|
|
for (i = 0; i < NUM_ENTITIES; i++) {
|
|
/* Remember the dirty rectangle info for this entity */
|
|
if (NumDirtyRectangles >= MAX_DIRTY_RECTANGLES) {
|
|
/* Too many dirty rectangles; just redraw the whole screen */
|
|
DrawWholeScreen = 1;
|
|
} else {
|
|
/* Remember this dirty rectangle */
|
|
DirtyRectangles[NumDirtyRectangles].Left = Entities[i].X;
|
|
DirtyRectangles[NumDirtyRectangles].Top = Entities[i].Y;
|
|
DirtyRectangles[NumDirtyRectangles].Right =
|
|
Entities[i].X + IMAGE_WIDTH;
|
|
DirtyRectangles[NumDirtyRectangles++].Bottom =
|
|
Entities[i].Y + IMAGE_HEIGHT;
|
|
}
|
|
/* Point to the destination in the system buffer */
|
|
RowPtrBuffer = SystemBufferPtr + (Entities[i].Y * SCREEN_WIDTH) +
|
|
Entities[i].X;
|
|
/* Point to the image to draw */
|
|
TempPtrImage = ImagePixels;
|
|
/* Copy the image to the system buffer */
|
|
for (j = 0; j < IMAGE_HEIGHT; j++) {
|
|
/* Copy a row */
|
|
for (k = 0, TempPtrBuffer = RowPtrBuffer; k < IMAGE_WIDTH; k++) {
|
|
*TempPtrBuffer++ = *TempPtrImage++;
|
|
}
|
|
/* Point to the next system buffer row */
|
|
RowPtrBuffer += SCREEN_WIDTH;
|
|
}
|
|
}
|
|
}
|
|
/* Copy the dirty rectangles, or the whole system buffer if appropriate,
|
|
to the screen. */
|
|
void CopyDirtyRectanglesToScreen()
|
|
{
|
|
int i, j, k, RectWidth, RectHeight;
|
|
unsigned int TempCount;
|
|
unsigned int Offset;
|
|
char far *TempPtrScreen;
|
|
char far *TempPtrBuffer;
|
|
|
|
if (DrawWholeScreen) {
|
|
/* Just copy the whole buffer to the screen */
|
|
DrawWholeScreen = 0;
|
|
TempPtrScreen = ScreenPtr;
|
|
TempPtrBuffer = SystemBufferPtr;
|
|
for (TempCount = ((unsigned)SCREEN_WIDTH*SCREEN_HEIGHT); TempCount--; ) {
|
|
*TempPtrScreen++ = *TempPtrBuffer++;
|
|
}
|
|
} else {
|
|
/* Copy only the dirty rectangles */
|
|
for (i = 0; i < NumDirtyRectangles; i++) {
|
|
/* Offset in both system buffer and screen of image */
|
|
Offset = (unsigned int) (DirtyRectangles[i].Top * SCREEN_WIDTH) +
|
|
DirtyRectangles[i].Left;
|
|
/* Dimensions of dirty rectangle */
|
|
RectWidth = DirtyRectangles[i].Right - DirtyRectangles[i].Left;
|
|
RectHeight = DirtyRectangles[i].Bottom - DirtyRectangles[i].Top;
|
|
/* Copy a dirty rectangle */
|
|
for (j = 0; j < RectHeight; j++) {
|
|
|
|
/* Point to the start of row on screen */
|
|
TempPtrScreen = ScreenPtr + Offset;
|
|
|
|
/* Point to the start of row in system buffer */
|
|
TempPtrBuffer = SystemBufferPtr + Offset;
|
|
|
|
/* Copy a row */
|
|
for (k = 0; k < RectWidth; k++) {
|
|
*TempPtrScreen++ = *TempPtrBuffer++;
|
|
}
|
|
/* Point to the next row */
|
|
Offset += SCREEN_WIDTH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Erase the entities in the system buffer at their current locations,
|
|
updating the dirty rectangle list. */
|
|
void EraseEntities()
|
|
{
|
|
int i, j, k;
|
|
char far *RowPtr;
|
|
char far *TempPtr;
|
|
|
|
for (i = 0; i < NUM_ENTITIES; i++) {
|
|
/* Remember the dirty rectangle info for this entity */
|
|
if (NumDirtyRectangles >= MAX_DIRTY_RECTANGLES) {
|
|
/* Too many dirty rectangles; just redraw the whole screen */
|
|
DrawWholeScreen = 1;
|
|
} else {
|
|
/* Remember this dirty rectangle */
|
|
DirtyRectangles[NumDirtyRectangles].Left = Entities[i].X;
|
|
DirtyRectangles[NumDirtyRectangles].Top = Entities[i].Y;
|
|
DirtyRectangles[NumDirtyRectangles].Right =
|
|
Entities[i].X + IMAGE_WIDTH;
|
|
DirtyRectangles[NumDirtyRectangles++].Bottom =
|
|
Entities[i].Y + IMAGE_HEIGHT;
|
|
}
|
|
/* Point to the destination in the system buffer */
|
|
RowPtr = SystemBufferPtr + (Entities[i].Y*SCREEN_WIDTH) + Entities[i].X;
|
|
|
|
/* Clear the entity's rectangle */
|
|
for (j = 0; j < IMAGE_HEIGHT; j++) {
|
|
/* Clear a row */
|
|
for (k = 0, TempPtr = RowPtr; k < IMAGE_WIDTH; k++) {
|
|
*TempPtr++ = 0;
|
|
}
|
|
/* Point to the next row */
|
|
RowPtr += SCREEN_WIDTH;
|
|
}
|
|
}
|
|
}
|