Please keep in mind that this is a really simplified view; there are a couple of limitations when implementing this in the real world that complicate the proper execution. Nevertheless, this explanation will allow you to get the basic idea. A video card has multiple modes. Some of them, in order to assure better performance, will probably do things a little differently from the way I described on the previous page. However, there exist a number of modes that all video cards know: EGA, VGA, and SVGA. In the text to follow, we will work on the latter.
The standard printing functions are slow. Most of the time this is because they perform multiple security checks, and because they are capable of executing some complex tasks (like printing both integer and double numbers and text). However, whenever we want to perform a fast print to the screen, we want to bypass all this. The solution is to copy the data from the system memory to the graphic card's memory ourselves.
The structure of the video card's memory depends on the mode it is running and the video card itself. In order to somehow bypass incompatibilities, there exist some standardized modes that all video cards can run. SVGA is just one of these, and while you run in this mode, the structure of all video cards will be the same regardless of whether the subject is a 10-year-old card or a brand new one just rolled out on the doors of NVidia or ATI.
The SVGA mode is a text-based console window that can print out 80 characters in a row and has 25 rows. The address of the used memory in this mode starts at 0XB800 with the offset of 0X000. Starting from this point, for every character on the screen we have two corresponding bytes, one after another.
The first holds the character itself (coded in the ASCI) and the second is the attribute byte. This tells to the video card how to draw out the character found in the first byte. I have have depicted the structure of this in the following image.
The coding used is the RGB channel (Red Green Blue). Every color comes out of the combination, at some level, of these colors. We have one bit for each channel that shows whether that color is mixed in into the resulting color, and a fourth bit (the I character in the picture above) is an intensity bit. If it is on, the color is brighter; if not, a darker style of the color appears on the screen.
The bits on 6,5,4 communicate the background color for that character. The seventh bit is the blink bit. If this is on, the text will blink, just like the cursor does in a Word document. The character set used, and the font, depend on the selected table. This is a table that the video card stores in a ROM or RAM memory of 256 elements (one for each ASCI code). If it is in the RAM, the user can modify it.
You can change the font and the colors used with some Bios functions of the video card. The default values are characters that are 9 X 16 pixels in size. The primary character set (up to the 127th ASCI code) is at the address 0XF000 to 0XFA6E. The rest of the character set is at the address returned by the break vector 0X1F. We will describe break vectors in a future article.
The current operating systems do not allow this kind of low level access to the video card. In order to allow this, we need to use an environment that grants us that access. The Turbo C or the Borland C 3.1 development IDE allow this. First you will need to acquire such a thing. A few Google searches should definitely reveal a couple of sources. Furthermore, these are 16-bit programs.
Because the Windows operating system's backward compatibility is only one step long, you cannot run these programs on a 64-bit operating system. If you are in this situation, to try out this code you will need to install a 32-bit operating system. In order to make the task of installing a new operating system a little easier, you may use a virtualization method like the one presented by the VMware Player or the Windows virtualization system.
Once you have a development environment like the Borland Turbo C++ 3.0 up and running, you can create a new file and use the pokeb function to write data to the video memory. Defined in the dos.h header, this function will store a byte value at the memory location and the offset specified. With the following formula: (80*y+x)*2 we can calculate the offset of a character in row y and column x. Now we can start up and just use the following code:
#include <dos.h>
#include <time.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
typedef unsigned int uint;
typedef unsigned char uchar;
void clear () // clear the screen -> black bg and fg
{
uint x, y;
uint offset;
for (x=0; x<80; ++x)
for (y=0; y<25; ++y)
{
offset = (80*y + x)*2;
pokeb (0xB800, offset, 0);
pokeb (0xB800, offset+1, 0);
}
}
// Put a character in the x-th row y-th column
// The attributes are inside a and the character is c
void putXY (uint x, uint y, uchar c, uchar a)
{
uint offset;
if (x<80 && y<25)
{
offset = (80*y + x)*2;
pokeb (0xB800, offset, c);
pokeb (0xB800, offset+1, a);
}
}
// Put the text(s) in l-th row with attributes a
void putLine (uint l, uchar *s, uchar a)
{
uint i;
for (i=0; s[i]; ++i)
putXY (i, l, s[i], a);
}
//Print a file text. Attributes are random. Stop per screen.
void putFileText (char *fname)
{
FILE *f;
char s [80];
uint l;
uchar a=15;
randomize (); // to get truly random attributes
f = fopen (fname, "r"); // open file
if (f == NULL)
return;
for (l=0; !feof (f); ++l) // until we have from file
{
do {// avoid same foreground and background colors
a = random (256);
} while ((a & 0XF0)>>4 == (a & 0X0F));
fgets(s, 80, f); //get item and print
putLine (l, (uchar*) s, a);
if (l==24) // when the screen is full, clear and wait
{
getch ();
clear ();
l=-1; //reset to start
}
}
fclose (f); //close the file
}
//Fill the background with the attribute, use space chars
void fillBackground (uchar a)
{
uint x, y;
uint offset;
for (x=0; x<80; ++x)
for (y=0; y<25; ++y)
{
offset = (80*y + x)*2;
pokeb (0xB800, offset, ' ');
pokeb (0xB800, offset+1, a);
}
}
// Fill the screen with zeros
void fillForeground (uchar a)
{
uint x, y;
uint offset;
for (x=0; x<80; ++x)
for (y=0; y<25; ++y)
{
offset = (80*y + x)*2;
pokeb (0xB800, offset, '0');
pokeb (0xB800, offset+1, a);
}
}
// construct an attribute byte based on the input
uchar setAttributes (uchar Bi, uchar Br, uchar Bg, uchar Bb,
uchar i, uchar r, uchar g, uchar b)
{
uchar a=0;
a |= (Bi&1) <<7;
a |= (Br&1) <<6;
a |= (Bg&1) <<5;
a |= (Bb&1) <<4;
a |= (i&1) <<3;
a |= (r&1) <<2;
a |= (g&1) <<1;
a |= (b&1) <<0;
return a;
}
// merge predetermined background and foreground colors
uchar setBackgroundForegound (uchar B, uchar F)
{
uchar a=0;
a |= (B&0xF) <<4;
a |= (F&0xF) <<0;
return a;
}
//The main entry point of the program
int main ()
{ //set SVGA text mode
uchar a;
textmode (3);
clear ();
// Using a yellow char color and dark red background
// print letter a at 0 0
a = setAttributes (1, 1, 0, 0, 1, 0, 1, 0);
putXY (0, 0,'a', a);
//at 39 12 put a white background
a = setBackgroundForegound (0, 4);
putXY (39, 12, 13, a);
getch (); //wait for user input
//clear the screen and wait for user input
clear ();
getch ();
//Black background, red letters print the command code
fillBackground (setBackgroundForegound (7, 10));
putFileText (
"C:Archit~1Archit~1Archit~1Video ext.cpp");
getch (); //wait for user input
textmode (LASTMODE); //reset the previously used text mode
return 0;
}
The code snippet on the previous page will deliver first a letter in the upper left corner, and a music key in the center of the screen.