Tuesday, May 25, 2010

Video Memory Programming in Text Mode



Nowadays we take so many things for granted. This isn't necessarily a bad thing, as knowing that performing a certain task will usually yield the same result can be a blessing. Nevertheless, some basic knowledge of how a thing gets done lets you better understand the world in which we live. Therefore, today I invite anyone who likes to code in any of the popular languages to see how a word gets printed out on the console screen.

Any half respectable book that tries to describe a new programming language starts with this. Let us print a sentence on the screen. The classic one is "Hello World!" of course, but some like to spice things up and use a more elaborate example. To get the text on the screen, most of the time you just call a function that will take care of the rest.

However, getting any information on the screen isn't as simple for the operating system. Remember that an operating system needs to work on multiple types of hardware configurations, and the way that some information gets from the system memory to display some colors on your screen varies. To make life simpler for the OS and to avoid design chaos, some basic rules have been established that all hardware manufacturers follow at some level.

The actual job of drawing something to the screen always falls to the video card. It has to carry the input data to your screen. Now we cannot assure a real time procession, as a sudden data burst may overload our bandwidth no matter how large that is. Therefore, all video cards have, at some level, some memory for their own usage.

This memory also allows them to store data that they need frequently. Doing this relieves some pressure from the CPU, as recurring data needs to be transferred to the video card only once. If you are interested in how a CPU works in detail, I invite you to look up the article series I dedicated to this in my profile. Besides the technical description, I created a simple demo sketch of my words using the VHDL language.

Returning to the video card, the device itself will in the future take care of getting the information from its memory to the screen. Think of this task as turning on or off a given color channel of the monitor at a given point on the screen, based on whether the value of a predetermined memory bit is zero or one. With this approach, all that the OS needs to do is copy the correct information from the system memory to a predetermined zone of the video card.

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.

Where to copy


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.

The code


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

#include

#include

#include

#include

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;>

for (y=0; y<25;>

{

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>

{

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;>

for (y=0; y<25;>

{

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;>

for (y=0; y<25;>

{

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.

What do we have in the end?

For the clean screen I will not post an image (surely you can imagine a black screen), however, the printing out of the code file itself is worth a couple of comments. The file name you need to use is the one the IDE itself sees. The long folder and file names are cut off and replaced with some numbers at the end, as you can see in my example.

The background color we set previously will appear only the first time, because afterward I use the clear function I wrote to clear previous information off of the screen, and this also resets the background color. Nevertheless, you are free to write a custom clear function that may skip writing to the locations specifying the background color. Using the peekb function to read out the previous background color and set it again after clearing it is also an option. I will leave these to you.

Next time we meet I will show you how to write to the video memory using the graphic mode. This will leave you the option of printing blazing fast lines, rectangles and circles on the user's screen. Until that you may post questions you have here on the blog following the article or join the DevHardware community. Live With Passion!

No comments:

Post a Comment