VESA BIOS EXTENSION 2.0
Basic Trainer
by dlt, May 1998
This trainer/tutorial is aimed at getting you started with VBE2; that is,
setting up an appropriate graphics mode, drawing to the screen, and some
other basic functions that you will probably need. If you are interested
in anything more advanced or in-depth, I suggest you get the VBE2 spec:
ftp://ftp.scitechsoft.com/devel/docs/vbe20-11.exe
What might also be helpful is the DPMI spec; 0.9 will do as well as 1.0.
ftp://x2ftp.oulu.fi/pub/msdos/programming/specs/dpmispec.arj (0.9)
or
ftp://x2ftp.oulu.fi/pub/msdos/programming/specs/dpmi100.zip (1.0)
As you will discover while reading this document, I use DJGPP, and I like
it a lot :-) Many thanks to DJ Delorie & co. Point yourself to:
http://www.delorie.com/
2. A very basic understanding of the differences between protected mode
and real mode.
3. Reading lessons help, as my science teacher is wont to say...
This is probably the first thing you will want to do. But before you can
set up a video mode using VBE2, you need to establish that VBE2 functions
are in fact installed on your system. For this you need a data structure.
VBE_INFO_STRUC VBE_Signature DWORD Version WORD OEM_String DWORD Capabilities DWORD VideoModeList DWORD TotalMemory WORD OEMSoftwareRev WORD OEMVendorName DWORD OEMProductName DWORD OEMProductRev DWORD Reserved 222 bytes OEM_Data 256 bytes VBE_INFO_STRUC ENDS
A couple of these may need explaining:
OEM_* are seg:off pointers to null-terminated strings.
VideoModeList points to an array of words representing the VBE2 display
modes supported by the video card. This list is terminated by a 0xffff.
TotalMemory is in 64k chunks, so you need to multiply by 65536 to get
the number of bytes of video memory actually available.
Version is a binary-coded decimal. In other words, the most significant
byte contains the major version number (the number before the decimal
point) and the least significant byte contains the minor version number.
Capabilities is a set of flags indicating what the video card can do. You
probably won't need to look at this.
You'll need to set this structure up in whatever form is appropriate to your
compiler. Note that for many C compilers, an int is 4 bytes big, in other
words a signed dword. You need to use the type short to store words. Also
beware of #pragma pack() and __PACKED__ conventions (look these up in your
compiler's documentation).
Now that you have the structure, you need to do three things:
2. Call the real-mode interrupt 0x10, with:
AX = 0x4f00
ES:DI = pointer to the (real mode) address of the VBE_INFO structure
(if you don't know how to call a real-mode interrupt, then have a look
at the section "CALLING REAL-MODE INTERRUPTS" at the end)
3. Check that
a) VBE_Signature is now equal to "VESA"
b) VBE_Version >= 0x0200
To set up a particular screen mode is relatively easy. Again, you call real-
mode interrupt 0x10, with AX = 0x4f02, and BX = the video mode you want. To
get back to text mode, do the same thing with BX = 3 (this is exactly the
same as a normal BIOS "set video mode" call).
LINEAR FRAME BUFFERS
This is probably the reason you wanted to use VBE2 in the first place, right?
For those who are in the dark, having a linear frame buffer is a neat little
protected mode gimmick whereby you can access all of the video card's memory
directly. Yup, that's right, no messing around with write-planes and small
windows into video memory... you get to play with the whole lot at once. You
needn't worry about the mechanics of how this works. Just pretend that the
video card's memory has been tacked onto the end of the "normal" memory in
your system. All you really have to do is find out the address where it
begins. This is unfortunately a less-than-simple process.
STEP 1: Find out where the linear frame buffer is going to be for your particular video mode. Another (large) structure is required. This is it: Mode_Attributes WORD Window_A_Attributes BYTE Window_B_Attributes BYTE Window_Granularity WORD Window_Size WORD Window_A_Segment WORD Window_B_Segment WORD Window_Function DWORD (POINTER) Bytes_Per_Scanline WORD Horiz_Resolution WORD Vert_Resolution WORD Horiz_Char_Size BYTE ; for text Vert_Char_Size BYTE Number_Planes BYTE Bytes_Per_Pixel BYTE Number_Banks BYTE Memory_Model BYTE ; arrangement of data Bank_Size BYTE Number_Pages BYTE Reserved BYTE Color_Data 9 BYTES Linear_Framebuffer DWORD (POINTER) Offscreen_Offset DWORD Offscreen_Size WORD Reserved 206 BYTES
I won't go into the details of this structure. What we're interested
in is that Linear_Framebuffer entry. What it is, is a *PHYSICAL*
pointer to the start of the buffer. The reason I emphasize PHYSICAL
is because its physical address is not neccessarily the same thing as
the address you will use when accessing it (not that the physical
address has much to do with reality either...).
STEP 2:
Ask your DPMI server (the program that's in charge of protected mode)
to convert the physical address into something you can work with. This
step is very much dependant on the compiler you are using. Usually it
will be a function called "physical_address_mapping" or suchlike. For
those of you using assembler the function is:
DPMI (int 31h) function 0x0800 AX = 0x0800 SI:DI = 32-bit size (length) of requested block BX:CX = 32-bit physical address of start of block
Here is the DJGPP code I use. Look in DPMI.H or the equivalent for your compiler and you should find equivalent structures and functions:
__dpmi_meminfo ms; ms.size = vbe2info->TotalMemory*65536l; ms.address = (unsigned int)modeinfo->FrameBuffer; __dpmi_physical_address_mapping(&ms); bufferptr=ms.address;
STEP 3:
Decide how you're going to access the memory. There are generally two
methods. The first is that you allocate a selector with which you
can address the video memory. This is a big pain, and will slow you
down because of the overrides (eg. fs:[edi] instead of just [edi]).
The advantage is that it will work on *all* protected mode servers
(including Windows NT and Linux DOSEMU). However, you are unlikely to
be running a program which uses VBE2 linear framebuffering under a
secure operating system such as the two mentioned. In most cases, you
can use near pointers. The trick is to set the upper limit of your
DS selector to 0x000000ff. Then you can access the video memory using
DS, like so:
videobuffer = bufferptr - ds_linear_base
where ds_linear_base is the linear address of the start of your data
segment. This should be provided to you by your compiler or whatever
DPMI routines you are using. In DJGPP, you can use the variable
__djgpp_conventional_base, but you have to ADD it to bufferptr in the
example above, not subtract it (assume it's already been negated for
you).
Setting the limit of the DS selector can be done with DPMI function
0x0008; some compilers provide functions which do this for you.
DJGPP's is called __djgpp_nearptr_enable.
STEP 4:
You now have direct access to the video memory, where everything is
stored linearly. If you have 1 byte/pixel and 640 pixels/row, then
you can write to a pixel with
videobuffer[y*640+x] = colour
which obviously makes relatively simple work of complex algorithms.
PANNING, PALETTES, PAGE FLIPPING
The good news is that you've got past the hard bit. The rest is fairly
trivial. Here are a few other things you might want to do with VBE2:
PANNING Use the Get/Set Display Start function. Real-mode interrupt 0x10 AX = 0x4F07 BH = 0 BL = 0x00 to set the display start 0x80 to set it during a retrace (can be useful) 0x01 to get the current display start (not so useful) CX = X offset (first pixel in scanline) DX = Y offset (first scanline)
When the amount of available video memory is more than twice the
amount required for a screenful of information (Xres * Yres * BperPix),
then you can have multiple pages. The easiest way is probably to flip
between YOfs=0 and YOfs=YRes using the above function.
CHANGING THE LOGICAL SCANLINE WIDTH
This is helpful when setting up a large virtual page over which to
scroll, and also in calculating pixel addresses (for example, by
using a multiplication table or bit-shifting).
Real-mode interrupt 0x10 AX = 0x4F06 BX = as for get/set display start CX = number of pixels per logical scanline
PALETTE FUNCTIONS
VBE2 supplies palette manipulation functions, but unless you really,
really need 8-bit control over your video DAC, it's easier to use
the old VGA registers:
port[0x3c7] = read index port[0x3c8] = write index port[0x3c9] = r,g,b (write/read them one-by-one)
ANY QUESTIONS?
You can contact me via
Generally:
Use DPMI (int 31h) function 0x0300: AX = 0x0300 BL = real mode interrupt number BH = set bit one if the interrupt you are calling is an IRQ handler CX = number of words on stack to pass to interrupt ES:EDI -> real mode call structure The real mode call structure: R_EDI DWORD R_ESI DWORD R_EBP DWORD resvd DWORD R_EBX DWORD R_EDX DWORD R_ECX DWORD R_EAX DWORD R_FLAGS WORD R_ES WORD R_DS WORD R_FS WORD R_GS WORD R_IP WORD R_CS WORD R_SP WORD R_SS WORD
Tran's PMODE:
As I recall, he used int 33h with the interrupt number pushed onto the
stack. So, set up the registers as if you were in real mode, push
the interrupt number as an immediate, and call interrupt 33h.
DJGPP (and similarly for some other Cs):
Use the structure __dpmi_regs (defined in dpmi.h), and pass this to the function __dpmi_int(). For example: __dpmi_regs r; r.x.ax=0x4f01; r.x.bx=3; __dpmi_int(0x10,&r);
I believe some Cs have carried over their real-mode calling conventions;
so you will have things like UNION REGS r; dosint(0x10,&r); (if I
remember rightly :-))
Borland Pascal 7.0:
And that should be enough to get you started.