Computing History · Microprocessor Revolution

Framebuffer Arithmetic: What a Bitmap Mode Costs in Memory

Character cells versus linear bitmaps, the byte counts each requires at common resolutions, and why early machines split the difference with attribute bytes and planar layouts.

A display is refreshed continuously, so its contents must live in memory the display circuitry can read at a rate the monitor dictates. That requirement turns a graphics mode into a memory budget problem, and the budget is set by the resolution, the colour depth, and the refresh rate together. Early machines solved it by choosing representations that spend fewer bytes per pixel than a straightforward linear array would, and each of those representations imposed a cost somewhere else. The other half of the bill is paid in how the firmware exposes the mode to programs, since a display service that addresses the screen in character positions cannot describe a bitmap.

The cheapest useful display

A character-mode display stores a grid of codes rather than a grid of pixels. At 80 columns by 25 rows, the visible field is 2,000 positions. Each position needs a character code and, on a colour machine, an attribute describing foreground and background. Two bytes per cell gives 4,000 bytes of display memory, and a character generator in ROM turns each code into the dot pattern the monitor draws. The display circuitry reads one cell’s two bytes, fetches the corresponding row of the glyph, and shifts it out.

The memory is trivial. The resolution is not, because a character cell at 80 columns across a screen of roughly 640 pixels means each cell is eight pixels wide and, at 25 rows on a screen of 200 or 350 lines, eight or fourteen pixels tall. The character generator defines what can appear, and a program that wants a shape the generator does not contain must either redefine a glyph or abandon the mode.

Redefining a glyph is the standard escape hatch. The generator’s patterns live in memory the processor can rewrite, so a program can replace an unused character code with its own eight-by-eight bitmap and then place that code in the display. On machines with a fixed generator the trick still works if the video hardware reads glyph patterns from a writable region rather than from a dedicated ROM. The cost is that the character set is now shared state: two programs that both redefine glyph 64 conflict, and whichever wrote last is what the screen shows.

The linear bitmap and its byte count

A bitmap mode stores one or more bits per pixel. The arithmetic is direct: width times height times bits per pixel, divided by eight.

At 320 by 200 pixels with one bit per pixel, the frame is 64,000 bits, or 8,000 bytes. At 640 by 200 with one bit per pixel, it is 128,000 bits, or 16,000 bytes. Adding colour multiplies. Two bits per pixel at 320 by 200 gives 16,000 bytes and four colours per pixel. Four bits per pixel at 320 by 200 gives 32,000 bytes and sixteen colours.

Those numbers were decisive. A machine with 64 KB of address space cannot hold a 32 KB framebuffer and also hold its operating system, its program, and its data, because the processor needs to address all of them through the same 65,536 byte window. The 16-colour mode at 320 by 200 therefore existed but was expensive: it consumed half the address space, and a program using it had to fit everything else into what remained.

The way out was to reduce the number of bits per pixel while keeping the appearance of more colours. A palette register holds the mapping from a small pixel value to a wider colour output. With four bits per pixel the framebuffer stores one of sixteen indices, and each index selects from a larger set of colours held in the palette. The framebuffer is still 32 KB, so the palette reduces the colour range, not the memory. The real saving comes from lowering the bits per pixel and accepting fewer simultaneous colours on screen.

Packing pixels into planes and interlaced rows

Two layouts compete for the same framebuffer bytes.

The linear, or chunky, layout stores the bits for adjacent pixels in adjacent memory. One byte holds eight pixels at one bit per pixel, or two pixels at four bits per pixel. Advancing one pixel means advancing one bit or half a byte, and the address arithmetic is a multiplication by the row stride.

The planar layout splits the frame into bit planes. With four planes, plane 0 holds the least significant bit of every pixel, plane 1 the next bit, and so on. A pixel’s colour is assembled by reading the same bit position from four separate memory regions. The advantage is that a program can write to one plane without touching the others, which makes certain operations cheap: filling a region with colour 8 means writing only to plane 3. The disadvantage is that drawing a single pixel of arbitrary colour requires four separate writes at four addresses, and the video circuitry must fetch from four regions per pixel clock.

Machines chose between these based on what their software did most. A system used mainly for text and simple shapes benefits from planar writes that can target a single bit. A system used for fast block copies benefits from chunky layout, where a run of pixels is a contiguous run of bytes that a string move can transfer.

Planar layouts also interact with the instruction set. On a processor with bit-set and bit-reset instructions, drawing into one plane is a single operation on a byte at a known offset, with no read-modify-write sequence and no need to preserve the other planes. On a processor without those instructions, the same draw is a read, a mask, an or, and a write, and the four planes multiply that work.

The row order is a third variable. Some adapters store rows in an interleaved pattern so that the memory chips can be accessed in parallel, which means the address of a given pixel is not a simple linear function of its coordinates. The 320 by 200, 16-colour mode of the IBM adapter splits the frame into two banks 8,192 bytes apart, with even-numbered rows in the first bank and odd-numbered rows in the second. Each bank holds one hundred rows of eighty bytes, so 8,000 of its 8,192 bytes are used and the remaining 192 are unused. Computing the address of a pixel therefore requires a bank term as well as a row term, and code that assumed a linear layout produces a sheared image with every other line displaced.

/* Byte address of a pixel in the 320x200, 4bpp mode of the IBM adapter.
   Even rows live in the first bank, odd rows in the second, 8192 bytes apart.
   The layout is the documented one; the constants are not measurements. */
#define STRIDE 80          /* bytes per row: 320 pixels at 4 bits each */
#define BANK   0x2000      /* distance between the two banks */

unsigned int pixel_offset(int x, int y)
{
    unsigned int bank = (y & 1) * BANK;  /* odd rows start in the second bank */
    unsigned int row  = y >> 1;          /* row index within its own bank */
    return bank + row * STRIDE + (x >> 1);
}

Refresh bandwidth as the real constraint

Memory capacity is the visible limit, but bandwidth is the one that shapes the design. The display must read the entire framebuffer once per refresh. At 320 by 200 with four bits per pixel, the frame is 32,000 bytes. A refresh at 60 Hz reads 1.92 million bytes per second from display memory, which at the time was a substantial fraction of what the memory bus could deliver.

The processor shares that bus. On machines where the video circuitry and the processor contend for the same memory, the video reads take priority, and the processor is stalled for the duration. The stall is not uniform: it happens during the horizontal and vertical blanking intervals differently than during active display, so a program’s execution speed varies with what the display is doing. Code that must run at a precise rate, such as a routine that generates sound by toggling a bit in a tight loop, is affected by how much of the frame is being drawn.

The mitigation is to give the display its own memory, separate from the processor’s, which removes contention but adds cost and pin count. Machines that did this could run the processor at full speed regardless of the display mode, and machines that did not had documented timing variations that software had to accommodate. Those variations are the subject of cycle counting under video contention: the processor’s rated clock describes the cycles it completes when it owns the bus, not the rate at which it completes work.

The other mitigation is to reduce the bytes the display must read. A character mode reads 2,000 cells per frame rather than 32,000 pixels, a factor of sixteen less. That is why text modes were not merely a convenience but the default: they let the same memory bandwidth serve a full screen at a fraction of the cost.

Attribute bytes and the colour-per-cell compromise

Between character mode and full bitmap sits a representation that stores a character code plus a colour attribute per cell, with the glyphs themselves in a bitmap. This is still a character grid, but the glyph set is in RAM and the attributes give each cell its own colours. A cell is then two or three bytes depending on how the attributes are packed, and the visible resolution is the cell grid rather than the pixel grid.

The compromise has a specific consequence: colour changes can only occur at cell boundaries. A shape that crosses cells cannot have a different colour in the middle of a cell, so diagonal edges and fine detail show colour fringing. Programs worked around it by using cells smaller than the graphics they wanted, or by accepting the restriction and drawing in blocks.

The representation also makes scrolling cheap, because scrolling is a copy of cell codes within display memory rather than a movement of pixel data. Moving the visible window by changing a hardware offset register is cheaper still, and machines that provided such a register made full-screen scrolling nearly free. Machines without one had to copy the entire frame, which at 32 KB per frame is a significant amount of work per scroll step.

The trade across all these representations is consistent. Fewer bytes per screen means less memory and less bandwidth, at the cost of either resolution, colour depth, or the ability to address individual pixels. Every early display mode is a point on that line, and the software written for each machine encodes the choice in its assumptions about what a screen is.

References

  1. IBM Personal Computer Technical Reference — IBM Corporation, 1981
  2. Commodore 64 Programmer's Reference Guide — Commodore Business Machines, 1982
  3. IBM Personal Computer Options and Adapters Technical Reference — IBM Corporation