Dirty Rectangles
Efficient screen updates
Dirty rectangle rendering optimised screen updates by tracking and redrawing only the areas that changed, saving CPU cycles when most of the display remained static.
Why redraw the entire screen when only a small part changed? Dirty rectangle rendering tracks which screen regions have changed and redraws only those areas. For games with mostly static backgrounds and a handful of moving sprites — point-and-click adventures, strategy games, GUI applications — this dramatically reduces rendering work compared to redrawing every pixel every frame.
Dirty rectangles fall between double-buffering (full redraw, swap) and direct screen drawing (write while displaying, risk tearing). They give you tear-free output without the cost of touching unchanged pixels.
Fast facts
- Purpose: Reduce unnecessary redrawing.
- Method: Track changed screen regions as bounding rectangles.
- Benefit: Lower CPU usage when most of the screen is static.
- Best for: Static backgrounds with a few moving elements (adventures, GUIs, board games).
- Worst for: Full-screen scrolling, particle systems, fast-moving full-screen action.
How it works
Each frame:
| Step | Action |
|---|---|
| 1 | Mark previous sprite/object positions as “dirty” |
| 2 | Mark new sprite/object positions as “dirty” |
| 3 | Coalesce overlapping or adjacent dirty rects |
| 4 | For each dirty rect: redraw the background, then any objects that overlap it |
| 5 | Copy only the dirty rects from back buffer to front buffer (or to display) |
The dirty list is typically a small array of (x, y, w, h) rectangles, regenerated every frame.
Tracking changes
| Change type | Dirty region |
|---|---|
| Sprite moved | Old position + new position (often coalesced if they overlap) |
| Animation frame change | Sprite bounding box at current position |
| Score / HUD update | Score display area |
| Background change | Affected tiles (e.g. a door opening) |
| Particle effect | Bounding box of all particles (or per-particle if scattered) |
Rectangle operations
A small library of rectangle primitives suffices:
| Operation | Purpose |
|---|---|
| Union | Combine two overlapping rects into one bounding rect |
| Intersection | Find overlap area |
| Containment | Does rect A entirely contain rect B? |
| Clipping | Restrict rect to within screen bounds |
| Coalescing | Merge nearby rects whose union is cheaper than two separate updates |
The coalescing decision is heuristic: merge if the union area is less than the sum of the individual areas plus some overhead, otherwise keep them separate.
Pseudo-code
dirty_list = []
each_frame:
# Phase 1: erase and remember what was where
for sprite in moving_sprites:
if sprite.moved or sprite.animated:
dirty_list.append(sprite.previous_bbox())
dirty_list.append(sprite.current_bbox())
# Phase 2: coalesce overlapping rects
coalesce(dirty_list)
# Phase 3: redraw each dirty rect
for rect in dirty_list:
clip rect to screen
redraw_background(rect)
for sprite in sprites_overlapping(rect):
draw_sprite_clipped_to(sprite, rect)
# Phase 4: blit dirty rects to display
for rect in dirty_list:
blit(back_buffer, display, rect)
Advantages
| Benefit | Impact |
|---|---|
| CPU savings | More processing time available for game logic |
| Reduced flicker | Smaller updates blit faster, lower chance of catching the raster mid-update |
| Memory bandwidth | Less data moved per frame; matters on shared-memory systems (Amiga chip RAM) |
| Scales with activity | Idle screens cost nothing to “render” |
Limitations
| Drawback | Situation |
|---|---|
| Tracking overhead | Bookkeeping is non-trivial; small rects don’t pay back |
| Full-screen changes | If everything moves, dirty-rect costs more than full redraw |
| Many objects | Coalesce-and-redraw degenerates toward full-screen as object count rises |
| Scrolling | The whole screen is dirty every frame; no benefit |
| Z-ordering | Rectangles overlapping in depth need careful redraw order |
| Complex backgrounds | Background reconstruction must be cheap, or savings vanish |
When to use
| Game style | Dirty rects? |
|---|---|
| Point-and-click adventures (Sierra, LucasArts) | Yes — ideal use case |
| Strategy games with mostly static maps | Yes |
| Board games, card games | Yes |
| GUI applications (Workbench, GEM, Windows 1-2) | Yes — original motivating use case |
| Side-scrolling platformers | No — too much movement |
| Vertical-scrolling shooters | No |
| Action games with full-screen scroll | No |
| Particle-heavy effects | Marginal at best |
Platform-specific notes
| Platform | Application |
|---|---|
| Amiga | Built into the operating system: the Layers library keeps a DamageList of exposed rectangles per window, and BeginUpdate()/EndUpdate() clip redrawing to it |
| PC (pre-VGA) | EGA-era games before fast hardware blits; Sierra and LucasArts ports |
| C64 | Less common — sprites are hardware, so the technique mostly applies to the playfield |
| GUI systems | The Macintosh Window Manager accumulates an “update region” and raises an update event when it is not empty; GEM AES hands the application a “rectangle list” of the parts of a window that need redrawing |
| HTML5 Canvas | Modern web games still use this; Canvas’s clearRect + redraw pattern is dirty-rect rendering |
Where the technique was written down
The clearest period descriptions come from windowing systems rather than games, because overlapping windows made partial redraw a system service. Apple’s 1984 Window Manager documentation describes an “update region” that “the Window Manager keeps track of… and accumulates” as windows are uncovered, with the application asked to redraw only when the region is not empty. GEM on the Atari ST exposes the same idea as a rectangle list: James Luczak’s ST-Log tutorial (January 1987) walks through fetching each rectangle with WIND_GET and redrawing “the correct number of rectangles” in a loop. On the Amiga, the ROM Kernel Reference Manual’s Layers chapter documents the DamageList and the BeginUpdate()/EndUpdate() pair that installs it as the clipping list, so that “only the damaged areas are actually rendered into, saving time”.
Modern relevance
Dirty rectangles remain alive in:
- Web rendering — browsers track dirty regions for repaint optimisation.
- Game engines — UI systems use dirty-rect updating (Unity UGUI, Unreal Slate).
- GUI toolkits — Qt, GTK, AppKit/UIKit all use damage-region tracking under the hood.
- Compositing window managers — every modern window manager tracks per-window damage rectangles.
The terminology has evolved (now “damage tracking” or “invalidation”) but the core idea is unchanged from the 1980s.