CGA: The Oldest Tricks In the PC’s Book

The vast bulk of my retrocoding articles on this blog have been based on the Commodore 64—a system from 1982. Let’s step back a year to 1981, and a very different, yet also more familiar system.

I refer, of course, to the IBM PC, an 8088-based system with the infamous Color Graphics Adapter. Those of you readers of a certain age remember its incomprehensible choice for its primary graphics palette of cyan, magenta, and eye-searing white:

monuments_menu

(Image shown is Apogee’s old puzzle platformer The Monuments of Mars!. It’s better than it looks, and its spiritual predecessor Pharaoh’s Tomb was better still. Both are available for free from the 3DRealms back catalog.)

Now, normally the PC is out of my area of interest for these kinds of articles. If you want to learn about the history of pushing the PC, graphically, to its limits, that is more or less the history of id Software, and those guys were and are living legends and they wrote magisterial technical memoirs. Several things that drove me to investigate this:

  1. While reminiscing about the first computer games we had played, somebody mentioned Round 42, which used a display mode I had never seen, and which Wikipedia indicated was a nonstandard, semi-documented CGA mode. Between the documentation and the Wikipedia article I could get almost everything I needed to work out how to enter the mode.
  2. While trying to research the rest of the technique, I encountered the other nonstandard semi-documented CGA mode, which only worked when hooked to a television instead of an actual CGA monitor. This was apparently pretty widely used, but I had never heard of it because by the time I first touched an IBM machine, color monitors were the universal standard.
  3. In April of this year, the 8088 and the CGA were put back on the map by the astonishing demo 8088 MPH, which took both of these old techniques, applied them simultaneously (a thing that isn’t supposed to even work), and then pushed them even further, to get 1024 colors out of a CGA display intended to cap out at 4.

I am restricting myself in this article to the old, established techniques; I won’t be trying to replicate the 8088MPH CGA mode effects here. Nevertheless, before I go on, I must repeat one of their caveats:

Don’t try this anywhere but in emulation or on actual, original hardware. If you’re lucky, feeding a VGA CRT this code will simply produce incorrect displays. If you aren’t, you can permanently damage the monitor, possibly in actually dangerous ways. Those of you who configured X Windows for Linux back in the early and mid 1990s may recall the stern warnings that getting this stuff wrong could literally make the monitor spontaneously combust. They weren’t kidding, and we are swimming on those waters here.

What It Is

I’m going to be discussing and demonstrating two old techniques for getting a better palette out of the CGA. These are two techniques, and which one was actually feasible depended on whether your CGA card was hooked up to a television set or a proper computer monitor.

  • For computer monitors, you could produce a 160x100x16 graphics mode that used all 16 of the CGA’s colors. IBM hinted that this was possible in their official documentation but didn’t say how to do it. A shareware game for the IBM XT named “Round 42” seems to be the go-to historical example for this mode.
  • For television screens, you could produce a 160x200x16 display with an entirely different palette of 16 colors, one that in fact matches the 16 low-res colors of the Apple II.

Properly configured, DOSBox can simulate both of these techniques. Here it is using the 160x100x16 mode to display a suitably dithered version of one of my old LiveJournal icons:

glorious_cga

And here is a set of colorbars showing the 16 alternate colors:

cga_artifacts

What It’s Doing

I’ve lamented in the past that the full details of why techniques work are often lost in the noise or assumed as background knowledge. Sceners are generally experts talking to each other, after all. However, VileR (one of the people involved in the 8088MPH demo) wrote up a fantastic illustrated backgrounder that goes into the theory of how these two old techniques work and how they influenced the new techniques he helped create this year.

I will attempt a bit of an executive summary below the fold before I move on to Actual Code with explanations, but I do suggest you check that article out.

The 160x100x16 mode is the simplest to explain: the Wikipedia page includes screenshots and the basics of the technique. The secret, such as it is, is that this is actually a text mode, mutated so that it’s an 80×100 display, with the character cells shrunk to 8×2 instead of the normal 8×8, and then filled with character number 222. That character is normally one of the ASCII-art box-drawing characters—it has eight identical rows where the left four pixels are the background color and the right four pixels are the foreground color. Pixels are then drawn by assigning background colors for even-numbered pixels and foreground colors for odd-numbered pixels. By default, background colors in the range 8-15 are treated as background colors 0-7 with blinking text, but it’s possible to configure a CGA display so that doesn’t happen.

The 160×200 mode, also known as “CGA composite mode”, relies on quirks of North American televisions. Color television was kind of a hack bodged into the pre-existing black and white televisions. There’s an additional signal added to it (at about 15MHz). There’s a reference signal between lines (the “color burst”), and then the hue of a point is based on the difference in phase of the color signal between what’s transmitted mid-line and what was transmitted during the color burst. Now, as it happens, there were 160 cycles of this color transmission frequency per display line, and multiples of 160 show up a lot in these old 8-bit systems. The C64 and IBM 40-column modes were both effectively 320 pixels across, and their fonts made sure to always be “double-dotted” – which is to say, any lit pixel had at least one adjacent lit pixel. The IBM 80-column modes were 640 pixels across. The Atari 2600 and some low-res modes on the PCjr were 160 pixels across. The Atari’s programming manual even refers to pixels not as pixels but as color clocks—that is, cycles of the color’s oscillator.

The core insight here is that when you have a higher resolution than 160 pixels across, changes in luminance will be mistaken by an SDTV decoder as part of the chrominance signal. (The C64 actually warned in its programmer’s reference guide to double-dot all your graphics or else you risked “CHROMA noise”.) Here we take advantage of this; by laying down regular stipple patterns in a 640×200 monochrome bitmap display, we essentially lay down a chrominance signal to be interpreted by the television set. Here is what the colorbars image at the top of this article looks like when it’s fed through a video monitor instead:

cga_stipple

More details, including some very fine hue charts, are at VileR’s blog article linked above.

How To Do It: Setting the modes

The CGA card was programmed through a number of I/O ports that you accessed with the out instruction. There are three registers that matter: Port 0x3d8 is the mode control register, port 0x3d9 controlled color output, and then ports 0x3d4 and 0x3d5 configure the CRT controller. This is where things get exciting—the values that configure the CRT controller are used to determine the horizontal and vertical sync rates. This is where we enter the realm of “could do real damage to actual hardware”—overdriving a CRT is a Bad Thing.

Anyway, the trick here is that the way that you normally set a mode is by disabling video entirely, rewriting the values of the CRT controller appropriately, and then re-enabling video in the new mode. To get the low resolution graphics mode, you actually set up the CRT controller as if you were doing a graphics mode and then set 80-column text instead. The relevant CRT control values are “vertical total” (127), “vertical displayed” (100), “maximum character rows before VSYNC” (112), and “maximum scanline per character” (1, for 2 rows total). All the other values remain as they would be for 80-column mode, and BIOS knows how to set that (interrupt 10h, AH = 00h, AL = 03h).

Meanwhile, to do the high-resolution composite 160×200 mode, one simply sets the 640×200 mode as usual through BIOS and then sets the mode bit that enables the NTSC colorburst. (The BIOS graphics mode—”Mode 6—is monochrome and so doesn’t set that.)

Setting the 160x100x16 mode

The IBM PC used the 8088 chip, which was programmed the same as the 8086. I’m using the Netwide Assembler here since it lends itself well to 16-bit x86 code. Of course, x86 16-bit assembler is bonkers, but we’ll see that as we come to it. Most of our techniques here don’t do anything too dumb.

Step 1: setting 80-column text mode

This gets us our defaults. This is also how we leave the low-resolution mode and go back to normal text mode again, so we break this out into its own routine:

set_mode_3:
        mov     ax, 0x03
        int     10h
        ret

Step 2: Disable CGA video

The mode control register has three bits we care about here. Bit 0 controls 40 vs. 80 character mode. Bit 3 is the “enable video” bit. Bit 5 is the “drop the number of background colors to 8 and enable blink” bit. The default value of the register is 0x29, which enables video and blink in 80 character mode. We just turn bits 3 and 5 off:

        mov     dx, 0x3d8
        mov     al, 1
        out     dx, al

Step 3: Program the CRT controller

The CRT controller is obnoxious to program. There are 17 registers, but only two ports; you write the register number to port 3d4, and then write the value to 3d5. These are almost entirely write-only registers, so we can’t adapt. We must know. Fortunately for us, we do, because IBM listed the proper values in their documentation.

This code is pretty repetitive, and we can simplify our lives by setting up a port-writing routine that uses all of AX instead of constantly trashing AL:

write_cga_reg:
        mov     dx, 0x3d4
        push    ax
        mov     al, ah
        out     dx, al
        inc     dx
        pop     ax
        out     dx, al
        ret

So, then we can set up the four registers we need with four calls to this function:

        mov     ax, 0x047f      ; Vertical total of 127.
        call    write_cga_reg
        mov     ax, 0x0664      ; Vertical displayed of 100.
        call    write_cga_reg
        mov     ax, 0x0770      ; Vertical sync position of 112 rows
        call    write_cga_reg   ;   (224 visible scanlines)
        mov     ax, 0x0901      ; Maximum scanline of 1 (2/char)
        call    write_cga_reg

That’s 224 scanlines per screen, which is great for CGA, but noticably less great with VGA. VGA’s CRT controller is port-compatible with CGA, but the implications of these registers are very different! It stays 224 scanlines per screen, but that’s only half of a VGA screen, which will translate to a vertical sync speed of 140Hz. Hope your monitor is a very capable multisync.

Step 4: Fill the screen with character 222

Here’s where x86 demonstrates how little it cares about notions like RISC:

        mov     ax, 0xb800
        mov     es, ax
        mov     ax, 0x00de
        mov     cx, 8000
        xor     di, di
        rep     stosw

Basically all the work here is done by the rep stosw instruction. That instruction stores the value of AX to memory location ES*16+DI, then increments DI by 2, and then does that CX times. So here we set up ES, DI, AX, and CX appropriately (note also that instead of assigning 0 to DI, we XOR it with itself, because it’s shorter and faster that way, because x86 assembler is bonkers), and then we execute the repeated store-word instruction to blast 8,000 copies of 0x00de to B8000—the physical RAM location of CGA text screen memory. I’ll go into more detail about what’s going on here and how you write to the screen in the section where I describe how to draw in these modes, but for now, we’ll leave it at “this fills the screen with black-on-black copies of character 222.”

Step 5: Re-enable video

This is basically step 2, but with the video enable bit set. We leave blink off so that we have access to all 16 colors in the “background”, which for us are even-numbered pixel columns.

        mov     dx, 0x3d8
        mov     al, 9
        out     dx, al

Entering the 160×200 composite mode

Compared to the rigamarole above, the composite-video mode is embarassingly easy to enter. Simply activate mode 6 (640x200x2 mode), then write in the normal value to the mode port for a color display:

        mov     ax, 0x0006
        int     10h
        mov     al, 0x1a
        mov     dx, 0x3d8
        out     dx, al

Drawing to the 160x100x16 display

As we mentioned before, this is a text mode. Text modes in DOS from CGA through VGA all work the same way: there is a block of screen memory starting at location B8000 that represents the screen. There is one 16-bit value for each text cell, left to right, top to bottom, in order:

Bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Blink Background color Foreground color Character code

We don’t want to touch the character code here, and we’ve disabled blink to give us the full four bits for background color. Thus, to set point (X, Y) to C, we need to find the memory location for X, Y’s color byte, and then set the correct half of that to C.

When writing 16-bit x86 code for this, it’s probably most effective to put the value B800 into the ES register and then use the DI register to hold the appropriate offsets you’re writing to.

My sample program is simply loading the whole screen with an image, so it fills them in two pixels at a time from an array of 80*25=2,000 elements.

Drawing to the 160×200 composite display

Given the absolute madness of the theory behind this technique, it’s refreshing and astonishing to find that the method of programming it is so simple as to be nearly trivial. As VileR mentioned in his discussion, a repeating pattern at some multiple of the NTSC colorburst frequency will be interpreted as a solid bar of color by the television screen. For BIOS mode 6—the 640×200 monochrome display we use here—that is “a repeating pattern of four pixels”. There are 16 possible patterns of 4 pixels, and Mode 6 is actually a bitmapped mode. That means that to display a color from 0 to 15, we find the appropriately aligned block of 4 bits in the bitmap and just write that 4-bit number there. We get to treat it like a 16-color 160×200 bitmap!

There are two caveats here, though. First off, we do need a pattern to get a proper display; whenever a color changes from one to another a television set will likely introduce some artifacting in its own right. Single pixels of a color will likely be distorted still. And second, mode 6’s programming is kind of weird.

Mode 6 uses what’s called an “even/odd” layout, designed, I would guess, to make it easier to produce an image on interlaced displays. With 8 bits per pixel and 640 pixels across, 80 bytes suffices to specify one complete scanline in this mode. Like the text mode, this starts at B8000. Unlike the text mode, though, instead of just being a solid block of (in this case) 16,000 bytes to represent the whole screen, it’s two blocks of 8,000 bytes; one for the even-numbered rows (0, 2, 4, etc.) and one at BA000 for the odd-numbered rows (1, 3, 5, etc.). Given how annoying it is to juggle values in segment registers, when programming this mode I would normally suggest keeping the ES register at B800 and then just adding 0x2000 to your offset register when writing to odd-numbered rows.

My sample program is simply drawing horizontal bars, so I’m just stuffing the video memory with constants, two bytes (and thus four pixels) at a time, with updates as needed. I then repeat the process with the 0x2000 offset to catch the odd-numbered cases.

Alternate composite palettes

Within mode 6, on the CGA (and, as near as I can tell, only on the CGA), you can change the displayed monochrome color by writing a value from 0 to 15 into port 0x3d9. This alters how the color signal is interpreted by the television. The sample program will count down from 15 to 1 to let you see each palette.

Similar techniques are also possible in the 320x200x4 graphics modes, but DOSBox does not support those. (Among other things, this means that the PC version of Ultima II looks much worse than it was intended to.)

The core insight of the 256 and 512-color palettes in 8088MPH was that you could make this happen in the 160x100x16 mode too, by picking the right characters to use instead of 222. However, by default, 80-column color text mode does not work right on television displays, and additional trickery is needed to make that work out. VileR’s article, and the articles by reenigne that it links, discuss how to compensate for that, and reenigne even has sample code. I leave the explanation of those techniques to them, as I do not have the hardware I would need to even attempt replication.

Full sample applications

I’ve put the full NASM code up at the blog’s Github account. The comments include build and run instructions.

Note that the DOSBOX simulation system defaults to VGA, which does not properly handle all CGA commands. In particular, it doesn’t have the mode control register, so blink remains enabled, in addition to only half the screen being used:

vga_cga

If you want this program to run properly, you’ll need to reconfigure DOSBOX some. The relevant setting is “machine” under the “dosbox” header – it needs to be set to “cga” instead of the default “svga_s3”. That will produce the image at the top of this article.

Advertisement

5 thoughts on “CGA: The Oldest Tricks In the PC’s Book

  1. Pixman

    Hey, I’m coming from the C64 side of the business and I’m also trying Atari 2600.

    I REALLY love your site! And your mission is awesome 🙂
    Is there any way to contact you? I haven’t found an e-mail or a contact form.

    I’d like to talk to you about some stuff you’ve written about. Your site was very helpful and I hope it will grow.
    I will spread this site as much as I can.

    Regards,
    Pix / Michael

    1. mcmartin1723 Post author

      Hi Pixman. Glad you like what you’ve found! I’m still sort of new at this WordPress thing, so I don’t have contact forms set up. You can reach me at bumbershoot.software at gmail if you wish, or if you want to ask about other articles in the archives, feel free to comment there directly.

  2. gau_veldt

    The four-bit artifacting also works on Apple ][c (or ][e with 80C card) in the double hires mode to get 140×192 16-color graphics on a TV (same pixel colors as the lo-res block graphics mode). It was also split into even/odd due to the way the 80-column hardware worked (it took the evens from the main 64K memory bank and the odds from the extended 64K memory bank). There was even a paint program available that worked in this mode.

  3. Jason Knight

    You should grab the codebase for my game Paku Paku, it includes video detection and sets compatible modes for both EGA and VGA. For VGA it forces the 640×400 text mode, sets the character height to 4px, and uses the proper blink disable. For EGA it forces the 640×200 mode and again uses the correct EGA blink disable method. It also compensates for the differences between the PCJr and Tandy video and the CGA, which also does things… differently.

    The rar includes the full game source. Should be easy enough to yank out the system detection code.

    https://deathshadow.com/downloads/paku_1_6.rar

Comments are closed.