::[ PART 04 - 15/16 BIT SVGA 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!

 
::[ INTRODUCTION ]::
15/16bit modes offer better (albeit a tad slower) graphical screen output. The greatest advantage higher bit modes have over their 8bit mode cousin is the number of colors it can render.

Now comparing the three bit-depths:

  • 8bit modes - 256 colors
  • 15bit modes - 32,768 colors
  • 16bit modes - 65,536 colors
As you can see, with colors ranging to the thousands, we can show a far better detail of whatever it is we want to show...:).

It is assumed that you have read Part 3 of my tutorial, because I will be using some infos there, here! (Actually, I will be reusing ALL codes there, 'cept for the part where we plot a pixel...:) )

Oh yeah, 15/16bit modes are also called "highcolor modes". As opposed to 24/32bit modes which are called "truecolor modes"...:).

SideNote: We CAN get into 24/32bit modes using VESA VBE 1.2, but I'm only going to discuss 15/16bit modes, sorry!

 
::[ TUTORIAL ]::
When learning how to plot pixels on highcolor (i.e. 15/16bit) modes, we must first know about shifting bits and how to pack bits onto an unsigned int.

"Huh Willie? Could you run that by me again? 'cause you're speaking like you're from another planet or somethin'. < eyes stares blankly at Willie >" - Perplexedly Disoriented Reader

Actually, this is the reason why I divided discussion of 8bit modes with highcolor modes...:).

Continuing...

A bit is either a 0 or 1. Most of you guys must know this already, but it is important one keep this in mind...:).

Now, when we say 8bit modes, we mean that we are in a mode where each pixel can be any of the 256 colors it is allowed to. How do we know that? Because an 8bit mode has each pixel being defined by eight (8) bits! ("Well duh!") Here's an illustration:

      00000001

If we have eight thingi-ma-dus capable of having a value of 0s or 1s, how many possible combinations are there??? Well, we solve it through: 2(raised to 8), which is equal to 256!

(Why 2(raised to 8) you ask? Why not, say, 5(raised to 8)?!? What's so special about '2'?!? Well, we can't use '5' here because we're using bits here, and bits can only use '2' numbers, a 0 or 1...:) ).

Now that was how a pixel in 8bit mode would look like...but how about 15/16bit modes? Well, instead of 8 bits, we have 15 bits per pixel for 15bit modes, and 16 bits per pixel for 16bit modes! (Rather obvious...:) )

Here's another illustration (15 bits):

      000100010001001

I won't bother giving an illustration for 16 bits...it's too darn obvious...;).

Now we discuss about shifting bits.

When we shift bits to the right, we are like dividing them with the magnitude of 2(raised to n), as opposed to shifting bits to the left, to which, we are multiplying the number with 2(raised to n).

For example, if we want to shift 3 bits to the right the number 32,700:

  Visually:
  
      0111111110111100     -> 32,700 in bits

      0011111111011110     -> 1 right shift
      0001111111101111     -> 2 right shifts
      0000111111110111     -> 3 right shifts

      0000111111110111     =  4,087

  Mathematically:

      -> 32,700 / [2(raised to 3)]
      -> 32,700 / 8
      -> 4,087.5
      
      (because we're using integers)
      -> 4,087.5 becomes 
      -> 4,087

As you can see, both are essentially the same. So why bother with all these stuffs? Why not just divide/multiply a number directly? Well, my dear soon-to-be-enlightened reader, when rendering graphics, speed is veeerrry important! And multiplications and divisions are WAAAYYYYyyy SLOOWWWwww compared to shifts.

Let's recap as early as now:

  • Shifting n times to the right is equal to DIVIDING a number by a magnitude of 2(raised to n)
  • Shifting n times to the left is equal to MULTIPLYING a number by a magnitude of 2(raised to n)
"But I have the latest Pentium processor, wouldn't my hardware speed the code up so I won't have to bother with all these shifts?" - Inquiring Reader

Well, you may argue that everyone has a Pentium these days...Pentiums or not, shifting bits are ALWAYS faster than doing multiplications and divisions!

Now we discuss about packing Red, Green, and Blue intensity values on to an unsigned int (which is 16 bits long...).

When we pack RGB values on to an unsigned int, we need to know first WHICH bit position we want to place each RGB value.

Let me show you again the (incomplete) table that shows what resolution each mode is:

   15-bit   7-bit    Resolution   Colors   
   mode     mode                           
   number   number                         
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   
   0x10D     -        320x200      32K   (1:5:5:5)
   0x10E     -        320x200      64K   (5:6:5)
   0x10F     -        320x200      16.8M (8:8:8)
   0x110     -        640x480      32K   (1:5:5:5)
   0x111     -        640x480      64K   (5:6:5)
   0x112     -        640x480      16.8M (8:8:8)
   0x113     -        800x600      32K   (1:5:5:5)
   0x114     -        800x600      64K   (5:6:5)
   0x115     -        800x600      16.8M (8:8:8)
   0x116     -        1024x768     32K   (1:5:5:5)
   0x117     -        1024x768     64K   (5:6:5)
   0x118     -        1024x768     16.8M (8:8:8)
   0x119     -        1280x1024    32K   (1:5:5:5)
   0x11A     -        1280x1024    64K   (5:6:5)
   0x11B     -        1280x1024    16.8M (8:8:8)

Notice that for those modes with colors that are greater than or equal to 32K (32,768 colors), there're stuffs written beside it like 1:5:5:5, or 5:6:5...they are the bit position that I was talking about earlier.

So a 15bit mode would have 1:5:5:5 as its bit positions...here's an illustration:

  bit pos:    15|14 13 12 11 10|9  8  7  6  5 |4  3  2  1  0
              +-|--------------|--------------|------------+
              |X|              |              |            |
              |X|      R       |      G       |      B     |
              |X|______________|______________|____________|

As you can see the first 5 bits (0-4) are stored with the Blue intensity value, the next 5 bits (5-9) with the Green intensity value, and the last 5 bits (10-14) with the Red intensity value. The last bit (15) is an unused bit, it's called an "Alpha Bit".

Similarly, if we're in 16bit modes, we use the ratio R:G:B = 5:6:5...:).

(Some of you might notice it by now that a 15bit mode is actually a 16bit mode with an "Alpha Bit"...:) ).

Oh yeah, we STILL define our RGB intensity values through the range 0-63...which is 6bits (i.e. 2(raised to 6) is 64!).

"Uhh, could you give me some code for that? 'Cause I dunno how to implement what you just blabbered above..." - Confused Reader

Of course! Here's how to pack RGB values on to an unsigned int for 15bit modes (Again, 15bit modes has a ratio of 1:5:5:5):

  //----8<----[ CODE BEGIN ]--------//
    unsigned int RGB15bit(unsigned char R,
                          unsigned char G,
                          unsigned char B)
    { unsigned int Red,Green,Blue;
      unsigned int Final_Color = 0;

      R   = R >> 1;       // Shift right 1 bit
                          // to make this
                          // 6bit value into 
                          // 5bits

      Red = R << 10;      // Shift left 10 bits
                          // 'cause that's
                          // the starting bit 
                          // position of Red.
                          // See my sample
                          // illustration above

      G     = G >> 1;       
      Green = G << 5;

      B     = B >> 1;
      Blue  = B << 0;
      Final_Color = Red + Green + Blue;
    
      return(Final_Color);
    }
  //----8<----[ CODE END   ]--------//

Here's a code for packing 16 bits (5:6:5):

  //----8<----[ CODE BEGIN ]--------//
    unsigned int RGB16bit(unsigned char R,
                          unsigned char G,
                          unsigned char B)
    { unsigned int Red,Green,Blue;
      unsigned int Final_Color = 0;

      R     = R >> 1;      
      Red   = R << 11;

      Green = G << 5;

      B     = B >> 1;
      Blue  = B << 0;     
      Final_Color = Red + Green + Blue;
    
      return(Final_Color);
    }
  //----8<----[ CODE END   ]--------//

Notice we didn't "shrink" our G bit to 5 bits because our ratio asked that we let G have a size of 6 bits (5:6:5, remember?...:) ).

Ok, we're nearly done!

Notice first that we returned an unsigned int for our "bit-packing" functions above...that's because the unsigned int that is returned will be used in our plotting of pixels.

Let's create a new function for plotting pixels:

  //----8<----[ CODE BEGIN ]--------//
    void putpixel(int x, int y, unsigned int color)
    { long int offset = (long)y*ModeINFO.Bytesperline + x + x;

      setbank(offset >> 16);
      poke(0xA000, offset, color);
    }
  //----8<----[ CODE END   ]--------//

The parameter color will take the value you get after you pack your bits onto an unsigned int.

The poke() function "writes" an integer to the given segment and offset. It looks like this:

      poke(segment, offset, integer_value);

poke()'s almost the same with memset(), except memset writes bytes...poke writes integers.

We CAN still limit ourselves to using memset()...how? Well an integer is 16bits long, a char is 8bits long, so we "give" the first char variable bits 0-7 of our integer then "give" the second char variable bits 8-15. Then we plot pixels via memset() using the first char variable first, add one to the offset, then plot the next char variable...this way we won't be using poke(), which is not one of the functions in the ANSI-C Standard (I think), which makes porting codes harder than necessary...:).

Oh yeah, if you're asking how do we "give" bits to and fro...well, we use what was discussed above...shifts!...:).

Here's how we might go about plotting pixels using purely shifts and memset()s:

  //----8<----[ CODE BEGIN ]--------//
    void putpixel(int x, int y, unsigned int color)
    { long int offset = (long)y*ModeINFO.Bytesperline + x + x;
      unsigned int    temp = color << 8;
      unsigned char   ch1;
      unsigned char   ch2;

      ch1  = temp  >> 8;    // "give" bits 0-7
      ch2  = color >> 8;    // "give" bits 8-15

      setbank(offset >> 16);
      memset(MK_FP(0xA000,offset),   ch1, 1);
      memset(MK_FP(0xA000,offset+1), ch2, 1);
    }
  //----8<----[ CODE END   ]--------//

Nice huh?...:).

Anyway...

Now remember in 8bit modes we calculated our offset as:

      offset = y*(x-resolution) + x

The reason we could do this is because each pixel was 1 byte (i.e. 8 bits = 1 byte), so we just plug in the x-resolution of our mode in the equation...:).

Well things work differently for highcolor modes...each pixel is no longer 1 byte (i.e. each pixel is 16 bits, which is 2 bytes!), so we use information retrieved from our MODE_STRUCT variable and use the field Bytesperline...:).

(Still not clear? Well, let's take mode 0x101, that mode has a resolution of 640x480-8bit, since each pixel of an 8bit mode is 1 byte, we're saying that the number of bytes (i.e. pixels) in one line on the screen is 640! But now that we're dealing with 15/16bit modes, each pixel is 2 bytes, hence we MUST use the field Bytesperline from MODE_STRUCT...:) )

Oh yeah, those of you wond'rin' why we added x twice to the offset. Since we have 2 bytes per pixel here, we should've multiplied x with 2, and the equation would look like:

    offset = y*ModeINFO.Bytesperline + x*2;

But as you all should know by now, multiplication is SLOW, so we just added x twice...same effect!

(And yes, we could also just shift x once to the left...:) )

OK! We're done! Now relax, try running the given code below (within the NOMAD.C Section) to see what you've just learned!

Pat yourself on the back 'cause you can now do something only a handful of people can do!

(Note: My sample code below plots 100,000 random pixels in 640x480-15bit mode...For 16bit mode, replace the RGB15bit() with RGB16bit() given above, then choose a 16bit mode that you like).

 
::[ 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:

Whew! This was a bit hard to explain...I hope I explained it well enough to make sense...:). If you still don't get what I've been blabbering here, well just rip the code below and use it for a while...then read the codes a few times, then maybe, just maybe, you'll get the idea...:).

Oh yeah, the tutorial after this will use VESA mode 0x110 (that's a 640x480-15bit mode). This is for you to get "acquainted" with VBE more and get more comfortable with it.

If your video card doesn't support VESA mode 0x110, simply choose a mode with a different bit depth, but please try to stay with a 640x480 resolution...anyway, I doubt if anyone of you still have a video card unable of showing stuffs in that resolution...:).

Part 5 we learn how to draw a line...it might seem simple to you, but again, in computer graphics, SPEED IS CRITICAL! You may know tons of maths about how to draw a line on a two dimensional surface...but you also need to know HOW to minimize calculations to speed up your code...:).

Briefly, we'll learn about how sine and cosine could be used to draw a line (slow, but it works!)...or we may use the very well-known Bresenham's Line Algorithm (quite fast indeed!)...all will be discussed in Part 5.

 
::[ NOMAD.C ]::

  #include <DOS.H>
  #include <MEM.H>
  #include <STDLIB.H>
  #include <STDIO.H>

  //--[ 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);
  }

  //--[ PACKS RGB TO AN UNSIGNED INT ]------//
  unsigned int RGB15bit(unsigned char R,
                        unsigned char G,
                        unsigned char B)
  { unsigned int Red,Green,Blue;
    unsigned int Final_Color = 0;

    R     = R >> 1; 
    Red   = R << 10;

    G     = G >> 1;
    Green = G << 5;

    B     = B >> 1;
    Blue  = B << 0;     
    Final_Color = Final_Color + Red + Green + Blue;
    
    return(Final_Color);
  }

  //--[ PLOTS PIXEL DIRECTLY TO MEMORY ]---//
  void putpixel(int x, int y, unsigned int color)
  { long int offset = (long)y*ModeINFO.Bytesperline + x + x;
    unsigned int    temp = color << 8;
    unsigned char   ch1;
    unsigned char   ch2;

    ch1  = temp  >> 8;
    ch2  = color >> 8;

    setbank(offset >> 16);
    memset(MK_FP(0xA000,offset),   ch1, 1);
    memset(MK_FP(0xA000,offset+1), ch2, 1);
  }

  void main() {
    unsigned int *runner;
    unsigned int found = 0;
    long int     ctr;

    setTXTMODE();

    printf("\n\nNOMAD GRAPHICS TUTORIAL - [ PART 4 ]\n");
    printf("\n  This is a sample code included with GFXTUT4.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)==0x110) found = 1;
	  runner++;
      }

      if(found==1) {
	  getVesaModeInfo(0x110,&ModeINFO);
  	  setGFXMODE(0x110);
	  for(ctr=0; ctr<100000; ctr++)
	    putpixel(random(640), random(480), 
                     RGB15bit(random(63),random(63),random(63)));
  
        setTXTMODE();

        printf("\n\nNOMAD GRAPHICS TUTORIAL - [ PART 4 ]\n");
        printf("\n  That was 100000 random pixels plotted using");
        printf("\n  mode 0x110 (640x480-15bit)\n");
        printf("\n  That's all for now...:)\n");
        printf("\n  email: [ willietang@hehe.com ]\n");
      } else
      { setTXTMODE();

        printf("\n\nNOMAD GRAPHICS TUTORIAL - [ PART 4 ]\n");
        printf("\n  SORRY! It seems your monitor doesn't support the");
        printf("\n  screen mode 640x480-15bit through VESA.\n");
        printf("\n  That's all for now...:)\n");
        printf("\n  email: [ willietang@hehe.com ]\n");
      }
    }
  }
This tutorial was written on: 10.22.2001 | Updated: 11.14.2002
:.Site.:.Menu.:
:// .site..news.
:// .tutorials..1.
:// .tutorials..2.
:// .projects.
:// .forums.
:// .gbook.
:// .links.