Double Buffering Animation using Mode 13h (320x200x256)

The following is a brief tutorial and example program that explains the use of VGA Graphics Mode 13 hex and the "double buffering" technique of animation.

Explanation of Double Buffering Animation Technique

To create high speed, flicker free sprite animations, you must update the screen faster than the human eye's "sampling rate" -- the amount of time that it takes for the eyes to realize something has happened. This makes it appear that the screen is actually being animated and not simply a series of pictures being drawn one frame after another. One easy and efficient way to acheive this is to use a technique called "double buffering." This method is used in many popular comerical games such as Wolfenstein and Doom. The way it works is by performing all the drawing operations of each frame of an animation to offscreen buffer of the same size as the video memory (of course, smaller sized buffers can be used to only animate only a portion of the screen). When the drawing to the buffer is complete, the contents of the video memory are replaced with the contents of this buffer. Repeating this process for a series of frames acheives animation. In order to get higher speed animation, this last step of copying the offscreen buffer into the video memory should be done in assembly. Other shortcuts can also be used to increase performance considerably.

       off-screen buffer                VGA video buffer
 ----------------------------      ----------------------------
 |                          |      |                          |     
 |                          |      |                          |
 |    320x200               |      |    320x200               |    
 |    64,000 Bytes          | -->  |    64,000 Bytes          |
 |                          | -->  |                          |
 |                          |      |                          |
 |                          |      |                          | 
 |                          |      |                          |
 ----------------------------      ----------------------------
  all drawing is performed
  on this "off-screen"
  buffer, then the
  entire screen is moved at
  once into the video 
  buffer.

pseudo-code for double buffering animation


main

  initialize graphics

  allocate memory for offscreen buffer
    For full screen animation in mode 13h, the buffer should be
    64K (320 x 200 = 64K)


  do {

    clear contents of double buffer

    calculate current frame of animation

    draw current frame to double buffer

    copy double buffer to video buffer

  }


Brief Tutorial On Video Mode 13 Hex (320x200x256)

Why Use Mode 13h?

All graphics modes (CGA, EGA, VGA, SVGA, XGA, etc.) use a portion of memory that represents the bitmap displayed on the screen. This is called the video buffer. For mode 13h, the buffer starts at A000:0000 and ends at A000:F9FF, thus creating a buffer 64K large. Each pixel is represented by one byte (320 x 200 = 64,000), which makes it very easy to manipulate the screen in this mode. The facility, combined with having 256 colors to work with, makes up for the relatively low resolution. Most people won't even realize it -- did you realize that Wolfenstein, Heretic, and X-Wing were all written in mode 13h?

Initializing Mode 13h

To change the graphics mode, you simply set register AH to 0 (video function 0: set video mode), set the value of AL with the new video mode (0x13 in our case), and then call interrupt 10. I believe that it is easiest to do this in assembly, which can easily be used from within C.

assembly code to set video mode to 13h:


  mov ah, 0                 ; Video function 0 = set video mode
  mov al, BYTE PTR 0x13     ; Set al with proper video mode
  int 10h                   ; Interrupt 10: video interrupt

But before initializing graphics, you should know how to return to text mode.

assembly code to set video mode to 3h (text mode):


  mov ah, 0                 ; Video function 0 = set video mode
  mov al, BYTE PTR 0x03     ; Set al with proper video mode
  int 10h                   ; Interrupt 10: video interrupt

The Color Look-Up Table

Mode 13h is capable of displaying 256 colors at a time. However, it can display a total of 262,144 different colors. The way this works is that there is a color look-up table that store the red, green and blue value for each color, 0-255. Each color value (red, green, or blue) can be 0-63, creating a total of 2^18 (262,144) different colors. Each of these 262,144 colors can be descibed with a different value for the red, green, and blue components for its value on the look-up table. For instance, if I set color 22 with r=63, g=63, b=63, it will be white, the presence of all colors. If I set it to r=0,g=0,b=0, it will be black, the absence of color. It is important to userstand these basics of the way 256 color modes work, but it is beyond the scope of this tutorial to discuss the technicalities of manipulating this look-up table. The default table is fine to use as a start. See tutorial number two, "VGA Mode13h Color Look-Up Table Manipulation" for more information on the Look-Up table.

Plotting Pixels

To change the color of a pixel (x, y), all that has to be done is to change the value of the byte at A000:0000 + [y*320+x] (A000 is the segment, [y*320+x] is the offset).

plotting a pixel:


  unsigned char far *video_buffer = (char far *)0xA0000000L;

  void Plot_Pixel(int x, int y, int color) {
    video_buffer[y*320 + x] = color;
  }  

One way to improve this function is to remove the multiplication, thereby speeding up the function tremendously. The way to do this is with bit shifting. If you take a number and shift it left or right, it's like multiplying or dividing by two. The only catch is that 320 is not a multiple of two. However 320 = 256 + 64, both of which are multiples of two. If you mutiply y by 256 and the add it to y multiplied by 64, it's the same as y*320. Therefore, you can add y shifted 8 bits (256) to y shifted 6 bits (64) and x. We'll be shifting to the left, because we're multiplying. This simple process speed the execution of this function up to ten times.

improved Plot_Pixel:


  unsigned char far *video_buffer = (char far *)0xA0000000L;

  void Plot_Pixel(int x, int y, int color) {
    video_buffer[((y<<8) + (y<<6)) + x] = color;
  }


This tutorial, and the example program included, are enough to get you started with animation and VGA graphics. However, you will still probably want to check out some other sources. Here are a few I recommend.

Other References


Example Program

A modified version for Watcom C++ Protected Mode will be available soon. This version has been tested with Borland C++ 3.1 and Turbo C++ 3.0.

// I N C L U D E S ///////////////////////////////////////////////////////////

#include <conio.h>
#include <stdio.h>
#include <process.h>
#include <alloc.h>
#include <dos.h>
#include <mem.h>

// D E F I N E S  ////////////////////////////////////////////////////////////

#define MODE13H             0x13
#define TEXT_MODE           0x03

// P R O T O T Y P E S ///////////////////////////////////////////////////////

void Video_Mode(int vmode);
int  Create_Offscreen_Buffer(int width, int height);
void Show_Offscreen_Buffer(void);
void Delete_Offscreen_Buffer(void);
void Fill_OBuffer(int color);
void Plot_PixelOB(int x, int y, unsigned char color);
void Draw_Sprite(int x, int y);

// G L O B A L S  //////////////////////////////////////////////////////////

unsigned char far *video_buffer     = (char far *)0xA0000000L;
unsigned char far *offscreen_buffer = NULL;
int sprite_x, sprite_y;

void main(void) {
  sprite_x = 0;
  sprite_y = 10;

  Video_Mode(MODE13H);

  if(!Create_Offscreen_Buffer(320, 200)) {
    Video_Mode(TEXT_MODE);
    printf("Not enough memory to create offscreen buffer.\n");
    printf("Exiting to DOS...\n");
    exit(1);
  }

  while(!kbhit()) {
    Fill_OBuffer(0);  // clear contents of double buffer
    sprite_x++;
    if (sprite_x >= 309) sprite_x = 0;
    Draw_Sprite(sprite_x, sprite_y);
    Show_Offscreen_Buffer();
  }

  Delete_Offscreen_Buffer();
  Video_Mode(TEXT_MODE);
}

/////////////////////////////////////////////////////////////////////////////
// V I D E O _ M O D E                                                     //
// Sets the video mode to the value in vmode.                              //
/////////////////////////////////////////////////////////////////////////////

void Video_Mode(int vmode) {
  asm mov ah, 0
  asm mov al, BYTE PTR vmode
  asm int 10h
}

/////////////////////////////////////////////////////////////////////////////
// C R E A T E _ O F F S C R E E N _ B U F F E R                           //
// Creates an offscreen buffer of the specified size.                      //
/////////////////////////////////////////////////////////////////////////////

int Create_Offscreen_Buffer(int width, int height)  {
  // allocate enough memory to hold the double buffer
  if ((offscreen_buffer = (unsigned char far *)farmalloc((unsigned int)width * (height + 1)))==NULL)
     return 0;
  // fill the buffer with black
  _fmemset(offscreen_buffer, 0, width * height);
  return 1;
}

/////////////////////////////////////////////////////////////////////////////
// S H O W _ O F F S C R E E N _ B U F F E R                               //
// Copies the contents of the offscreen buffer to the video buffer.        //
/////////////////////////////////////////////////////////////////////////////

void Show_Offscreen_Buffer(void)  {
  char far *buffer = offscreen_buffer;
  int buffer_size = 320*200/2;
  // this functions copies the double buffer into the video buffer
  asm   push ds               ; //save DS on stack
  asm   mov cx,buffer_size    ; //this is the size of buffer in WORDS
  asm   les di,video_buffer   ; //es:di is destination of memory move
  asm   lds si,buffer         ; //ds:si is source of memory move
  asm   cld                   ; //make sure to move in the right direction
  asm   rep movsw             ; //move all the words
  asm   pop ds                ; //restore the data segment
}

/////////////////////////////////////////////////////////////////////////////
// D E L E T E _ O F F S C R E E N _ B U F F E R                           //
// Deletes the offscreen buffer.                                           //
/////////////////////////////////////////////////////////////////////////////

void Delete_Offscreen_Buffer(void)  {
  // this function free's up the memory allocated by the double buffer
  if (offscreen_buffer) farfree(offscreen_buffer);
}

/////////////////////////////////////////////////////////////////////////////
// F I L L _ O B U F F E R                                                 //
// Fills a 64K offscreen image buffer with the specified color.            //
/////////////////////////////////////////////////////////////////////////////

void Fill_OBuffer(int color) {
  _fmemset(offscreen_buffer, color, 64000);
}

/////////////////////////////////////////////////////////////////////////////
// P L O T _ P I X E L O B                                                 //
// Plots a pixel to the offscreen buffer.                                  //
/////////////////////////////////////////////////////////////////////////////

void Plot_PixelOB(int x, int y, unsigned char color) {
  offscreen_buffer[((y<<8) + (y<<6)) + x] = color;
}

/////////////////////////////////////////////////////////////////////////////
// D R A W _ S P R I T E                                                   //
// Draws a 10x10 box as an example sprite                                  //
/////////////////////////////////////////////////////////////////////////////

void Draw_Sprite(int x, int y) {
  int i, j;
  for (i=0; i<=10; i++) {
    for (j=0; j<=10; j++) {
      Plot_PixelOB(i+x, j+y, 11);
    }
  }
}


Copyright © 1999, Sam Headrick.