Plotting pixels in VESA modes

Source

The following text presumes you have downloaded the vesainfo.zip file and taken a look at the source. You can download the latest version of all these files from the main tutorial page (as soon as the tutorial is finished).

Bank Switching

One of the most annoying (but unavoidable) aspects of the VESA standard is bank switching. Screen memory starts at segment 0A000h in real mode. This value won't work in protected mode since there is no correlation between a real mode segment value and a protected mode selector value, but BP has the variable SegA000 to deal with this (SegA000 is always 0A000h in real mode). In our VESA library we override BP's SegA000 variable with our own. Please note that the value will be exactly the same as Borland Pascal's. The reason we do this is so that someone using a non-BP compiler can port the code more easily (good luck if you try, though ;). Due to 16-bit segmentation, only 64K of memory is accessable from any one segment. Therefore, you can only access 64K of screen memory at a time. (Note: on some video cards you can access 128K of screen memory by using the memory at SegB000. This is very unstandard, and so we do not support it). Many video modes take up far more than 64K. For example, 800x600x8bpp takes up 480000 bytes.

Banks

If you have ever tried to write to screen memory while in a VESA mode, you found that you can only write to the top part of the screen. This is, in fact, a 64K bank (or window). The first 64K is bank 0, the second 64K is bank 1, and so on.

Granularity

Okay, the last sentence of the previous paragraph is a lie. Bank 1 isn't necessarily the second 64K. The granularity of the card determines how big each bank is. Values that I have seen are 4K, 16K, and 64K, but it is possible to have a bank that is 1K. This is incredibly wasteful (not to mention slow) so you can presume that the smallest bank will be 4K. There are various reasons why to make the bank sizes smaller than 64K (which I won't bore you with here). This doesn't mean that you have to write code specific to every bank granularity. There is an easy way to write the program to think that it is always on a card with a granularity of 64K. Basically it works like this:
PseudoBank:=BankVals[Offset div 65536];
where BankVals is computed as:
var GranInc:word;
    BankVals:array[0..479] of word;

GranInc:=65536 div (longint(Granularity)*1024);
BankVals[0]:=0;
for count:=1 to 479 do
 BankVals[count]:=BankVals[count-1]+GranInc;

The reason to multiply Granularity by 1024 is because the VESA GetModeInfo() call returns the granularity in 1K blocks. So, if the granularity of the card was 4K, the value in Granularity would be 4. Let's look at two different implementations:

64K window granularity:
BankVals[0] = 0
BankVals[1] = 1
BankVals[2] = 2
etc...

4K window granularity:
BankVals[0] = 0
BankVals[1] = 16
BankVals[2] = 32
etc...

If you wanted access to the second 64K chunk, you would simply load the bank value with BankVals[1]. What the above snippet of code does is virtualize the machine into 64K banks, even if that isn't the case. Because look-up-tables (LUTs) are so quick, you will actually get a speed increase over computing the bank every time. This array system is very inexpensive to compute when changing banks (which you shouldn't be doing often anyway), and the only drawback is the memory usage. The reason the upper bound of BankVals is 479 is so that I can access the highest resolution on a card with the smallest bank size (ie, 1600*1200 div 4096). A safe upper bound is 469, but 479 allows for paragraph alignment ((480*2) mod 16=0).

Windows

No, not that piece of software written by you know who. Some VESA implementations allow for different read and write windows. Since video memory moves (where the source and destination addresses are in video memory) don't require the data to go through the bus, they can be done very quickly. However, since the source data you wish to read may not be in the same 64K address space as the destination, you would have to change banks for every read/write you do. Two windows would reduce the need for excesive bank-switching. Personally, I don't do vid-vid memory copies, and therefore never need a separate read and write window. But, since some cards have it, you must take that into account when writing a VESA library.

Okay, I'm ready to chuck VBE 1.2 and use VBE 2.0

Don't be so hasty. I'd like to point out some serious flaws with VBE 2.0. First of all, not everyone has VBE 2.0. Sure, you can download SciTech's VBE 2.0 TSR. But if you do, this will slow down your VBE 1.2 code (and you have to pay for it.. ick). This is due to SciTech's implementation of the direct bank-switching routine. They were trying to do programmers a favour by storing the current bank internally. Unfortunately, in protected mode this causes a GPF, and forces you to use the much slower Int 10h call. Secondly, the linear frame buffer isn't accessable on all machines. Of the 5 PCs we have here, only 1 supports the LFB. This means that you will have to use non-direct bank-switching anyway. Either that, or limit what machines your program(s) will run on to those few that support a LFB. Until I can guarantee that one can get a LFB on any machine with VBE 2.0, I will use VBE 1.2. Another reason we don't use VBE 2.0 is because we haven't been able to create a stable LFB in BP 7.0.

Examples

Click on the parchment at the bottom of this page to download the VESA library that will show the correct way to initialize a VESA mode and a universal way to plot a pixel.
-----------------------------
Previous pageUp one levelNext pageDownloadable stuff