Adding VGA hardware palette support

VGALIB has lead a long and meandering path, development has been an exercise of leveling up each of 3 different environments: PC hardware running DOS, SDL under Linux, and SDL under emscripten. Much of the early development was done in dosbox with the Borland C++ 3.1 IDE, but once I grew past the point of basic C++, using std::string, I had to abandon the BC3.1 IDE and go strictly to makefiles. It was during this time that using the BC3.1 IDE for editing (and it’s weird Brief key sequences) started to become an exercise in patience. I really enjoyed developing on Linux, since that’s what I’ve done for the last 25 years.

Moving to makefiles under DOS was no small feat, the issue is that dosbox is a best effort emulator for running games, but compatibility with Borland C++ 4 and later is sketchy causes crashes. I ended up creating a Windows 2000 VM with Virtualbox to compile VGALIB, but even that acts peculiar and cmd.exe requires End Task. Virtualbox doesn’t have guest additions for any 16bit legacy OSes, so Win2K is the oldest usable environment. My current development environment is Eclipse for the editing (with VIM plugin), Win2K to compile the DOS programs, and dosbox to run them. For Linux and emscripten I use Eclipse with command line make.

The reason my build environment is important to this article has to do with the development target that was most feature complete: SDL running on Linux. Palettized 8bit mode on SDL is really a pain to program to, much more so than straight RGB or RGBA, but it mimics the original IBM VGA 13h mode most closely. I implemented palette support as a matter of requirement when I added SDL support, since there there is no default palette. Until this time I hadn’t added hardware palette support to the VGA driver, I simply relied on the default VGA palette (which is fine for most things).

Default VGA palette no more! I got around to adding palette loading to the VGA driver, again based on code I wrote originally > 25 years ago in assembly language. My first programming endeavors were typically the “bounce a ball around the screen” type. A typical example of this type of genre is shown to the right. Wavyrain reprogrammed the VGA palette into 64 shades of gray, red, green, and blue, then plotted a random scatter of pixels around a vector. This formed the basis of the palette update routine.

Adding VGA palette support wasn’t as straightforward as SDL, SDL will accept a palette of 256 RGB tuples, but VGA doesn’t quite work that way. VGA implements color through a RAMDAC, this is a 3 channel DAC with a RAM lookup table that stores an intensity for each color in a 256 entry table. The VGA RAMDAC implements 18bit color, that’s why VGA has 262,144 possible colors, it’s 6x6x6 bits. The palettes we work with today, the palettes stored in images, and generally any indexed source, have RGB tuples with intensity values between 0 and 255, VGA needs a value between 0 and 63.

The palettes included in VGALIB are largely based on GIMP palettes, but all have 8:8:8 RGB tuples to represent the colors. Strictly speaking, these colors won’t translate onto a real VGA screen, they are artificial constructs which map to a truecolor display (all modern displays are 8:8:8 truecolor). When you load a palette into the VGA RAMDAC, you must shift the color values 2 bits to the right, quantizing the potential colorspace from 16,777,216 colors down to just 262,144 colors. You are throwing away the lower 2 bits of the channel, but the end result is actually 64 times smaller (26) in whole.

These images illustrate the difference between an 18bit colorspace and 24bit colorspace:

18bit color space

It may be difficult for you to see, but if you look closely at the darker areas of green (we’re more sensitive to green, so a reduction in green gamut will be most noticeable), you can see discrete steps of color.

24bit color space

In case your curious what goes into setting a palette entry, here’s one of the routines. I didn’t end up using this routine ultimately because the VGA palette registers are designed to be bulk loaded, loading registers one at a time is slower and wasteful. This is code I wrote > 25 years ago and I just scraped from an old program:

asm {
  mov dx, VGA_PAL_REG
    mov al, index
    out dx, al

    inc dx
    mov al,r
    shr al,2
    out dx, al
    mov al, g
    shr al,2
    out dx, al
    mov al, b
    shr al,2
    out dx, al
  }

The final routine to load palettes into the VGA RAMDAC ended up being quite simple and short:

void vga::setpalentries(palette::pal_t *pal, int palette_entries)
{
#ifdef __BORLANDC__
    outportb(VGA_PAL_MASK,0xff);		// update all palette indices
    outportb(VGA_PAL_REG,0);			// start at index 0
    for(int i=0;i<_palette_size;i++) {	// output quantized RGB tuple
        outportb(VGA_PAL_DATA,pal[i].r>>2);
        outportb(VGA_PAL_DATA,pal[i].g>>2);
        outportb(VGA_PAL_DATA,pal[i].b>>2);
    }
#endif
}

This post ended up being a long winded way of introducing a relatively simple and obscure feature implemented in 10 lines of code, but perhaps the story was interesting along the way.

Leave a Reply

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