305 lines
12 KiB
Markdown
305 lines
12 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[Previous](45-02.html) [Table of Contents](index.html) [Next](45-04.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
------------------------ --------------------------------- --------------------
|
||
[Previous](45-02.html) [Table of Contents](index.html) [Next](45-04.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
* * * * *
|
||
|
||
Graphics Programming Black Book © 2001 Michael Abrash
|