The problem with the previous examples is the when the monitor is drawing the screen, the background
is temporarily visible behind the sprites. This gives the effect of flicker between the sprite and
the background. What can we do to get rid of this? We just have to make sure that the image buffer
never holds anything that we don't want it to... Let me introduce you to double buffering!
Of course, the main disadvantage of it is that it costs an extra 64k of memory that might be needed
elsewhere. We'll make this an optional part of GraphPro, so that applications that don't need it,
or don't want to deal with the small amount of added complexity don't have to use it.
Below is a listing for "FlipPage" that flips the current page from the hidden buffer to the visible
buffer. It uses "rep movsd" to move data four bytes at a time:
In all of the examples, even with the fastest sprite routines we could manage, there was a flickering
present when we did animation. Although a little flicker is acceptable for small example programs,
real games require animation beyond that.
The trick behind double buffering is that we keep a copy of the screen in main memory. When we are
done updating this buffer, we copy the whole thing to the video buffer and segment A000h. This has
a number of advantages:
The first thing that we need is to make a procedure that allocates the memory for the second buffer.
How nice it is that we have been using "Screen.Buffer" to access video memory... All we have to do
now is change that pointer! The procedure would look something like this:
PROCEDURE InitDoubleBuffer;
BEGIN
IF NOT Screen.DBuffer THEN { Make sure that we aren't already in DB mode! }
BEGIN
GETMEM(Screen.Buffer, 64000); { That's all there is to it! }
Screen.DBuffer := TRUE;
END;
END;
PROCEDURE CloseDoubleBuffer;
BEGIN
IF Screen.DBuffer THEN
BEGIN
FREEMEM(Screen.Buffer, 64000);
Screen.Buffer := PTR($A000, 0);
Screen.DBuffer := FALSE;
END;
END;
Okay, we added a variable to Screen ("Screen.DBuffer") and we added two procedures. We can put
CloseDoubleBuffer in the CloseGraph procedure, so we don't need to call it all of the time... But
how do we copy the screen?
PROCEDURE FlipPage; ASSEMBLER;
ASM
cmp Screen.DBuffer, 0
je @Done { Can't flip if no second buffer! }
push DS
mov CX, 64000 / 4
mov AX, 0A000h
mov ES, AX
xor DI, DI
lds SI, Screen.Buffer
cld
db $66; rep movsw
pop DS
@Done:
END;
A sorely needed little routine is one to clear the screen for us. Below is a routine that clears
it to any color:
PROCEDURE ClrScr(Color : BYTE); ASSEMBLER;
ASM
les DI, Screen.Buffer
mov CX, 64000 / 4
mov AL, Color
mov AH, AL
mov BX, AX
db $66; shl AX, 16
mov AX, BX
db $66; rep stosw
END;
All we need now is a nifty example and we're set!
The example for today displays a bunch of flying dots... Try uncommenting the DoubleBuffer Define at
the top of the program to see why we need these routines!
USES CRT, GraphPro;
{$DEFINE DoubleBuffer }
TYPE
PtType = RECORD
X, Y,
DX, DY : INTEGER;
Color : BYTE;
END;
CONST
NumPoints = 2000;
VAR
I : INTEGER;
Points : ARRAY[0..NumPoints] OF PtType;
BEGIN
WRITELN('Initilizing!');
FOR I := 0 TO NumPoints DO
WITH Points[I] DO
BEGIN
X := Random(318)+1;
Y := Random(198)+1;
DX := Random(2)*2-1; { Either 1 or -1 }
DY := Random(2)*2-1; { Either 1 or -1 }
Color := Random(15)+1;
END;
InitGraph;
{$IFDEF DoubleBuffer} InitDoubleBuffer; {$ENDIF}
WHILE NOT KeyPressed DO
BEGIN
ClrScr(0);
FOR I := 0 TO NumPoints DO
WITH Points[I] DO
BEGIN
X := X + DX;
Y := Y + DY;
IF (X > 318) OR (X < 1) THEN DX := -DX;
IF (Y > 198) OR (Y < 1) THEN DY := -DY;
SetPixel(X, Y, Color);
END;
FlipPage;
END;
CloseGraph;
END.