Animation?

Table of Contents:
What was all of that flicker?
Double Buffering
Implementing a double buffer
A example animation program


I saw a lot of flickering, what can we do about it?

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 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!


Double Buffering

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:

  • No flickering
  • Main memory is MUCH faster than Video memory
  • The user cannot see the frame being update, even at low FPS.

    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.


    Implementing a Double Buffer

    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?

    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:

    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!


    Example Program

    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!

    -----------------] Example Starts here [-----------------
    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.
    
    -----------------] Example Ends here [-----------------


  • Created by Chris Lattner