[ Nomad Programming, Graphics, and Algorithms Tutorial ] [ Compiler : Borland C++ 3.0/3.1 ] [ Part 3 - High Resolution Modes ] [ http://nomad.openglforums.com ] <<------------------------------------------------------>> [ 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! [ INTRODUCTION ] High resolution graphics...let's admit it, mode 13H just doesn't cut it out in today's standard. I mean, with the speed at which technology is pacing itself, a resolution of 320x200 would look pale compared to its higher resolution cousins. 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). [ TUTORIAL ] It was decided that a brief introduction about how numbers are dealt with be done first: A char has a range of: -128 to 127 An int has a range of: -32,768 to 32,767 So what does this mean? For example, if we were to ask the user to input a number and if the user were to input 32900, and the data type of our receiving variable is an int, then I'll bet that the variable didn't store the number properly (i.e. 32900 is GREATER than 32767, which is the greatest value that an int could store...). 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: unsigned char - its range is 0 to 255 unsigned int - its range is 0 to 65,535 Obviously, the difference is that unsigned data types can't take negative numbers...but the range of numbers that it can store is the same. 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: o The array VESAsignature[4] is actually a string that MUST form "VESA". o VersionMinor and Version Major is, well, the version number of VESA that is installed on your monitor. (i.e. if the VESA version installed on your monitor is 1.2, then VersionMinor should have the value 2, and VersionMajor the value 1). o The OEM pointer is a string that stores what video card is installed on your computer. o Don't mind the Capabilities field...it doesn't concern us much...:). o The VideoModes pointer points to a list of graphic modes that your screen can be set to. The list is terminated by the value 0xFFFF. (Also, each mode is one unsigned int, so you read one unsigned int at a time through this pointer, I'll show you how later...:) ). o TotalMemory shows how many 64K blocks of RAM is existing on your video card. o The Reserved[235] array, well, is reserved! So how do we get the VESA information of our monitor??? We use VBE function 00. Here's the code: //----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. [ ENDING ] And that's that! Not hard eh? If there is anything that's not clear to you, your mind, or to that part of you that lets you comprehend the things here, you may email me at (according to priority): willietang@hehe.com or willietang@yahoo.com 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! [ NOMAD.C ] #include #include #include #include //--[ VESA STRUCTURE ]---------------------// 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; //--[ MODE STRUCTURE ]---------------------// 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; // Window A's starting segment unsigned int WinBseg; // Window B's starting segment 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 256 colors 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; static int current_bank; static int bankmult; VESA_STRUCT VesaINFO; MODE_STRUCT ModeINFO; //--[ SETS SCREEN TO GIVEN MODE ]---------// int setGFXMODE(int mode_num) { _AH = 0x4F; _AL = 0x02; // VBE FUNCTION 0x02!!! _BX = mode_num; geninterrupt(0x10); //--[ INITIALIZE GLOBAL VARIABLES ]--// current_bank = 0; bankmult = ModeINFO.WinSize / ModeINFO.Granularity; return _AX; // zero if successful } //--[ SETS SCREEN TO TEXT MODE ]----------// void setTXTMODE() { _AX = 0x0003; geninterrupt(0x10); } //--[ SETS BANK USING INTERRUPTS ]--------// 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; } //--[ GETS VESA INFO ]--------------------// 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); } //--[ GETS MODE INFO ]--------------------// 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); } //--[ PLOTS PIXEL DIRECTLY TO MEMORY ]---// void putpixel(int x, int y, unsigned char colornum) { long int offset = (long)y*640 + (long)x; setbank(offset/65535); memset(MK_FP(0xA000,offset), colornum, 1); } void main() { unsigned int *runner; unsigned int found = 0; long int ctr; setTXTMODE(); printf("\n\nNOMAD GRAPHICS TUTORIAL - [ PART 3 ]\n"); printf("\n This is a sample code included with GFXTUT3.TXT"); printf("\n to show how to implement the sample codes that"); printf("\n were given in the said tutorial.\n"); printf("\n [ Press Any Key to Begin ]"); (void)getch(); if(getVesaInfo(&VesaINFO)==1) { runner = VesaINFO.VideoModes; while((*runner)!=0xFFFF) { if((*runner)==0x101) found = 1; runner++; } if(found==1) { getVesaModeInfo(0x101,&ModeINFO); setGFXMODE(0x101); for(ctr=0; ctr<100000; ctr++) putpixel(random(640), random(480), random(256)); setTXTMODE(); printf("\n\nNOMAD GRAPHICS TUTORIAL - [ PART 3 ]\n"); printf("\n That was 100000 random pixels plotted using"); printf("\n the direct-memory plotting method, all of which"); printf("\n used (the SLOW) interrupt 0x10 to switch banks"); printf("\n to and fro.\n"); printf("\n That's all for now. See you in GFXTUT4.TXT!...:)\n"); printf("\n email: [ willietang@hehe.com ]\n"); } else { setTXTMODE(); printf("\n\nNOMAD GRAPHICS TUTORIAL - [ PART 3 ]\n"); printf("\n SORRY! It seems your monitor doesn't support the"); printf("\n screen mode 640x480-8bit through VESA.\n"); printf("\n That's all for now. See you in GFXTUT4.TXT!...:)\n"); printf("\n email: [ willietang@hehe.com ]\n"); } } } <<------------------------------------------------------>> [ Written By: Willie Tang ] [ Written On: 10.14.2001 ] [ Updated On: 01.07.2003 ]