---[ Graphics: VESA Bios Extensions Part III ]--------[05/26/2008]---
by Timothy Trussell
---[ Where We Are ]--------------------------------------------------
In Part I of this series, we had an overview of what is involved in
implementing a VESA BIOS Extensions (VBE) interface for a compatible
video card.
In Part II, we coded part of this interface, which lets us detect if
the VBE subsystem is present on our video card.
This included:
1. Allocating/Releasing a memory block via the DPMI system
[AllocDOSMem/FreeDOSMem]
2. Putting information into this memory block, which is located
outside of the 32Forth memory allocation, and has to be
accessed in REAL mode instead of the normal 32Forth Protected
Mode environment.
[PokeDOSMem]
3. Transferring the data from the DPMI block to an array in the
32Forth Protected Mode environment.
[GetLowBuffer]
4. Checking to see if VBE is present on the video card.
[IsVBE?]
5. Linking all the above procedures together to get the video
info data, and then processing that data into a usable data
structure.
[VBEDetect]
6. A short demo to display the data that is returned from the
video card by calling VBEDetect.
[ShowInfo]
At the end of Part II, I said that our next step would be to code the
VBEOpen module, which will intialize the graphics mode that we want
to use. Actually, we will *start* to code this module, as it has
turned out to be a bit more complicated than I initially planned.
---[ Pseudo Code ]---------------------------------------------------
Referencing the extract from GVBE01, the pseudo-code that we will be
working with for this column is in these two sections:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
+-= int vbeGetModeInfo(int mode,VBEMODEINFO *bla) =- |
| start: |
| call VbeDetect JIK |
| 'DPMI 100h' Get memory to put modeInfo Block into |
| Ready RMREGS for 'VESA 4F01h' |
| 'DPMI 300h' to call VBE 4f01h in real mode |
| move everything from modeInfo Block to bla |
| free modeInfo Block with 'DPMI 101h' |
| return Success |
| End: |
| |
+-= VBESURFACE *vbeOpen(int X,int Y,int BPP) =- |
| start: |
| Make sure we arent already in a vesa mode |
| call VbeDetect JIK |
| 'DPMI 100h' to allocate for ModeInfo block (To check every |
| available mode) |
| Go through all available modes returned by VbeDetect until |
| finding a match |
| OR the Mode found with 4000h to say we want LFB |
| If It worked, map the LFB memory to accessible memory with |
| 'DPMI 800h' |
| Set vbeInit variable so from now on we know a mode is set |
| Make sure logical scanline width is equal to screen scanline |
| width. |
| Set everything in the return structure |
| end: |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
We will actually only get about half-way thru the VBEOpen section.
---[ Accessing the Mode Information ]--------------------------------
To be able to set up a VESA graphics mode, we specify three basic
parameters that we want to use:
1. Screen width (X resolution)
2. Screen height (Y resolution)
3. Color depth (Color)
The first two parameters are pretty straightforward, indicating the
X/Y maximums for the desired screen resolution, but the color depth
parameter needs a little more explanation.
In our normal usage of the color parameter, we have been thinking of
it as being in the range of 16, 256, 65536 and 4294967295 separate
colors available for display on the monitor.
In VBE, we have to think of this in a slightly different manner.
VBE defines the color depth by the number of bits required to display
the pixel on the screen.
The 256 color modes are defined by an 8-bit pixel, as is the case in
Mode 13h - 320x200x256. This means that a single byte is used for
each pixel on the screen.
Therefore, rather than thinking of the modes as
640x480x256 800x600x256 1024x768x256 and 1280x1024x256
VBE looks at them as
640x480x8 800x600x8 1024x768x8 and 1280x1024x8
The 65,536 color mode uses 16-bits/pixel, and VBE sees them as
640x480x16 800x600x16 1024x768x16 and 1280x1024x16
The 4,294,967,295 color mode uses 32-bits/pixel, seen as
640x480x32 800x600x32 1024x768x32 and 1280x1024x32
---[Note]------------------------------------------------------------
Those who already know something about the available colors on a
graphics card will be quick to bring up that there are other color
modes available, specifically the 32,768 and 16,777,216 color modes
that use 15-bits and 24-bits per pixel, respectively.
--------------------------------------------------------[End Note]---
So, how do we figure out how to initialize the video display to the
resolution we want to use - and/or/if it is even available?
We will concentrate on the 640x480x256 (640x480x8) mode for this.
What we do is scan the modes list that we retrieved during VBEDetect
to see if any of the modes listed match the requirements we have - an
X resolution of 640, a Y resolution of 480, and a color depth of 8.
The mode listing is in the LocalBuf[] array, where it was transferred
when VBEDetect was executed. You can view this by entering:
LocalBuf[] 256 dump
which, on my system, displays the following:
3BF4F0 00 01 01 01 02 01 03 01 04 01 05 01 06 01 07 01
3BF500 0E 01 0F 01 11 01 12 01 14 01 15 01 17 01 18 01
3BF510 1A 01 1B 01 30 01 31 01 32 01 33 01 34 01 35 01
3BF520 36 01 3D 01 3E 01 45 01 46 01 47 01 48 01 52 01
3BF530 FF FF 4E 56 49 44 49 41 00 4E 56 49 44 49 41 20
3BF540 43 6F 72 70 6F 72 61 74 69 6F 6E 00 47 37 32 20
3BF550 42 6F 61 72 64 20 2D 20 70 32 38 30 68 31 32 20
3BF560 00 43 68 69 70 20 52 65 76 20 20 20 00 00 00 00
3BF570 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF580 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF590 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
This listing is a table of 16-bit values, ending when the value
$0FFFF is found (at $3BF530 here). The data following the $0FFFF
value are the OEM strings, which have been placed sequentially
following the mode listing.
In this particular case, the above mode numbers have the following
resolution descriptions - on the video card in *my* system.
Do NOT take this list as a table of what these mode numbers are
ALWAYS defined as - they aren't.
-------------------------------------------------------
Node Mode XxYxBPP Node Mode XxYxBPP
-------------------------------------------------------
1: 100h, 640x400x8 17: 11Ah, 1280x1024x16
2: 101h, 640x480x8 18: 11Bh, 1280x1024x32
3: 102h, 800x600x4 19: 130h, 320x200x8
4: 103h, 800x600x8 20: 131h, 320x400x8
5: 104h, 1024x768x4 21: 132h, 320x400x16
6: 105h, 1024x768x8 22: 133h, 320x400x32
7: 106h, 1280x1024x4 23: 134h, 320x240x8
8: 107h, 1280x1024x8 24: 135h, 320x240x16
9: 10Eh, 320x200x16 25: 136h, 320x240x32
10: 10Fh, 320x200x32 26: 13Dh, 640x400x16
11: 111h, 640x480x16 27: 13Eh, 640x400x32
12: 112h, 640x480x32 28: 145h, 1600x1200x8
13: 114h, 800x600x16 29: 146h, 1600x1200x16
14: 115h, 800x600x32 30: 147h, 1400x1050x8
15: 117h, 1024x768x16 31: 148h, 1400x1050x16
16: 118h, 1024x768x32 32: 152h, 2048x1536x32
---[Note]------------------------------------------------------------
While the listing from this video card appears in a sequential order,
there is no set standard for how the modes have to appear in this
data block, so don't expect resolution 0100h to be the first one
always listed.
This is illustrated by this listing from my laptop:
3BF4F0 82 01 0D 01 0E 01 0F 01 20 01 92 01 93 01 94 01
3BF500 95 01 96 01 A2 01 A3 01 A4 01 A5 01 A6 01 B2 01
3BF510 B3 01 B4 01 B5 01 B6 01 C2 01 C3 01 C4 01 C5 01
3BF520 C6 01 00 01 83 01 84 01 85 01 86 01 01 01 10 01
3BF530 11 01 12 01 21 01 03 01 13 01 14 01 15 01 22 01
3BF540 05 01 16 01 17 01 18 01 23 01 07 01 19 01 1A 01
3BF550 1B 01 24 01 40 01 41 01 42 01 43 01 44 01 FF FF
3BF560 52 41 44 45 4F 4E 20 49 47 50 20 33 34 30 4D 20
3BF570 00 41 54 49 20 54 65 63 68 6E 6F 6C 6F 67 69 65
3BF580 73 20 49 6E 63 2E 00 4D 53 32 20 00 30 31 2E 30
3BF590 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3BF5E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
In this case, they are listed in resolution order:
-------------------------------------------------------
Node Mode XxYxBPP Node Mode XxYxBPP
-------------------------------------------------------
1: 182h, 320x200x8
2: 10Dh, 320x200x15 29: 185h, 640x400x24
3: 10Eh, 320x200x16 30: 186h, 640x400x32
4: 10Fh, 320x200x24 31: 101h, 640x480x8
5: 120h, 320x200x32 32: 110h, 640x480x15
6: 192h, 320x240x8 33: 111h, 640x480x16
7: 193h, 320x240x15 34: 112h, 640x480x24
8: 194h, 320x240x16 35: 121h, 640x480x32
9: 195h, 320x240x24 36: 103h, 800x600x8
10: 196h, 320x240x32 37: 113h, 800x600x15
11: 1A2h, 400x300x8 38: 114h, 800x600x16
12: 1A3h, 400x300x15 39: 115h, 800x600x24
13: 1A4h, 400x300x16 40: 122h, 800x600x32
14: 1A5h, 400x300x24 41: 105h, 1024x768x8
15: 1A6h, 400x300x32 42: 116h, 1024x768x15
16: 1B2h, 512x384x8 43: 117h, 1024x768x16
17: 1B3h, 512x384x15 44: 118h, 1024x768x24
18: 1B4h, 512x384x16 45: 123h, 1024x768x32
19: 1B5h, 512x384x24 46: 107h, 1280x1024x8
20: 1B6h, 512x384x32 47: 119h, 1280x1024x15
21: 1C2h, 640x350x8 48: 11Ah, 1280x1024x16
22: 1C3h, 640x350x15 49: 11Bh, 1280x1024x24
23: 1C4h, 640x350x16 50: 124h, 1280x1024x32
24: 1C5h, 640x350x24 51: 140h, 1400x1050x8
25: 1C6h, 640x350x32 52: 141h, 1400x1050x15
26: 100h, 640x400x8 53: 142h, 1400x1050x16
27: 183h, 640x400x15 54: 143h, 1400x1050x24
28: 184h, 640x400x16 55: 144h, 1400x1050x32
This ordering is apparently at the choice of the manufacturer. This
is, indeed, one of the main reasons that the VESA standard was first
created, to eliminate the confusion created by a lack of standard
nomenclature for what each mode was supposed to be.
--------------------------------------------------------[End Note]---
Now, finding a match to the X/Y/Color parameters is done by running
sequentially through the available modes, loading the data for each
and comparing all three values until a match is found.
We will start with the code that retrieves the specified mode info
from the video card:
code (GetModeInfo) ( mode -- 0/1 )
es push
di push
ds push
si push
dosmem_segment #) ax mov
op: ax PREGS 34 + #) mov \ reges/regds are not set by INT10
di di xor \ a/b/c/dx and e/si are, though
$4F01 # ax mov
bx cx mov
INT10 #) call
1 L# jc \ error if carry set
$4F # al cmp
1 L# jne \ 4Fh not returned, error out
1 # bx mov \ return bx=1 as success
2 L# ju
1 L:
bx bx xor \ return bx=0 as error
2 L:
si pop
ds pop
di pop
es pop
end-code
no-expand
(GetModeInfo) will retrieve the information for the specified mode,
returning data to a DPMI allocated real mode buffer we created using
the AllocDOSMem word in GVBE02. (GetModeInfo) is not to be called
directly, but is called from the VBEGetModeInfo word.
The format of the data returned by this call is defined by the
following structure:
S{
\ Mandatory information for all VBE revisions:
{2BYTE}-DEF :: .ModeAttributes \ mode attributes
{1BYTE}-DEF :: .WinAAttributes \ window A attributes
{1BYTE}-DEF :: .WinBAttributes \ window B attributes
{2BYTE}-DEF :: .WinGranularity \ window granularity
{2BYTE}-DEF :: .WinSize \ window size
{2BYTE}-DEF :: .WinASegment \ window A start segment
{2BYTE}-DEF :: .WinBSegment \ window B start segment
{1WORD}-DEF :: .WinFuncPtr \ pointer to window function
{2BYTE}-DEF :: .BytesPerScanLine \ bytes per scan line
\ Mandatory information for VBE 1.2 and above:
{2BYTE}-DEF :: .XResolution \ horizontal resolution in pixels
{2BYTE}-DEF :: .YResolution \ vertical resolution in pixels
{1BYTE}-DEF :: .XCharSize \ character cell width in pixels
{1BYTE}-DEF :: .YCharSize \ character cell height in pixels
{1BYTE}-DEF :: .NumberOfPlanes \ number of memory planes
{1BYTE}-DEF :: .BitsPerPixel \ bits per pixel
{1BYTE}-DEF :: .NumberOfBanks \ number of banks
{1BYTE}-DEF :: .MemoryModel \ memory model type
{1BYTE}-DEF :: .BankSize \ bank size in KB
{1BYTE}-DEF :: .#ImagePages \ number of images
{1BYTE}-DEF :: ._Reserved \ reserved for page function
\ Direct Color fields (req'd for direct/6 and YUV/7 memory models)
{1BYTE}-DEF :: .RedMaskSize \ size of red mask in bits
{1BYTE}-DEF :: .RedFieldPos \ bit pos of lsb of red mask
{1BYTE}-DEF :: .GreenMaskSize \ size of green mask in bits
{1BYTE}-DEF :: .GreenFieldPos \ bit pos of lsb of green mask
{1BYTE}-DEF :: .BlueMaskSize \ size of blue mask in bits
{1BYTE}-DEF :: .BlueFieldPos \ bit pos of lsb of blue mask
{1BYTE}-DEF :: .RsvdMaskSize \ size of reserved mask in bits
{1BYTE}-DEF :: .RsvdFieldPos \ bit pos of lsb of reserved mask
{1BYTE}-DEF :: .DirectColorInfo \ direct color mode attributes
\ Mandatory information for VBE 2.0 and above:
{1WORD}-DEF :: .PhysBasePtr \ flat frame buffer address
{1WORD}-DEF :: .OffScreenMemPtr \ start of offscreen memory ptr
{2BYTE}-DEF :: .OffScreenMemSize \ amount of offscreen memory in
\ 1k units
14 BYTE* :: .reserved2[] \ pad to 64-byte size
\ 206 BYTE* :: .reserved2[] \ pad to 256 byte block size
}S vbemodeinfo-obj
---[Note]------------------------------------------------------------
For the time being, I am reducing the size of this structure from
256 bytes to 64 bytes, as the possible data in the .reserved2[] field
is not used - by me - at this time. I will leave the full filler
entry in place, pending such time as that area is required.
The full 256-byte block will be transferred during the $4F01 call to
the DPMI allocated memory block, but we will only transfer the first
64 bytes of that data - for now.
--------------------------------------------------------[End Note]---
For matching to the three parameters, we will be comparing them to
the .XResolution, .YResolution and .BitsPerPixel elements in this
structure.
The high level word that calls (GetModeInfo) is VBEGetModeInfo, which
performs the following functions:
1. Detects VBE
2. Allocates DPMI memory block
3. Calls (GetModeInfo) to get the mode info data
4. Transfers data from low memory to specified aray
5. Frees DPMI memory block
6. Returns BOOLEAN on Failure/Success (0/1)
and is coded as:
: VBEGetModeInfo ( mode &dst -- 0/1 )
VBEDetect if
AllocDOSMem if
swap (GetModeInfo) if
64 swap GetLowBuffer
1
else
drop 0
then
FreeDOSMem
else
2drop 0
then
else
drop
cr ." Error: Problem accessing information for mode #: "
hex 3 .R ." h" decimal cr
\ should probably abort here...
0
then
;
To be able to examine the data available for all of the resolutions
available, it is a trivial matter to create a linked list array that
will hold all of the entries.
The following module creates a Single Linked List:
\ ------------------
\ ---[ Mini Linked List ]--------------------------------------------
\ ------------------
\ Allows for loading all of the mode information into a linked list
\ structure array.
S{
{1WORD}-DEF :: .next \ pointer to next list entry
{1WORD}-DEF :: .mode# \ VESA mode number for this node
vbemodeinfo-obj :: .modeinfo \ data that defines this mode
}S vbelist-obj
value %%first \ first list entry - anchor
value %%current \ the one we're working with
value %%prev \ the last one we worked with
\ Create the initial linked list node
DoFirst creates the initial node for the list we'll be working with.
The first step is to allocate a block of memory on the heap.
The pointer to this block is then stored into the %%current and
%%first pointers. We then set the .mode# element to the mode parameter
passed on the stack.
VBEGetModeInfo requires two parameters, the mode number that we want
to get the information for, and an address to store the data to.
On success, the .modeinfo block of the array pointed to by %%current
(and %%first at this point) is filled with the 64 bytes of data that
represent the data defined by the VBEModeInfo structure, above.
On an error, for now, a text message is displayed. This will probably
be removed in the final version of the VBE module, as on an error, we
will probably do an abort call from VBEGetModeInfo, so an error will
(should) never get back to this point.
: DoFirst ( 1stmode# -- )
vbelist-obj HeapAlloc dup \ allocate first block on heap
to %%current \ init list pointers
to %%first
dup
%%current .mode# ! \ set .mode# element of structure
%%current .modeinfo \ get .modeinfo address pointer
VBEGetModeInfo not if \ get the mode information data
." DoFirst: Error getting "
hex
%%current .mode# @ 3 .R
decimal
." h mode information"
cr
then
;
\ Add a new node to the list, and populate it
AddNode is used to add nodes to the list that is anchored by the
pointer %%first.
The %%prev point is set to the %%current pointer, freeing %%current
to be used to point to the new data block we will then allocate on
the heap.
The address of the new block is then put into the previous .next
field (%%current %%prev .next !), adding the new entry into the linked
list sequencing.
Then, as in DoFirst, the .mode# field and .modeinfo array is filled
with data for the current mode parameter.
: AddNode ( mode# -- )
%%current to %%prev \ set %%prev to %%current
vbelist-obj HeapAlloc \ allocate new memory block
to %%current \ set %%current to new block
%%current %%prev .next ! \ link to previous node
dup
%%current .mode# ! \ set .mode# element of structure
%%current .modeinfo \ get .modeinfo address pointer
VBEGetModeInfo not if \ get the mode information data
." AddNode: Error getting "
hex
%%current .mode# @ 3 .R
decimal
." h mode information"
cr
then
;
\ Walk through the mode list and add a node for every entry
The last word of the Linked List module, BuildModeList, calls DoFirst
to create the initial list node, then simply walks thru the mode
table listing in the LocalBuf[] array and calls AddNode for each
entry, until the end of list pointer, $0FFFF, is found.
value %%modeptr \ work pointer
value %%count \ count of nodes created
\ %%modeptr is initialized to the start of the list data prior to
\ this word being called.
\ Could be passed as a parm on the stack. Depends on the user.
: BuildModeList ( -- )
%%modeptr H@ DoFirst \ create initial list entry
begin
2 +to %%modeptr \ increment to next mode#
1 +to %%count \ count how many nodes we make
%%modeptr H@ \ get a mode entry
dup $0FFFF = if \ end of list when $0FFFF found
drop 1 \ exit loop if found
else
AddNode \ create new node, populate it
0 \ keep going
then
until
;
---[ Finding A Specific Mode ]---------------------------------------
Now, it is a simple matter to perform the match comparisons to find
if the mode we want to use is available.
: FindMode ( x y bpp -- 0/1 )
%%first to %%current
begin
%%current .modeinfo >R
2 pick R@ .XResolution H@ =
2 pick R@ .YResolution H@ = AND
over R> .BitsPerPixel C@ = AND if
1 1 \ exit - found
else
%%current .next @ 0= if
0 1 \ exit - not found
else
%%current .next @ to %%current
0 \ loop again
then
then
until
if
dup 3 RSHIFT \ x y bpp bpp/8
VBESurface .depth ! \ x y bpp
VBESurface .bits ! \ x y
VBESurface .height ! \ x
VBESurface .width ! \ --
%%current .mode# @
VBESurface .vbeMode !
%%current VBESurface .node^ !
1
else
drop 2drop
VBESurface vbesurface-obj 0 fill
0
then \ 0/1
;
On success, the array VBESurface, with the following structure:
S{
{1WORD}-DEF :: .vbeMode \ VBE mode number
{1WORD}-DEF :: .node^ \ linked list node address of data
{1WORD}-DEF :: .width \ width of screen
{1WORD}-DEF :: .height \ height of screen
{1WORD}-DEF :: .bits \ bits per pixel
{1WORD}-DEF :: .depth \ bytes per pixel
{1WORD}-DEF :: .^lfb \ pointer to LFB
}S vbesurface-obj
is filled with the corresponding information, and a TRUE value is
returned.
To keep from having to do an additional search to find the node that
has the data for this mode, the .node^ element is set to the node
address in the linked list.
On failure, VBESurface is zeroed, and a FALSE value is returned.
---[ Viewing the Mode Information ]----------------------------------
With all the above code, we will now add some utility words to let us
look at the information that defines each of the different modes on
the video card.
The following words:
ListNodeData ShowEntries ModeList
access the VBE system and exercise all of the elements of the VBE
interface that we have coded so far.
The main routine, ModeList, must be executed before the other two
will function, as it is the procedure that initializes and loads the
VESA information into the linked list.
ShowEntries will walk thru the linked list, displaying a large amount
of the information available for each resolution. Pressing ESC will
exit it listing.
ListNodeData displays simply the mode number and the resolution for
that mode. Pressing ESC will exit the listing.
---[ Wrapping Up ]---------------------------------------------------
That almost does it for the "preparation" steps of initializing the
VESA interface.
We have - oh - probably one more step to go before we can attempt to
energize all those high resolution mode, and then we should be off
and running.
In Part IV, we'll get into trying to map the Linear Frame Buffer into
the system memory, and then the final stage of coding the VBEOpen
module is next.
------------------------------------------------------[End GVBE03]---