Home Memoirs of a Gamer Movies I watched Guidebook Links

the Lobdegg's Comprehensive Guidebook to DOS Programming

Chapter 2 - Video

Section 4: EGA (Enhanced Graphics Adapter)

Initialization
Plotting Pixels
Bit Planes and You
Bit Plane Registers
VGA trickery
Clean Up
References

Introduction

There is a considerable time span in the DOS era during which EGA 16 color graphics were considered if not the state of the art, then at least the defacto standard.

Initialization

Like most video modes covered by BIOS, initialization is as simple as calling int 0x10 with the correct video mode ID.
Setting EGA 320x200
Assembly Borland Turbo C++ /
Open Watcom 16bit
Open Watcom 32bit
    xor ah, ah
    mov al, 0x0D
    int 0x10
union REGS regs;
regs.h.ah = 0;
regs.h.al = 0x0D;
int86(0x10, &regs, &regs);
union REGS regs;
regs.h.ah = 0;
regs.h.al = 0x0D;
int386(0x10, &regs, &regs);

Plotting Pixels

EGA is a bitmap mode, similar to most graphics modes available to IBM PC compatibles. This means drawing to the screen is as simple as taking the video ram's base address 0xA0000 and adding an offset calculated by multiplying the Y position by the screen's width in bytes and adding the X coordinate.

Thus drawing a dot in the top left hand corner of the screen is as easy as setting the first byte of vram (0xA0000) to 1! Hey presto, we're drawing!

Assembly Borland Turbo C++ Open Watcom 32bit
    mov ax, 0xA000
    mov ds, ax
    xor bx, bx
    mov [ds:bx], byte 0x01
unsigned char far *screen;
screen = MK_FP(0xA000,0);
screen[0] = 1;
unsigned char *screen;
screen = (void*)0xA0000;
screen[0] = 1;

Well...almost. You see, there's something to bare in mind with EGA...

The Bit Planes

Bit Planes and You

Now, there is a caveat to drawing in EGA that isn't generally a problem in most other video modes. EGA uses a memory model called bit planes.

What are bit planes, you ask? To put simply instead of packing multiple pixels into a single byte of data like CGA and Tandy 16 color modes do, EGA decided to implement what is functionally 4 monochrome vram buffers. Every 8th pixel all 4 of those vram banks load the next byte's worth of pixels into a shift register. 8 bits go in and one bit goes out. Each pixel clock tick causes the shift register to output the next bit, just like in Monochrome graphics modes. Why 4 planes? Well because if you put 4 monochrome video buffers together, you get 4 bits per pixel, or 16 colors. Sounds simple, right? From a hardware perspective, it is!

Unfortunately we're not writing hardware, we're writing software, and that hardware simplification requires we do a little extra in software.

So how are these bit planes combined? Well on original CGA displays, to facilitate 16 color text mode, there was a TTL signal for Blue, Green, Red, and Intensity.

What intensity did on a hardware level was push the 3 color signals up by 1/3rd. Sort of anyways. Brown is a special case, since strictly following that model gives you a more olive color rather than brown. Coming from the 70's I guess everyone had their fill of yellowy olive colors and wanted something a bit more woody colored. I could name other brown materials here but let's keep this PG.

Each bit plane can be activated for writing independently of the others. This is why when you wrote 1 to vram above you got a white pixel. By default all 4 bit planes are enabled for writing. This means you can write black and white graphics really easily! Writing a single byte can set all 4 bit planes at once, making the color bright white! We can set any combination of the bit planes to active too! If we want to draw something that is Brown and Black, we can turn on the Red and Green channels and write a single byte. This also means we can draw completely different images to each bit plane and the hardware will blend the buffers together for us for nifty affects!

Duke Nukem 2 famously used this trick to create a cloaking effect for a powerup.

So, how do we control which bit planes we're writing to anyways? Well I'm glad you asked...

Bit Plane Registers

There are two important registers in the EGA spec: 0x03C4 and 0x03C5. The actual functionality of these two registers is pretty complicated and go far beyond the scope of this chapter, but a reference to the OSDev website can be found at the bottom of this page for full details. For our needs, we just need to know 0x03C4 is an index value and it needs to be set to 2, and then 0x03C5 gets the value for the register indexed in 0x03C4 which needs to be set to the desired bit mask.

Enabling EGA bit planes
Assembly Borland Turbo C++ / Open Watcom
    mov dx, 0x03C4
    mov al, 0x02
    out dx, al
    inc dx
    mov al, mask
    out dx, al
outp(0x03C4,0x02);
outp(0x03C5,mask);

The value stored in mask is going to depend on which planes we want to write to.

Mask Value (binary) Plane
1 (0001) Blue
2 (0010) Green
4 (0100) Red
8 (1000) Intensity

To combine masks just or the planes you want together:

Let's mix Blue(1), Green(2), and Intensity(8).
Assembly Borland Turbo C++ / Open Watcom
    mov al, 0x01
    or al, 0x02
    or al, 0x08
mask = 0x01;
mask = mask | 0x02;
mask |= 0x08;
This will produce a bright aqua color where ever we write a pixel to be 'on'.

VGA Trickery

VGA, being 100% backwards compatible with CGA and EGA as part of the spec introduces a fun little quirk. See, VGA cables don't take TTL signals for Blue, Green, and Red. Instead they have analog voltage levels controlling the color. The closer to Ground, the darker the color, the closer to 0.7 Volts, the brighter. So unlike CGA and EGA cards which are physically wiring signals to each of the 4 "color" planes, VGA cards send all requests to a 256 color palette register. This means that at least in EGA modes, most cards will allow a program to replace the standard EGA palette with a customized VGA palette with 18 bits of color depth per color value! Then just combine the bit planes such that the Blue channel is our 1st bit, Green is our 2nd, Red our 3rd, and Intensity becomes our 4th bit. 4 bits means 16 colors, right? So for each pixel we can combine our bit planes to make color indices 0 to 15.

There's just one catch....

VGA cards often don't map EGA colors to palette indices 0 through 16. In my experience it's best to start with something like:

EGA Color Index VGA Color Index
0 0
1 1
2 2
3 3
4 4
5 5
6
7 7
8 16
9 17
10 18
11 19
12 20
13 21
14 22
15 23

Then explore from there. I should also warn you that just because your palette mapping works on one VGA card does not mean it will necessarily work on all, so test on as many different platforms as possible to ensure you find all edge cases.

Clean Up

Just like with initialization, exiting EGA graphics mode is super simple!

Exiting EGA for Text Mode land
Assembly Borland Turbo C++ /
Open Watcom 16bit
Open Watcom 32bit
    xor ah, ah
    mov al, 0x03
    int 0x10
union REGS regs;
regs.h.ah = 0;
regs.h.al = 0x03;
int86(0x10, &regs, &regs);
union REGS regs;
regs.h.ah = 0;
regs.h.al = 0x03;
int386(0x10, &regs, &regs);