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, &regs, &regs); }

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:

  1. Sort the two points (x1,y1) and (x2,y2) so that the lowest y coordinate comes first.
  2. If y1 == y2 then draw the line straight away. (It's horizontal, easy)
  3. Calculate deltaX from the formula given.
  4. 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