|
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.
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
}
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?
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
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.
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;
}
// 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);
}
}
}