Reading and Writing the DAC Palette

Intro

This tutorial will help you gain a higher understanding of the DAC color table, and how that is used by the VGA card to get the proper color onto the screen. Lets not rush into anything too quickly. First off, lets get an understanding of how colors are handled and used on the VGA card.

Color

Every visible color can be split up into 3 intensities of Red, Green, and Blue. This is pretty much common knowledge. The big mystery is how this is represented by the computer. Our eyes do all the interpretation and communication to our brains to accomplish our vision, but what about computers? The answer is quite simple.

For the remainder of this section, I refer to color as one of the 3 primary colors, Red, Green or Blue.

Each color is represented by a number. That number’s maximum value represents the brightest that color could ever get. An intensity of 0 means that it is black or not visible at all. The interesting thing about this method is that the precision of the color is completely dependant upon how high the number can get. This tells us how many intensities of that color are possible, while in real life we have an unlimited number of intensities. If we use a 2 bit number to represent our intensities, we would have to split up all possible intensities of that color into 4 steps. If we wanted to represent a real color, we would only come up with a gross approximation. What if we used a 4 bit number. Then we would have to split it up into 16 steps. This still isn’t acceptable! This is where 8 bit color comes into play. Here we get 256 intensities for each color. Ironically, this is also the standard number of colors available in a VGA mode. Unfortunately, we only end up with 64 shades of each color, instead of 256.Why are we limited to only 256 colors and 64 shades of each intensity? That’s because of the DAC Color Table.

DAC Color Table

The DAC color table provides a very quick way to access a color by its index, instead of always providing the RGB intensities whenever the color needs to be put on the screen. The table has 256 indexes that correspond to our 256 colors available at one time. Each index stores the 8 bit value corresponding to each RGB intensity. A crappy thing about each index, is that only the lower 6 bits are used, forcing only 64 shades of each intensity. To display our color after we fill in the table with the 256 colors we want, we simply write the index number to video ram. Video ram uses our index to go into the table and use the 3 8-bit intensities to display our color correctly.

Palette Registers

Palette Mask 0x3c6
Register Read 0x3c7
Register Write 0x3c8
Data Port 0x3c9

Setting the DAC Color Table

The code to actually set the color table is REALLY Simple. Here’s my function ripped from my Video class, it will work with DJGPP(32bit) and C++(16bit).

C++ Functions for Writing

  1. #define uchar unsigned char
  2.  
  3. void Video::Write_DAC_C_Palette(uchar StartColor,uchar NumOfColors,uchar *Palette)
  4. {
  5.     outp(0x03C6,0xff);          //Mask all registers so we can update any of them
  6.     outp(0x03C8,StartColor);    //1st color to input!
  7.     for(short i=0; i<NumOfColors*3; i++ )
  8.     {
  9.         outp(0x03C9,Palette[i]<<2);
  10.     }
  11. }
  12.  
  13. void Video::Set_DAC_C(uchar Color,uchar Red, uchar Green, uchar Blue)
  14. {
  15.     outp(0x03C6,0xff);     //Mask all registers even though we only need 1 of them
  16.     outp(0x03C8,Color);    //Color to set
  17.     outp(0x03C9,Red);
  18.     outp(0x03C9,Green);
  19.     outp(0x03C9,Blue);
  20. }

That's right. These functions give us absolute power! The fist function sets the whole palette to the colors we want. The only stipulation is that we have our buffer (Palette) filled in with our colors. We send a value of 0xff to the mask port so that we can update all registers. We then tell it that we are going to start giving it colors starting with index 0. From then on, we simply communicate with the Data Port, giving it our intensities. One point worth mentioning is that these functions only use the lower 6 bits of the data that is sent to it. The DAC only uses 6 of the 8. If we are reading from and writing to the DAC using the functions listed in this tutorial, this won't be a problem since we will actually be reading 6 bit values. While this function is running, any colors that are on the screen that have different RGB values will immediately turn into the new colors. This may or may not be what you want, so be careful! The second functions does close to the same thing except it just modifies a single color! I hope this clears up some of the mystery.

BIOS Functions for Writing

  1. <i>#define uchar unsigned char</i>
  2.  
  3. void Video::Set_DAC(uchar Register,uchar Red,uchar Green,uchar Blue)
  4. {
  5.     union REGS regs;
  6.     regs.h.ah=0x10;
  7.     regs.h.al=0x10;
  8.     regs.x.bx=Register;
  9.     regs.h.ch=Green;
  10.     regs.h.cl=Blue;
  11.     regs.h.dh=Red;
  12.     int86(0x10,&regs,&regs);
  13. }
  14.  
  15. void Video::Write_DAC_Palette(uchar StartColor,uchar NumberOfColors,uchar* Palette)
  16. {
  17.     REGPACK regs;
  18.     regs.r_ax=0x1012;
  19.     regs.r_bx=StartColor;
  20.     regs.r_cx=NumberOfColors;
  21.     regs.r_es=FP_SEG(Palette);
  22.     regs.r_dx=FP_OFF(Palette);
  23.     intr(0x10,&regs);
  24. }

The first function is to be used to set a single color. If you want to be cool and set all of the color palette at once, use the second. The buffer that you pass it must be 768 unsigned characters. It must represent Red,Green,Blue,Red,Green,Blue etc.

NOTE: The Second function will not work under DJGPP because it is a 32 bit compiler, meaning it doesn't address memory in a SEG:OFF scheme. Modify the SetPalette function from above instead!

Reading the DAC Color Table

You'll find out that reading the colors from the DAC Color Palette is very very close to writing to it! Since we're at it, i thought you might enjoy a little function that loads the palette from the DAC color table, and it's in inline assembly. What else is there ?!

Inline Assembly Functions for Reading

  1. void Video::Load_DAC_Palette()
  2. {
  3.     address=(long)&Palette;
  4.     asm ("
  5.         movl $768,%%ecx    <font color="black">// cx is our counter:  Section #1</font>
  6.         movw $0x03c6,%%dx   <font color="black">// dx is our port</font>
  7.         movw $0xff,%%eax   <font color="black">// ax is the input number to go to port</font>
  8.         outw %%ax,%%dx
  9.         
  10.         movw $0,%%eax    <font color="black">//Section #2</font>
  11.         movw $0x03c7,%%dx    <font color="black">//3c7 to read the contents</font>
  12.         outw %%ax,%%dx
  13.         
  14.         movw $0x03c9,%%dx    <font color="black">// Section #3</font>
  15.         movl _address,%%ebx
  16.    
  17.         LoopStart:
  18.         inb %%dx,%%eax
  19.         movl %%eax,(%%ebx)
  20.         addb $1,%%bx
  21.         Loop LoopStart"
  22.         :
  23.         :
  24.         :"%ecx","%eax","%dx","%ebx");
  25. }

Now all this is really doing is telling the read port in the DAC table that we want to read from it. And we start to read away! In the first section we set cx to the number of repetitions we are going to have our loop iterate. Its 768 since there are 256 colors, and a Red, Green, and Blue component for each. We also set the mask to 0xff so that all colors can be accessed. In section #2, we tell it that we want it to start giving us the colors starting with color 0. In section #3, we set the location of where we are going to be writing the info, and set the port to 0x03c9, because that's where the data is going to show up! In the loop we read from the data port once, and increment our pointer. The only crappy thing about this approach is that the palette must be global since inline asm doesn't use the stack. I'll get over it :) One other item to mention is that the data read from the DAC is only 6 bits, so if you intend to write it after reading it, don't do any bit shifting because it is already aligned!

NOTE: The above inline assembly code will only work under DJGPP. With a little modification it can be ported to normal inline assembly under normal C++(16).

C++ Functions for Reading

  1. #define uchar unsigned char
  2.  
  3. void Video::Read_DAC_C_Palette(uchar StartColor,uchar NumberOfColors,uchar* Palette);
  4. {
  5.     outp(0x03C6,0xff); <font color="black">         //Mask all registers so we can update any of them</font>
  6.     outp(0x03C7,StartColor); <font color="black">   //1st color to read!</font>
  7.     for(short i=0; i<NumOfColors*3; i++ )
  8.     {
  9.         Palette[i]=inp(0x03C9);
  10.     }
  11. }
  12.  
  13. void Video::Get_DAC_C(uchar Color,uchar& Red,uchar& Green,uchar& Blue)
  14. {
  15.     outp(0x03c6,0xff);
  16.     outp(0x03c7,Color);
  17.     Red      = inp(0x03c9);
  18.     Green = inp(0x03c9);
  19.     Blue  = inp(0x03c9);
  20. }

Notice that there are very few changes from the set of writing functions to convert them into reading functions. Instead of sending our start color to 0x03c8 (DAC Write Port), we're sending it to 0x03c7 (DAC Read Port). From there we can do inports from 0x03c9 (DAC Data Port) again!

BIOS Functions for Reading

  1. #define uchar unsigned char
  2.  
  3. void Video::Get_DAC(uchar Register,uchar& Red,uchar& Green,uchar& Blue)
  4. {
  5.     union REGS regs;
  6.     regs.h.ah=0x10;
  7.     regs.h.al=0x15;
  8.     regs.x.bx=Register;
  9.     int86(0x10,&regs,&regs);
  10.     Red      = regs.h.dh;
  11.     Green = regs.h.ch;
  12.     Blue  = regs.h.cl;
  13. }
  14.  
  15. void Video::Load_DAC_Palette(uchar StartColor,uchar NumberOfColors, uchar* Palette)
  16. {
  17.     REGPACK regs;
  18.     regs.r_ax=0x1017;
  19.     regs.r_bx=StartColor;
  20.     regs.r_cx=NumberOfColors;
  21.     regs.r_es=FP_SEG(Palette);
  22.     regs.r_dx=FP_OFF(Palette);
  23.     intr(0x10,&regs);
  24. }

The first can be used to load the R,G,B values of a particular color, while the second is for loading the whole palette into a buffer of unsigned characters.

NOTE: As in the first section, the second BIOS function will not work under DJGPP because it doesn't use the SEG:OFF scheme for addressing memory.

Many many thanks to Nutty for helping me learn the ways of DJGPP inline assembly, and the great suggestions!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>