VGA Programming
Overview
This section will explain how the VGA card works, concentrating on mode 13h and
mode X. It will be fairly basic, nothing very complicated. It also covers basic graphics programming, such as line drawing algorithms. Jump directly to a section by using the links below.
Mode 13h
This is the simplest graphics mode there is. You get a 320 X 200 resolution, and a choice of 256 colours, which can be chosen from a palette of about 255,000.
To get into this mode, the assembly instructions are to load the AX register with the value 0x13 and call interrupt 0x10. Here's the C code to do this:
void setMode13h()
{
__dpmi_regs r;
r.x.ax = 0x13;
__dpmi_int(0x10, &r);
}
This is the code for the DJGPP compiler. (check the What you need page to find out about DJGPP). The code for the Borland C compiler etc. should be something like the following:
void setMode13h()
{
union REGS regs;
regs.x.ax = 0x13;
int86(0x10, ®s, ®s);
}
To get back into text mode, just change the 0x13 to 0x03, as follows:
void setTextMode()
{
__dpmi_regs r;
r.x.ax = 0x03;
__dpmi_int(0x10, &r);
}
The Borland C example is similar.
Now I'll show you how to display something.
Video Memory
The method of drawing directly to the screen is very simple, and makes use of
the video memory in the computer. In most modes, this is 64k of memory starting at the segment 0xA000. In flat mode addresses, this is 0xA0000. The contents of this 64k determine what gets displayed on the screen. Change a byte in this memory block and the display changes.
It is possible to write directly to this memory whenever you want, but this can cause flickering, so it is best to wait until the "Vertical Retrace". This is the time when the electron gun in your monitor is going back from the bottom to the top. During this time, the video memory is not being used so you can write to it. For the moment, we'll just write to it and if your computer is fast enough you won't notice a thing.
In mode 13h, the way this memory corresponds to the screen is simple. Each
pixel on the screen has one byte which corresponds to it (hence 256 colours).
There are 320 columns and 200 rows, which gives a total size of 64000 bytes to describe the whole screen. The layout is completely linear, so I'll just give a few examples. The top left pixel is at offset 0 in the video memory. The pixel on the top row and in the 50th column is at offset 49. The last pixel on the first row is at offset 319. The first pixel on the 2nd row is at offset 320 and so on.
So it should be fairly obvious that the formula for the offset of a pixel at
coordinates (x,y) is given by (320 * y) + x.
Plotting Pixels
Here's a simple C (DJGPP) function to plot a pixel.
void drawPixel(int x, int y, unsigned char colour)
{
if ((x >= 0) && (x <= 319) && (y >= 0) && (y <= 199))
_farpokeb(_dos_ds, 0xA0000+(y*320)+x, colour);
}
The Borland C version is similar, but I think you can use the pokeb
function instead of _farpokeb. And you will have to deal with segment rubbish as well, so you might as well just get DJGPP. Go to here to find out more.
This method is fine, but it is a bit slow, because of the multiply needed. There are two solutions to this: a lookup table (LUT) or using shifts instead of multiplies.
Using a look-up table
This is easy, we just make an array of ints, with a size of 200, which contains the offset of each row on the screen. Here's some code:
int row_table[200];
void MakeRowTable()
{
for (int i=0; i<200; i++)
row_table[i] = i*320;
}
Then to find the offset of the pixel at (x,y) we just do row_table[y]+x.
Using shifts instead of multiplies
In case you don't know (and you really should) a shift moves all the bits in the binary representation of a number to the left or right. So if we have the binary number 00101101 (=45) and we shift it to the left once, we get 01011010, which is equal to 90 in decimal. So it just multiplies it by 2. In general, if we shift a number to the left N times, the number is multiplied by 2^N. You are probably saying "so what?" at this stage, so I'll point out the application. It all comes down to
320 = 256+64 = 2^8 + 2^6
So to multiply a number by 320, we can do the following:
int offset;
offset = y << 6;
offset += y << 8;
Which is fast enough, because shifts are way faster than multiplies.
Line Drawing
This is the fundamental drawing algorithm, as it is needed to draw polygons and
so on. The basic idea is to start at one endpoint of the line, and for every
row we go down, to add a precalculated number, deltaX, to our x-coordinate.
Calculate deltaX, from the simple formula
deltaX = (x2 - x1) / (y2 - y1). This basically says that the distance for an
individual y is the same as the distance for (y2 - y1) rows divided by the
number of rows. I should probably have a diagram here, but I haven't got
around to it yet. Do not attempt to divide by (y2-y1) if y1 is equal to y2. Division by zero is a stupid thing to do.So here is the full algorithm:
- Sort the two points (x1,y1) and (x2,y2) so that the lowest y coordinate
comes first.
- If y1 == y2 then draw the line straight away. (It's horizontal, easy)
- Calculate deltaX from the formula given.
- Now do your main loop. Increment your y coord by 1, and your x coord by
deltaX, then draw the point using the drawPixel routine.
One important thing to note: This line will not draw shallow lines
correctly. This is because they have more than one pixel on each row. To fix
this, do a test to see if the line is shallow, and if it is, use a different
algorithm where the x is incremented by 1 and the y coord is incremented by
deltaY. The algorithm given here is perfectly good for use in polygons, as
you will see later.
And that's all you need to do. Before I show you the source code for this
routine, I should point out that this is not the line drawing algorithm which
is usually used. Bresenham's Algorithm uses only integer arithmetic and is
therefore faster. If you want to see this algorithm, there are details in the
PC Game Programmer's Enclycopedia, see my links page
for details of where to get it. One more method is to use the same algorithm,
but use fixed point numbers instead of floating point. More about fixed point
later.
typedef struct POINT {
double x,y,z;
} POINT;
void drawLine(POINT *p1, POINT *p2, unsigned char colour)
{
POINT *ptop, *pbot;
double deltaX,x;
int i,y,ix;
// sort points
if (p2->y > p1->y)
{
ptop = p1;
pbot = p2;
}
else
{
ptop = p2;
pbot = p1;
}
//handle horizontal line
if (ptop->y == pbot->y)
{
if (ptop->x < pbot->x)
{
for (i=ptop->x; i<=pbot->x; i++)
{
drawPixel(i, ptop->y, colour);
}
}
else
{
for (i=pbot->x; i<= ptop->x; i++)
{
drawPixel(i, ptop->y, colour);
}
}
return;
}
//handle all other lines
deltaX = (pbot->x - ptop->x) / (pbot->y - ptop->y);
x = ptop->x;
for (i=ptop->y; i<=pbot->y; i++)
{
ix = x;
drawPixel(ix, i, colour);
x += deltaX;
}
return;
}
That's all folks...
Back to the main page