amiga-bootcamp/17_demoscene/sprite_techniques.md

24 KiB
Raw Permalink Blame History

← Home · Demoscene Techniques

Sprite Techniques — Multiplexing, Sprite-Built Images, and Attached Sprites

Overview

The Amiga has 8 hardware sprite channels, each displaying a 16-pixel-wide, arbitrarily-tall image in 3 colors (plus transparent). That sounds limiting — and it is, for games that need dozens of moving objects. But the demoscene turned this constraint into a creative engine. By repositioning sprites at different scanlines (multiplexing), building full-screen images from sprite data, and combining pairs into 15-color attached sprites, demoscene coders extracted far more from 8 channels than Commodore intended.

This article covers the specific demoscene sprite techniques that go beyond basic sprite usage. For the hardware architecture, DMA timing, and OS-level sprite API, see Sprites.

graph TB
    subgraph "Multiplexing"
        SMX["Vertical Multiplex<br/>Reuse sprites at different Y"]
        HMX["Horizontal Reposition<br/>Move SPRxPOS per line"]
    end
    subgraph "Construction"
        SBI["Sprite-Built Images<br/>Full graphics from sprite data"]
        SBM["Sprite-Built Masks<br/>Sprites as Blitter stencil"]
    end
    subgraph "Enhancement"
        ATT["Attached Sprites<br/>15-color sprite pairs"]
        OVL["Sprite Overlay<br/>Sprites on top of playfield"]
    end

    SMX --> SBI
    HMX --> SBM
    SBI --> ATT
    SBM --> OVL

Hardware Recap

Sprite DMA Channels

Sprite DMA Slots/Line Position Reg Data Regs Colors Notes
SPR0 2 $DFF140 $DFF144/6 COLOR17/COLOR18 + COLOR00 Usually mouse pointer
SPR1 2 $DFF148 $DFF14C/E COLOR19/COLOR20 + COLOR00
SPR2 2 $DFF150 $DFF154/6 COLOR21/COLOR22 + COLOR00
SPR3 2 $DFF158 $DFF15C/E COLOR23/COLOR24 + COLOR00
SPR4 2 $DFF160 $DFF164/6 COLOR25/COLOR26 + COLOR00
SPR5 2 $DFF168 $DFF16C/E COLOR27/COLOR28 + COLOR00
SPR6 2 $DFF170 $DFF174/6 COLOR29/COLOR30 + COLOR00
SPR7 2 $DFF178 $DFF17C/E COLOR31/COLOR32 + COLOR00

Sprite Data Format

Each sprite line is 2 words (32 bits). For a standard 16-pixel-wide, 3-color sprite, each word contains one bitplane of the image:

┌─────────────────────────────────────┐
│  Word 1 (bitplane 0)  │  Word 2 (bitplane 1)  │
│  bits: 16 pixels      │  bits: 16 pixels      │
└─────────────────────────────────────┘

Color mapping:
  00 = transparent (playfield shows through)
  01 = color register A  (e.g., COLOR17 for SPR0)
  10 = color register B  (e.g., COLOR18 for SPR0)
  11 = both set → COLOR00 (shared with background!)

Sprite Control Registers

Register Address Description
SPRxPOS $DFF140+8n Vertical start (bits 15-8) + horizontal start (bits 7-0)
SPRxCTL $DFF142+8n Vertical stop (bits 15-8) + attach bit (bit 7) + control
SPRxDATA $DFF144+8n Data bitplane 0 (write-only)
SPRxDATB $DFF146+8n Data bitplane 1 (write-only)

Note

Sprite position encoding: The 8-bit horizontal component uses low-resolution pixel units ($00 = leftmost visible, max ~$DA). The 8-bit vertical component is the scanline number (PAL: 0311, NTSC: 0261).


Technique 1: Sprite Multiplexing

The most important demoscene sprite technique. The Amiga's 8 sprites each persist from their start scanline to their stop scanline. By repositioning a sprite mid-frame (via the Copper writing new SPRxPOS/SPRxCTL values), the same hardware channel displays different images at different vertical positions.

How Multiplexing Works

sequenceDiagram
    participant Copper as Copper
    participant SPR0 as Sprite 0 Channel
    participant Screen as Display

    Note over Screen: Scanline 50
    Copper->>SPR0: SPR0POS = $3200 (start line 50, x=0)
    Copper->>SPR0: SPR0CTL = $5C00 (stop line 92, attach=0)
    Copper->>SPR0: SPR0DATA = image_A plane 0
    Copper->>SPR0: SPR0DATB = image_A plane 1
    SPR0->>Screen: Render image_A (lines 50-92)

    Note over Screen: Scanline 93
    Copper->>SPR0: SPR0POS = $5D28 (start line 93, x=40)
    Copper->>SPR0: SPR0CTL = $8728 (stop line 135, attach=0)
    Copper->>SPR0: SPR0DATA = image_B plane 0
    Copper->>SPR0: SPR0DATB = image_B plane 1
    SPR0->>Screen: Render image_B (lines 93-135)

Multiplexed Sprite Setup

The Copper writes new position and data values for each sprite at each repositioning point:

; sprite_mux.asm — Multiplex sprite 0 at two vertical positions

SPRITE_MUX_COPPER:
        ; ---- First instance: lines 50-79 (30 lines) ----
        dc.w    $8032,$FFFE           ; WAIT line 50
        dc.w    $0140,$3200           ; SPR0POS: start line 50, x=0
        dc.w    $0142,$4E00           ; SPR0CTL: stop line 78, no attach

        ; Sprite 0 data pointer in Copper (sets SPR0DATA/DATB via DMA)
        dc.w    $0180,$0000           ; COLOR00 = black (sprite uses it)

        ; ---- Reposition at line 80 ----
        dc.w    $8050,$FFFE           ; WAIT line 80
        dc.w    $0140,$5028           ; SPR0POS: start line 80, x=40
        dc.w    $0142,$6A28           ; SPR0CTL: stop line 106, x=40

        ; ---- Reposition again at line 110 ----
        dc.w    $806E,$FFFE           ; WAIT line 110
        dc.w    $0140,$6E50           ; SPR0POS: start line 110, x=80
        dc.w    $0142,$8C50           ; SPR0CTL: stop line 140, x=80

        dc.w    $FFFF,$FFFE           ; End

Multiplexing Limits

Factor Constraint Practical Limit
DMA bandwidth 2 slots per sprite per scanline (8 sprites = 16 slots) All 8 sprites always consume 16 slots — fixed cost
Copper repositioning 2 WAIT+MOVE pairs per reposition (8 words) ~7 repositions per scanline in LoRes 4-plane
Vertical spacing Must wait for sprite stop before restarting Minimum ~1 scanline gap between instances
Data storage Each sprite instance needs its own data in Chip RAM Limited by Chip RAM budget
Horizontal position Single 8-bit value, max ~$DA (218 pixels LoRes) LoRes only; HiRes sprites need different encoding

Typical Multiplexing Budgets

Configuration Sprites Multiplexes/Sprite Total Objects Colors
8 sprites × 1 mux 8 1 8 3 each
8 sprites × 3 mux 8 3 24 3 each
4 sprites × 5 mux 4 5 20 3 each
8 attached × 2 mux 4 pairs 2 8 15 each

Technique 2: Sprite-Built Images

Instead of using sprites for moving objects, demoscene coders use sprite data to construct static images — logos, borders, or even full-screen graphics. This frees bitplane memory for the main display and lets sprites act as an overlay layer.

How It Works

  1. Arrange 8 sprites vertically (no multiplexing) to cover the full screen height
  2. Each sprite is 16 pixels wide × the full display height (~256 lines)
  3. Total sprite coverage: 8 × 16 = 128 pixels wide, full height
  4. Combine with bitplanes for the remaining horizontal space

Logo Construction from Sprites

graph LR
    subgraph "Screen Composition"
        PF["Bitplane Layer<br/>320×256, 4-bitplane background"]
        S0["SPR0-3<br/>64px × 256 lines<br/>Logo left half"]
        S4["SPR4-7<br/>64px × 256 lines<br/>Logo right half"]
    end

    PF --> MUX["Priority MUX<br/>(BPLCON2)"]
    S0 --> MUX
    S4 --> MUX
    MUX --> OUT["Final Display<br/>Background + sprite overlay"]
/* sprite_logo.c — Build a 128-pixel-wide logo from 8 sprites */

#define LOGO_WIDTH  128   /* 8 sprites × 16 pixels */
#define LOGO_HEIGHT 256

/* Each sprite line = 2 words = 32 bits = 16 pixels × 2 bitplanes
   For a logo, we need to convert our image data to sprite format */

struct SpriteData {
    UWORD pos;      /* SPRxPOS value */
    UWORD ctl;      /* SPRxCTL value */
    UWORD data[LOGO_HEIGHT];  /* Bitplane 0 */
    UWORD datb[LOGO_HEIGHT];  /* Bitplane 1 */
};

/* Initialize 8 sprites to display a 128-pixel logo */
void init_sprite_logo(struct SpriteData sprites[8], int start_y) {
    int i;

    for (i = 0; i < 8; i++) {
        int x = i * 16;  /* Each sprite starts 16px after the previous */

        /* Position: high byte = Y, low byte = X (lowres units) */
        sprites[i].pos = (start_y << 8) | (x & 0xFF);

        /* Control: stop Y = start_y + LOGO_HEIGHT - 1 */
        int stop_y = start_y + LOGO_HEIGHT - 1;
        sprites[i].ctl = (stop_y << 8) | (x & 0xFF);

        /* Data is already in sprite format (2 words per line) */
        /* Would be filled from pre-converted image data */
    }
}

Technique 3: Attached Sprites (15-Color)

Normal sprites have 3 colors. Attached sprites combine two sprite channels (even+odd, like SPR0+SPR1) into a single image with 15 colors. The even sprite provides bitplanes 0-1, the odd sprite provides bitplanes 2-3. Together, they form a 4-bitplane (16-color, one transparent) image.

Attachment Encoding

The attach bit in SPRxCTL (bit 7) tells Denise to combine the sprite pair:

; attached_sprites.asm — 15-color sprite pair (SPR0 + SPR1)

        ; ---- Set up SPR0 (even: bitplanes 0,1) ----
        dc.w    $0140,$3200           ; SPR0POS: line 50, x=0
        dc.w    $0142,$5A80           ; SPR0CTL: stop line 90, ATTACH=1 (bit 7)

        ; ---- Set up SPR1 (odd: bitplanes 2,3) ----
        dc.w    $0148,$3200           ; SPR1POS: SAME position as SPR0
        dc.w    $014A,$5A00           ; SPR1CTL: stop line 90, ATTACH=0 (odd)

        ; ---- Set 15-color palette for SPR0+SPR1 ----
        ; SPR0 colors: COLOR17 (01), COLOR18 (10), COLOR00 (11)
        ; SPR1 colors: COLOR19 (01), COLOR20 (10), COLOR00 (11)
        ; Combined mapping:
        ;   0000 = transparent
        ;   0001 = COLOR17  (SPR0 only, bit 0)
        ;   0010 = COLOR18  (SPR0 only, bit 1)
        ;   0011 = COLOR00  (both)
        ;   0101 = COLOR19  (SPR1 only, bit 2)
        ;   ...
        ;   1111 = COLOR00  (all bits set)

Attached Sprite Color Table

Bits (3210) Color Register Notes
0000 Transparent Playfield visible
0001 COLOR17 Even sprite, plane 0
0010 COLOR18 Even sprite, plane 1
0011 COLOR00 Both planes of even sprite
0101 COLOR19 Odd sprite, plane 0
0110 COLOR20 Odd sprite, plane 1
0111 COLOR00 Odd combined
1001 COLOR17+19 Mixed
1010 COLOR18+20 Mixed
... Various See full table below

Note

The 4-bit combination gives 16 values. Value 0 is transparent, and values where both even and odd planes are all-ones map to COLOR00. The remaining 12 values use the sprite's own color registers (COLOR17COLOR20 for the SPR0+SPR1 pair), giving effectively 15 distinct non-transparent colors.

Attached Pair Assignments

Even Sprite Odd Sprite Color Registers Available
SPR0 SPR1 COLOR17, COLOR18, COLOR19, COLOR20 + COLOR00 15 colors
SPR2 SPR3 COLOR21, COLOR22, COLOR23, COLOR24 + COLOR00 15 colors
SPR4 SPR5 COLOR25, COLOR26, COLOR27, COLOR28 + COLOR00 15 colors
SPR6 SPR7 COLOR29, COLOR30, COLOR31, COLOR32 + COLOR00 15 colors

Technique 4: Sprite Overlay with Priority Control

BPLCON2 (bit 6) controls whether sprites appear above or below playfield bitplanes. Demoscene effects exploit this to create overlay effects — sprites that appear in front of or behind the playfield for visual layering:

; Set sprites behind playfield (for background effects)
dc.w    $0104,$0040           ; BPLCON2: sprites behind playfield

; Set sprites in front of playfield (default, for overlays)
dc.w    $0104,$0000           ; BPLCON2: sprites in front

This can be changed mid-frame by the Copper, enabling sprites to appear behind bitplanes in one area and in front in another — a common technique in multi-layer parallax effects.


Technique 5: Sprite-Based Color Effects

Each sprite's color registers can be changed by the Copper per scanline. This enables per-line color animation on sprite graphics:

; Animate sprite colors per scanline for rainbow effect
dc.w    $8032,$FFFE           ; WAIT line 50
dc.w    $0188,$0F00           ; COLOR17 = blue (SPR0 color A)
dc.w    $8033,$FFFE           ; WAIT line 51
dc.w    $0188,$0FF0           ; COLOR17 = cyan
dc.w    $8034,$FFFE           ; WAIT line 52
dc.w    $0188,$0FFF           ; COLOR17 = white
; ... continues for full rainbow

This technique is the basis for many demo effects where a sprite "absorbs" the current background color and appears to change color as it moves.


Antipatterns

1. The Invisible Sprite

Forgetting to set sprite color registers. By default, all sprite colors are $0000 (black), which is also the default background color — making sprites invisible against a black background.

Broken:

/* Sprite is there but invisible — COLOR17/18 match background */
custom.spr[0].pos = pos_value;
custom.spr[0].ctl = ctl_value;
/* Colors never set → invisible on black background */

Fixed:

custom.spr[0].pos = pos_value;
custom.spr[0].ctl = ctl_value;

/* Set sprite colors to visible values */
custom.color[17] = 0x0FFF;  /* Bright white */
custom.color[18] = 0x0F00;  /* Blue */

2. The Overlapping Multiplex

Repositioning a sprite before its previous instance has finished displaying. The sprite channel can only hold one position at a time — setting a new start position while the old one is still active causes visual artifacts.

Broken:

; Sprite 0 starts at line 50, runs to line 100
dc.w    $0140,$3200           ; SPR0POS: start=50
dc.w    $0142,$6400           ; SPR0CTL: stop=100

; But reposition at line 70 — before line 100 stop!
dc.w    $8046,$FFFE           ; WAIT line 70
dc.w    $0140,$4628           ; SPR0POS: start=70 ← CONFLICT!

Fixed:

; Let first instance finish (stop line 100), then reposition
dc.w    $8064,$FFFE           ; WAIT line 100 (after stop)
dc.w    $0140,$6428           ; SPR0POS: start=100, x=40
dc.w    $0142,$8C28           ; SPR0CTL: stop=140, x=40

3. The Misaligned Attached Pair

Attached sprites require both even and odd sprites to have identical positions. Even a 1-pixel offset breaks the 15-color illusion.

Broken:

dc.w    $0140,$3200           ; SPR0POS: line 50, x=0
dc.w    $0148,$3201           ; SPR1POS: line 50, x=1 ← OFFSET!

Fixed:

dc.w    $0140,$3200           ; SPR0POS: line 50, x=0
dc.w    $0148,$3200           ; SPR1POS: line 50, x=0 ← SAME!

4. The Sprite DMA Starvation

Disabling sprite DMA (DMACON bit 3) but still writing to sprite position registers. Without DMA, no sprite data is fetched — the registers are set but nothing appears.

Broken:

custom.dmacon = 0x8100;  /* Enable DMA, but forgot sprite bit (bit 3) */
/* Sprites won't display even though positions are set */

Fixed:

custom.dmacon = 0x8100 | 0x0008;  /* Enable DMA + sprite DMA (SPR0-DMA) */
/* Or simply: */
custom.dmacon = 0x81FF;  /* Enable all DMA channels */

5. The AGA Width Trap

AGA allows 32-pixel and 64-pixel wide sprites via FMODE settings. But the sprite data format changes — wider sprites need more words per line. Using OCS-format sprite data with AGA wide-sprite settings produces garbage.

Broken:

/* Set 32-pixel sprite mode but provide 16-pixel data */
custom.fmode = 0x0030;  /* SPR_FMODE = 32-bit fetch */
/* Sprite data still only 2 words/line (16px) → garbage */

Fixed:

if (aga_detected) {
    custom.fmode = 0x0030;  /* 32-bit sprite fetch */
    /* Provide 4 words per line (32px × 2 bitplanes) */
} else {
    custom.fmode = 0x0000;  /* OCS: 16-bit fetch */
    /* Provide 2 words per line (16px × 2 bitplanes) */
}

Decision Guide

flowchart TD
    START[Need sprite-based effect] --> Q1{Moving objects or<br/>image construction?}
    Q1 -->|Moving objects| Q2{How many simultaneous<br/>on one scanline?}
    Q1 -->|Image construction| SBI[Sprite-Built Image]

    Q2 -->|≤8| Q3{Need 15 colors?}
    Q2 -->|>8| Q4{Can tolerate<br/>vertical gaps?}

    Q3 -->|Yes, ≤4 objects| ATT[Attached Sprites<br/>4 pairs × 15 colors]
    Q3 -->|No, 3 colors fine| BASIC[Basic 8 sprites<br/>3 colors each]

    Q4 -->|Yes| MUX[Sprite Multiplexing<br/>Reuse channels vertically]
    Q4 -->|No| BLIT[Use Blitter BOBs instead<br/>No sprite limit]

    SBI --> Q5{Full screen or overlay?}
    Q5 -->|Overlay| OVL[Sprite overlay on playfield]
    Q5 -->|Full width| Q6{Need 15 colors?}
    Q6 -->|Yes| ASBI[Attached sprite-built image<br/>64px wide × 15 colors]
    Q6 -->|No| BSBI[Basic sprite-built image<br/>128px wide × 3 colors]

Performance Analysis

Sprite DMA Cost (Fixed, Always Paid)

Configuration DMA Slots/Scanline Notes
All 8 sprites enabled 16 Fixed cost regardless of use
Sprites disabled 0 Can reclaim 16 slots for other DMA
4 sprites (0-3) 8 Common for 2 attached pairs

Multiplexing Overhead

Operation Copper Words DMA Slots Notes
Reposition (POS+CTL) 4 4 WAIT + MOVE × 2
With new data pointers 8 8 POS + CTL + DATA/DATB ptrs
Full reposition + colors 12 12 Above + 2 color register writes

Tip

Each sprite reposition costs 412 DMA slots per scanline at the reposition line. Plan multiplexing points carefully — avoid repositioning all 8 sprites on the same scanline.


Historical Timeline

timeline
    title Sprite Techniques Evolution
    1985 : Amiga launch — 8 sprites, 3 colors each
         : Games use sprites for player, enemies, bullets
    1987 : First sprite multiplexing demos appear
    1988 : Kefrens "MegaDemo" — advanced sprite multiplexing
         : Scoopex uses sprites for copper-bar overlays
    1989 : Sprite-built logos in demos
         : First attached sprite demos (15-color objects)
    1990 : Complex sprite/Blt synchronization
         : Sprite multiplexing in commercial games
    1991 : Full-screen sprite effects in demos
         : Sprite-based "virtual BOBs" for performance
    1992 : AGA sprite enhancements: 32/64px wide
         : Melon Dezign uses wide sprites for design
    1993 : Advanced attached sprite animations
    1994 : Sprite techniques combined with chunky pixels
    2000+ : Demoscene continues optimizing sprite techniques
         : MiSTer ensures cycle-accurate sprite DMA

Modern Analogies

Amiga Sprite Concept Modern Equivalent Why It Maps
Sprite multiplexing Instance rendering / draw-call batching Same object data reused at different positions
Sprite-built image GPU sprite atlas / billboard rendering Multiple small textures composited into scene
Attached sprites Alpha channel / RGBA4444 textures More color depth by combining data from two sources
SPRxPOS reposition Transform matrix update Changing draw position per instance
Sprite priority (BPLCON2) Z-sort / depth buffer Controls which layer appears on top
Sprite DMA GPU texture fetch Autonomous hardware fetches pixel data
16px width limit Texture dimension constraints Hardware-imposed maximum per unit
Color register per sprite Per-object palette / uniform Color lookup specific to each sprite

Use Cases

Use Case Technique Notable Examples
Game player/enemy objects Basic sprites + multiplex Turrican, Shadow of the Beast
Mouse pointer SPR0 (reserved by OS) Workbench
Demo logo overlay Sprite-built image Melon Dezign, Sanity
Large colorful objects Attached sprites (15-color) Kefrens, Phenomena demos
Parallax background layer Sprite overlay behind playfield Lionheart, Leander
Status bar icons Fixed sprites Many games
Sprite-sprite collision CLXCON/CLXDAT hardware Turrican (sprite collision detection)
Color-cycling objects Per-line sprite color changes Numerous demos

FPGA / Emulation Impact

Concern Impact Notes
Sprite DMA timing Must fetch exactly 2 words per sprite per scanline Minimig/MiSTer implement precise DMA slot scheduler
Attached sprite decoding Denise must combine even+odd data correctly 4-bitplane lookup from 2 sprite channels
SPRxPOS/CTL latency Position changes take effect next line Must match real hardware delay
CLXCON collision detection Hardware collision between sprites/bitplanes Required for games like Turrican
AGA FMODE sprite width 32/64px sprites change data format FMODE.SPR_FMODE must be tracked
Sprite data staging Denise latches data at end of scanline for next line Pipeline behavior must be emulated

FAQ

Q: How many sprites can I display on screen at once? A: 8 per scanline (hardware limit). With multiplexing, you can reuse each channel multiple times vertically — a common demo technique shows 30+ "sprites" using 8 channels multiplexed 4 times each.

Q: Can sprites be wider than 16 pixels? A: On OCS/ECS, no — 16 pixels is the fixed hardware width. On AGA, FMODE bits can set 16, 32, or 64 pixel widths. For OCS, wider objects require Blitter BOBs or multiple sprites side by side.

Q: Why does SPR0 sometimes conflict with the mouse pointer? A: Intuition reserves SPR0 and SPR1 for the mouse pointer. If you take over the hardware (demos), you can use all 8 sprites. If running under the OS, use only SPR2-SPR7 or use the ExtSprite API (V39+) which cooperates with Intuition.

Q: What happens when sprites overlap? A: Lower-numbered sprites have priority — SPR0 appears on top of SPR1, which appears on top of SPR2, etc. The sprite/playfield priority is controlled by BPLCON2.

Q: Can I use sprites and Blitter BOBs together? A: Yes. Sprites are DMA-driven and cost zero CPU time. BOBs are software-driven (Blitter copies) and cost Blitter DMA time. Many games use sprites for small, frequently-moving objects and BOBs for larger or more colorful ones.

Q: What is CLXCON and how does collision detection work? A: CLXCON ($DFF098) configures which sprite and bitplane bits are included in collision detection. CLXDAT ($DFF00E) reports detected collisions. The hardware compares sprite and bitplane data in real-time and sets bits when matching pixels overlap. See Sprites for full details.


References

External Resources

  • Amiga Hardware Reference Manual — Chapter 5: Sprites
  • Amiga Graphics Archivehttps://amiga.lychesis.net — Sprite multiplexing and copper-driven sprite repositioning in commercial games
  • Scoopex Amiga Hardware Programming (Photon) — YouTube playlist — Sprite programming episodes covering hardware setup, multiplexing, and attached sprites
  • Pouet.nethttps://www.pouet.net — Sprite-based demo releases
  • Demozoohttps://demozoo.org — Demoscene encyclopedia
  • AMIGA Machine Code Tutorial — Lexington — Sprite programming from scratch