![]()
::[ PART 03 - HIGH RESOLUTION MODES
]::[ Source
]::[ Printable Version
]::
::[
DISCLAIMER
]::
I will hold no responsibility to whatever happens to you, your computer, your sanity, your pet, or whatever that may happen to your existence for your reading these texts and for the outcome of the various source code(s) given in each tutorial. So in short, read at your own risk!
I have decided to divide my tutorial on high resolution modes into two parts: 8bit modes, and 15/16bit modes. Along the way, we will learn how to shift bits and other stuffs. (I guess some of you might've noticed it by now that in one text file I might introduce various stuffs useful for another purpose, e.g. did you guys notice that memset() could be used to plot a rather fast horizontal line?). What will be used here is in accordance with VESA's VBE 1.2 Standard. There is a newer version of the said standard, but we are working in DOS real mode here (a BIG limitation), so it'll do (all monitors support this standard anyway). What's important is we learn how to use the standard...that IS the aim of this tutorial anyway. A complete documentation on VESA VBE 1.2 Standard should be found along with this file (unless you got this file from someone who didn't bother to copy everything completely...:) ). And yes, this tutorial is a long one!...:). Briefly, VESA stands for "Video Electronics Standards Association". It is the organization which created the VBE Version 1.2 that we will be using (VBE stands for "VGA BIOS Extension"). SideNote: We can use what we've learned on Part 2. That is, as long as we are in an 8bit mode (whatever the resolution is).
To those still a bit uninitiated with data types, well, yes, we can use a char to read a number, provided it's in a range of -128 to 127 (as stated above...:) ). Now if we were to place an "unsigned" keyword before each data type, what would happen? Well, they're listed below:
Strictly speaking, we should've used unsigned char as the data type in our parameter for stating the color number in our pixelMEMORY() function. Why? Because we are in 8bit mode, and we could only plot color numbers 0-255...NOT -128 to 127. The same is true for palSET() and palGET()...:). Now that's over, let's start the REAL tutorial proper. First, VESA has a structure that you load values with before you set your screen to a specified mode. The structure is defined as:
//----8<----[ CODE BEGIN ]--------//
typedef struct {
unsigned char VESAsignature[4];
unsigned char VersionMinor;
unsigned char VersionMajor
unsigned char far *OEM;
long int Capabilities;
unsigned int far *VideoModes;
unsigned int TotalMemory;
unsigned char Reserved[235];
} VESA_STRUCT;
//----8<----[ CODE END ]--------//
Explaining each field:
//----8<----[ CODE BEGIN ]--------//
char getVesaInfo(VESA_STRUCT *vesainfo)
{ _AH = 0x4F;
_AL = 0x00; // VBE FUNCTION 00!!!
asm les di,[vesainfo]
geninterrupt(0x10);
if(_AX==0x4F) return(1);
return(0);
}
//----8<----[ CODE END ]--------//
Hmmm...how do I go about explaining this code???...:). I guess you guys would know what _AH and _AL are already? Again, they're registers, think of registers (for now) as variables that you store values with to tell your interrupts what to do. As described in the accompanying file VESASP12.TXT, we give our _AL register the VBE function that we want to use, in this case, we're using function 00. So in short, we give _AH a value of 0x4F to let our interrupt know we are going to use VBE, then give _AL the value of WHICH VBE function we will use...:). The line "asm les di,[vesainfo]" is an inline assembly code. This means that I'm using assembly language here...well, sort of...:). This line means that I want to put into _DI (a register) with the segment value of vesainfo (which is passed from the parameter). The "les" means "Load Effective Segment". As I said in an earlier tutorial, a segment has a base and an offset. Each offset can access a maximum range of 64K...so what I'm doing above is loading the base address to _DI. After which, when I call interrupt 0x10, vesainfo will be stored with, well, the VESA information of our monitor!...:). Whew! I'm glad THAT'S over...:). (You might start to wonder where I get the values that I store the registers with...again, they are defined with the accompanying file VESASP12.TXT...there, you will see that it blatantly state what value to store each register and what value each register will return when you call interrupt 0x10). Now that you know the VESA information of your monitor, the only real info that you might want is to know if the screen mode you want to get in to is supported. This is known through the VideoModes pointer in the structure. Now remember I said that the VideoModes pointer is ended with a value of 0xFFFF? And that each mode is named using one unsigned int??? So we make a variable that "runs" through all the modes 'til we meet the value 0xFFFF! Here's a sample implementation of what I've just blabbered above:
//----8<----[ CODE BEGIN ]--------//
void main() {
VESA_STRUCT VesaINFO;
unsigned int *runner;
if(getVesaInfo(&VesaINFO)==1) {
runner = VesaINFO.VideoModes;
while((*runner)!=0xFFFF) {
printf("\n%X",*runner);
runner++;
}
}
}
//----8<----[ CODE END ]--------//
*runner is an unsigned int because that's what VideoModes' data type is in VESA_STRUCT. What we did is "give" runner the starting address of the VideoModes pointer, then "run" through each modes, printing the modes to screen, while (*runner) is not equal to 0xFFFF...:). "printf("\n%X",*runner);" merely prints the value of (*runner) in hexadecimal format. In short, if %d prints to screen a number in base-10 (decimal) format, then %X prints a number in base-16 (hexadecimal) format...:). (Didn't know you could do that did'ya???). Now that we can see what modes are supported by our monitor, let me introduce to you what resolution each mode no. is. Here's a table:
GRAPHICS TEXT
15-bit 7-bit Resolution Colors 15-bit 7-bit Columns Rows
mode mode mode mode
number number number number
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
100h - 640x400 256 108h - 80 60
101h - 640x480 256
109h - 132 25
102h 6Ah 800x600 16 10Ah - 132 43
103h - 800x600 256 10Bh - 132 50
10Ch - 132 60
104h - 1024x768 16
105h - 1024x768 256
106h - 1280x1024 16
107h - 1280x1024 256
10Dh - 320x200 32K (1:5:5:5)
10Eh - 320x200 64K (5:6:5)
10Fh - 320x200 16.8M (8:8:8)
110h - 640x480 32K (1:5:5:5)
111h - 640x480 64K (5:6:5)
112h - 640x480 16.8M (8:8:8)
113h - 800x600 32K (1:5:5:5)
114h - 800x600 64K (5:6:5)
115h - 800x600 16.8M (8:8:8)
116h - 1024x768 32K (1:5:5:5)
117h - 1024x768 64K (5:6:5)
118h - 1024x768 16.8M (8:8:8)
119h - 1280x1024 32K (1:5:5:5)
11Ah - 1280x1024 64K (5:6:5)
11Bh - 1280x1024 16.8M (8:8:8)
The table was copied from the accompanying file VESASP12.TXT. (Note that we won't concern ourselves with the 7-bit mode number, just use the 15-bit equivalent ALWAYS...:) ). Let's choose 640x480 as our target screen resolution. According to the table above, the 15-bit mode number for a resolution of 640x480 256 color is 101h...101h is 0x101...so we set this mode using this number. But wait, getting to this mode is not as simple as mode 13H. We don't just give _AX the value of our mode number then call interrupt 0x10...no, we still have another structure to deal with... this structure describes the mode that we want to get into. So to recap first, what I've told you above so far is to let you know what modes are supported using the VESA information structure loaded from your video card. Now I will show you how to load information about each mode that is supported by your monitor...:). So each mode will have the following structure:
//----8<----[ CODE BEGIN ]--------//
typedef struct {
unsigned int ModeAttribute;
unsigned char WindowA;
unsigned char WindowB;
unsigned int Granularity; // Granularity and WinSize are
unsigned int WinSize; // used to calc bankmult
unsigned int WinAseg;
unsigned int WinBseg;
void (far *bankSwitch)(void);
unsigned int Bytesperline; // Bytes per line
unsigned int Xres; // Width of the mode
unsigned int Yres; // Height of the mode
unsigned char charWidth; // For VESA text-modes only
unsigned char charHeight; // For VESA text-modes only
unsigned char bitplanes;
unsigned char bitsperpixel; // If 8 then we're in 8bit mode
unsigned char banks;
unsigned char memorymodel;
unsigned char banksize;
unsigned char imagepages;
unsigned char reserved1;
unsigned char redmasksize; // For 15/16-bit modes
unsigned char redfieldposition; // For 15/16-bit modes
unsigned char greenmasksize; // For 15/16-bit modes
unsigned char greenfieldposition; // For 15/16-bit modes
unsigned char bluemasksize; // For 15/16-bit modes
unsigned char bluefieldposition; // For 15/16-bit modes
unsigned char rsvdmasksize; // For 15/16-bit modes
unsigned char rsvdfieldposition; // For 15/16-bit modes
unsigned char directcolormode;
unsigned char Reserved[216]; // Reserved
} MODE_STRUCT;
//----8<----[ CODE END ]--------//
OK! All of the information that is of concern to us is commented beside the structure, so I won't bother explaining each field (I don't know all of them anyway...:) ). Here's how to get the mode information:
//----8<----[ CODE BEGIN ]--------//
char getVesaModeInfo(unsigned int mode,
MODE_STRUCT *modeinfo)
{ _AH = 0x4F;
_AL = 0x01; // VBE FUNCTION 01!!!
_CX = mode;
asm les di,[modeinfo]
geninterrupt(0x10);
if(_AX==0x4F) return(1);
return(0);
}
//----8<----[ CODE END ]--------//
Here's how one might use all of the information so far:
//----8<----[ CODE BEGIN ]--------//
void main() {
VESA_STRUCT VesaINFO;
MODE_STRUCT ModeINFO;
unsigned int *runner;
unsigned int found = 0;
if(getVesaInfo(&VesaINFO)==1) {
runner = VesaINFO.VideoModes;
while((*runner)!=0xFFFF) {
if((*runner)==0x101)
found = 1;
runner++;
}
if(found==1) {
getVesaModeInfo(0x101,&modeINFO)
printf("\nxRes:%d",modeINFO.Xres);
printf("\nyRes:%d",modeINFO.Yres);
}
}
}
//----8<----[ CODE END ]--------//
What the above code does is get information on mode 0x101, then output the x and y resolution of the mode. On second thought, I think I'll BRIEFLY discuss Granularity and WinSize (in the MODE_STRUCT). First the WinSize: WinSize is, well, the window size (in KB)...it's usually 64KB in size. The Granularity determines how large a leap the current bank moves to the next bank (more about banks later...). Now the relationship between WinSize and Granularity is that they are used together to let you switch banks correctly...you just say which bank number you want to be in, then multiply that bank number with the quotient below:
bankmult = ModeINFO.WinSize / ModeINFO.Granularity;
(I suggest you don't bother understanding what WinSize and Granularity is...I too got confused when I first read about them...just know that bankmult is the quotient when we divide our Window Size by its Granularity...:) ). I think I should explain now the notion of switching banks. You see, when in mode 13H, we have one whole 64K screen right? Don't believe me? Try multiplying 320x200...it's equal to 64000! A tad bit smaller than 65535 (Note: 64K = 65536...NOT 64000!!!). The remaining 1536 bytes is unseen to you, but is there...trust me! Now, if we were to get into a resolution of, say, 640x480 (8bit mode)...that'd be 307200 bytes! (How did I get that value? Multiply 640 by 480!). So if we have 64KB (65536 bytes) per segment... and our screen is 307200 bytes...how can we access ALL of the 307200 bytes to plot our pixels if we can only reach 65536 bytes at any given time??? We use banks. Banks are just one 64K segments on your screen. Depending on your screen resolution, you have different number of banks to switch to and fro. Here's a sample illustration to clarify things a bit:
A SCREEN IN 640x480-8bit MODE (VESA Mode 0x101): +------------------------------------+ |Bank 0 (64K BIG!) | | | |------------------------------------| |Bank 1 (64K BIG!) | | | |------------------------------------| |Bank 2 (64K BIG!) | | | |------------------------------------| |Bank 3 (64K BIG!) | | | |------------------------------------| |Bank 4 (64K BIG!) | | | +------------------------------------+ How do we know how many banks we have each mode? Here's a way: Since 640x480 = 307200 bytes and each bank is 65535 bytes, then we divide them! This gives us:
307200 / 65535 = 4.687 Banks!
Round up 4.687 to get 5 banks! Note that 4.687 banks means that some bytes on bank #5 are not seen on screen...guess why...:). So how do we switch banks??? There're two ways, I'll show you guys the slow way today...just so you can understand the idea of switching banks first...this one uses interrupts:
//----8<----[ CODE BEGIN ]--------//
void setbank(int bank_num)
{ bank_num = bank_num * bankmult;
if (current_bank == bank_num) return;
_AH = 0x4F;
_AL = 0x05; // VBE FUNCTION 0x05
_BX = 0x0;
_DX = bank_num;
geninterrupt(0x10);
if (_AX==0x4F) // SUCCESS
current_bank = bank_num;
}
//----8<----[ CODE END ]--------//
Notice the bankmult at the top???...:). Anyway, current_bank is a global variable that keeps track which bank you last used, so you don't need to switch bank if you're gonna plot a pixel on the same bank that you last accessed...;). So how do we plot a pixel now that we can get the VESA information, get info on the modes, and know how to switch banks??? Here's a sample code:
//----8<----[ CODE BEGIN ]--------//
void putpixel(int x, int y, unsigned char colornum)
{ long int offset = (long)y*640 + (long)x;
setbank(offset/65536);
memset(MK_FP(0xA000,offset),colornum,1);
}
//----8<----[ CODE END ]--------//
The "640" in the equation:
offset = y*640 + x
is merely the x resolution (the width) of the mode that we want to be in to. If, say, you want to be in 800x600 mode, you change the equation to:
offset = y*800 + x
I hope you get the flow...:). (Oh yeah, the equation is OK only if you're using an 8bit mode, for 15/16bit modes...read up Part 4 of my tutorial...:) ) Also, we divide offset by 65536 when we call setbank_SLOW() because we want to ignore the lower 16 bits of our offset (which is of type long int). Actually, we should be shifting our bits by 16 to the right...but since I haven't introduced shifting bits to you guys yet, I opted to use division instead (slowing down the already so SLOWWWww code...:( ). I'll discuss shifting bits on Part 4. For now, have fun with the information given to you here. Try running the code given in the NOMAD.C Section below, it plots 100,000 random pixels in mode 0x101 (640x480-8bit mode). Whew! I told you this was a bit long...:). Oh yeah...it seems I haven't told you how to get INTO a mode, huh?...well, without further adieu, here is the code (with some initializations):
//----8<----[ CODE BEGIN ]--------//
int setGFXMODE (int mode_num)
{ _AH = 0x4F;
_AL = 0x02; // VBE FUNCTION 0x02
_BX = mode_num;
geninterrupt(0x10);
bankmult = ModeINFO.WinSize / ModeINFO.Granularity;
current_bank = 0; // Default is bank 0
return _AX; // Zero if successful
}
//----8<----[ CODE END ]--------//
"Argh!...TOO much information!...Are you out of your mind?!? How do you expect us to grasp all of this Willie?!? < face turns red from frustration >" - A Reader Drove Mad By My Tutorial Well, I don't...:). Actually, you need not bother yourself much remembering (even knowing!) the various fields in each structure, how to call the interrupts, what to load values to the registers...etc., just copy the code below and use it for a while, then if you have time, look at the accompanying file VESASP12.TXT, then, you'd realize that the trick here is to look and follow the information given on that file...hehe.
I know, I know, it's a bit overwhelming (and confusing too!), I suggest ripping the code off below then use it for a while...then maybe you'd understand how bank switching works in high resolutions...maybe...:). Part 4 will be about getting us into 15/16bit modes... advantages are: we don't have to deal with the palette, and we're not limited to 256 colors! (Nice eh?...). Briefly, in 15/16bit modes, each pixel is a word (that is, one unsigned int). To state what color your pixel would be, you place your Red, Green, and Blue intensity to a corresponding bit place in your color...all of which will be elaborated in Part 4!
|
|