RasterCore

The VGA DAC: The 18-Bit Lie

:: SysOp

256 Colors out of… 262,144?

When we talk about “retro” PC graphics, we usually refer to VGA Mode 13h: 320x200 resolution with 256 colors. It was the standard for Doom, Wolfenstein 3D, Command & Conquer, and thousands of other titles. But have you ever wondered which 256 colors?

Unlike modern “True Color” (24-bit or 32-bit) displays where you specify exact Red, Green, and Blue values for every pixel, VGA uses an Indexed Color model. The video memory (VRAM) at 0xA0000 doesn’t store colors. It stores pointers.

The Palette Architecture

The VGA card has a specialized component called the DAC (Digital-to-Analog Converter). Inside this DAC is a small chunk of Static RAM (SRAM) capable of holding 256 entries. Each entry consists of three values:

  1. Red Intensity (6 bits)
  2. Green Intensity (6 bits)
  3. Blue Intensity (6 bits)

When the VGA hardware renders a pixel, it looks at the byte in video memory (say, 0x0F or 15). It goes to slot 15 of the DAC Palette, retrieves the RGB triplet stored there, and converts those digital values into analog voltages sent down the VGA cable to the CRT monitor.

The 6-Bit Limitation

A common source of confusion for modern programmers exploring VGA is the bit-depth. Standard RGB is 8 bits per channel (0-255). VGA RGB is 6 bits per channel (0-63).

If you try to set a VGA color to bright white using (255, 255, 255), the DAC will ignore the top two bits of each byte. Effectively, 255 becomes 63. The total color space is calculated as $2^6 \times 2^6 \times 2^6$, which equals 262,144 possible colors. From this vast selection, you must choose your “best” 256 colors to define the look of your game.

Programming the DAC

The DAC is accessed via standard x86 I/O ports. It’s a state-machine based protocol.

  1. Port 0x3C8 (DAC Write Address): You send the index (0-255) you want to start writing to.
  2. Port 0x3C9 (DAC Data): You write the Red, then Green, then Blue values.
    • Auto-Increment: After writing the third byte (Blue), the internal index automatically increments to the next color. This allows you to upload the entire 256-color palette by sending 1 byte to 0x3C8 and then 768 bytes to 0x3C9.

Code Example: A Simple Palette Fade

This C snippet (for DOS) fades the screen to black by slowly reducing the RGB values in the palette.

// DOS C Example (Turbo C / DJGPP)
#include <dos.h>

typedef struct {
    unsigned char r, g, b;
} Color;

Color palette[256];

void read_palette() {
    outp(0x3C7, 0); // Start reading from index 0
    for(int i=0; i<256; i++) {
        palette[i].r = inp(0x3C9);
        palette[i].g = inp(0x3C9);
        palette[i].b = inp(0x3C9);
    }
}

void fade_to_black() {
    read_palette();
    
    for(int step=0; step<64; step++) {
        // Wait for Vertical Retrace to avoid "snow" or tearing
        while(inp(0x3DA) & 8);
        while(!(inp(0x3DA) & 8));

        outp(0x3C8, 0); // Start writing at index 0
        
        for(int i=0; i<256; i++) {
            if(palette[i].r > 0) palette[i].r--;
            if(palette[i].g > 0) palette[i].g--;
            if(palette[i].b > 0) palette[i].b--;
            
            outp(0x3C9, palette[i].r);
            outp(0x3C9, palette[i].g);
            outp(0x3C9, palette[i].b);
        }
    }
}

Palette Cycling: The “Free” Animation

The separation of “Pixel Data” and “Color Data” allowed for a powerful optimization technique called Palette Cycling (or Color Cycling).

Imagine you want to draw a flowing waterfall. Method A (The hard way): Redraw every pixel of the water every frame with a new color. This moves thousands of bytes and is slow. Method B (The smart way): Draw the water once using color indices 100, 101, 102, and 103. In the next frame, change the palette so that index 100 becomes the color of 103, 101 becomes 100, etc.

You only change a few bytes in the DAC ports, but on screen, the entire waterfall appears to move. This technique was ubiquitous in the 90s:

  • Doom: The glowing red eyes of monsters and the pulsating lava.
  • SimCity 2000: The flowing water and traffic lights.
  • Duke Nukem 3D: The illuminating lights in corridors.

The Pel Mask (0x3C6)

One obscure feature of the DAC is the Pixel Mask register at port 0x3C6. This register acts as a bitwise AND filter for the palette index. If you write 0x0F (binary 00001111) to the Pel Mask, any pixel pointing to index 255 (11111111) will actually display the color at index 15 (00001111). While rarely used in games, it was sometimes used for “layering” effects or quick brightness shifts by masking out high bits.

Legacy

Today, we use 32-bit color (RGBA). We have the bandwidth to update millions of pixels per frame, so palette cycling is dead. But the aesthetic of the limited, carefully chosen palette remains a core part of pixel art. Artists like Mark Ferrari (famous for LucasArts backgrounds) mastered the art of “dithering” and “cycling” to create living, breathing worlds within the strict 18-bit limit of the VGA DAC.