mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-12 16:16:28 +00:00
More content added
This commit is contained in:
parent
5fac29ccd5
commit
8133b3a6cb
90 changed files with 7794 additions and 705 deletions
|
|
@ -1,8 +1,16 @@
|
|||
[← Home](../README.md) · [Overview](README.md)
|
||||
|
||||
# Amiga Hardware Models Reference
|
||||
# Amiga Hardware Models — Per-Model Architecture, Chip Inventory, and Expansion
|
||||
|
||||
## Model Specification Table
|
||||
## Overview
|
||||
|
||||
The Amiga line spans nine years (1985–1994), three chipset generations, and four CPU families. While every model shares the same core custom-chip architecture — a DMA-driven display system with dedicated audio, blitter, and copper coprocessors — the implementation details vary dramatically. The A1000's 256 KB Chip RAM and side-car expansion bears little resemblance to the A4000's 32-bit Zorro III bus and on-board SCSI. Understanding these differences is essential for developers writing software that must run across the entire family, for reverse engineers analyzing per-model binaries, and for hardware restorers diagnosing chipset compatibility.
|
||||
|
||||
This article provides a per-model architectural breakdown: which custom chips are present, how memory is mapped, what expansion options exist, and which factory jumpers or revisions affect behavior. For the underlying memory-type concepts (Chip vs Fast vs Slow RAM), see [memory_types.md](../01_hardware/common/memory_types.md). For address-space maps, see [address_space.md](../01_hardware/common/address_space.md).
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Table
|
||||
|
||||
| Model | Year | CPU | MHz | Chipset | Chip RAM | ROM | Expansion |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
|
|
@ -11,36 +19,573 @@
|
|||
| A2000 | 1987 | 68000 | 7.14 | OCS/ECS | 512 KB–1 MB | 256/512 KB | Zorro II, ISA, CPU slot |
|
||||
| A500+ | 1991 | 68000 | 7.09 | ECS | 1 MB | 512 KB | Edge connector |
|
||||
| A600 | 1992 | 68000 | 7.09 | ECS | 1 MB | 512 KB | PCMCIA, IDE, trapdoor |
|
||||
| A3000 | 1990 | 68030 | 16/25 | ECS | 1 MB | 512 KB | Zorro III, ISA, SCSI |
|
||||
| A1200 | 1992 | 68020 | 14.18 | AGA | 2 MB | 512 KB | PCMCIA, IDE, trapdoor |
|
||||
| A3000 | 1990 | 68030 | 16/25 | ECS | 1–2 MB | 512 KB | Zorro III, ISA, SCSI |
|
||||
| A1200 | 1992 | 68EC020 | 14.18 | AGA | 2 MB | 512 KB | PCMCIA, IDE, trapdoor |
|
||||
| A4000 | 1992 | 68030/040 | 25 | AGA | 2 MB | 512 KB | Zorro III, IDE |
|
||||
| A4000T | 1994 | 68040/060 | 25 | AGA | 2 MB | 512 KB | Zorro III, SCSI |
|
||||
| CD32 | 1993 | 68020 | 14.18 | AGA | 2 MB | 512 KB | SX-1, CD-ROM |
|
||||
| CDTV | 1991 | 68000 | 7.09 | OCS | 1 MB | 256/512 KB | Internal A2000-compatible |
|
||||
| CD32 | 1993 | 68EC020 | 14.18 | AGA | 2 MB | 512 KB | FMV slot (SX-1 expander) |
|
||||
|
||||
---
|
||||
|
||||
## Chipset Generations and Custom Chip Inventory
|
||||
|
||||
### OCS (Original Chip Set) — 1985
|
||||
|
||||
| Chip | Part Number | Function | Notes |
|
||||
|---|---|---|---|
|
||||
| **Agnus** | 8361 (NTSC) / 8367 (PAL) | DMA scheduler, blitter, copper, disk | 18-bit address (512 KB Chip RAM max) |
|
||||
| **Denise** | 8362 | Display encoder, sprite DMA, mouse | 320×200 to 640×400 interlace |
|
||||
| **Paula** | 8364 | Audio, floppy, serial | 4-channel DMA audio |
|
||||
| **Gary** | 5718 | System glue, bus control | Present on A500/A2000 only |
|
||||
| **Budgie** | — | A1000-specific glue | Replaced by Gary in later models |
|
||||
|
||||
### ECS (Enhanced Chip Set) — 1990
|
||||
|
||||
| Chip | Part Number | Function | Notes |
|
||||
|---|---|---|---|
|
||||
| **Agnus** | 8372 (Fat) / 8372A (Super) | DMA scheduler | Fat: 1 MB address; Super: 2 MB address |
|
||||
| **Denise** | 8373 (ECS) | Display encoder | Supports 640×480 productivity modes |
|
||||
| **Paula** | 8364 (unchanged) | Audio, floppy, serial | Same as OCS |
|
||||
| **Gary** | 5718 | System glue | Unchanged from OCS |
|
||||
|
||||
### AGA (Advanced Graphics Architecture) — 1992
|
||||
|
||||
| Chip | Part Number | Function | Notes |
|
||||
|---|---|---|---|
|
||||
| **Alice** | 8374 | DMA scheduler, blitter (64-bit), copper | 2 MB Chip RAM, 64-bit blitter fetches |
|
||||
| **Lisa** | 8375 | Display encoder | 24-bit color, HAM8, up to 1280×512 |
|
||||
| **Paula** | 8364 (unchanged) | Audio, floppy, serial | Still 4-channel, 8-bit DMA audio |
|
||||
| **Akiko** | — | CD32 only | Chunky-to-planar converter, CD-ROM glue |
|
||||
|
||||
---
|
||||
|
||||
## Per-Model Deep Dives
|
||||
|
||||
### A1000 (1985) — The Original
|
||||
|
||||
The A1000 is the progenitor. Its most distinctive feature is the **Writable Control Store (WCS)** — a 256 KB pseudo-ROM loaded from disk at boot. The Kickstart is not in a physical ROM chip; it is copied from a floppy into RAM that the CPU executes from at `$FC0000`.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ A1000 Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68000 @ 7.09 MHz │
|
||||
│ 256 KB Chip RAM (max 512 KB) │
|
||||
│ 256 KB WCS (Kickstart loaded disk) │
|
||||
│ OCS: Agnus 8361/8367, Denise 8362 │
|
||||
│ Budgie (glue logic) │
|
||||
│ Side expansion port (86-pin) │
|
||||
│ No internal drive bays (ext only) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| Chip RAM | 256 KB stock, 512 KB max (internal expansion) |
|
||||
| Kickstart | Loaded from "Kickstart" disk to WCS; not socketed ROM |
|
||||
| Expansion | Side expansion (86-pin, similar to A500 but different) |
|
||||
| Video | 23-pin RGB analog + composite + monochrome |
|
||||
| Keyboard | External, proprietary interface |
|
||||
|
||||
> **Developer note**: The A1000's WCS means `execbase->MaxLocMem` reports less than 512 KB even with full expansion, because `$FC0000`–`$FFFFFF` is occupied by WCS. Software that probes for available memory rather than hardcoding addresses will run correctly.
|
||||
|
||||
---
|
||||
|
||||
### A500 (1987) — The Icon
|
||||
|
||||
The best-selling Amiga model. Internally, it is an A1000 chipset repackaged into a keyboard case with a single floppy drive and a trapdoor expansion slot.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ A500 Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68000 @ 7.09 MHz │
|
||||
│ 512 KB Chip RAM (max 1 MB) │
|
||||
│ 256 KB Kickstart ROM (socketed) │
|
||||
│ OCS: Agnus 8361/8367, Denise 8362 │
|
||||
│ Gary (system glue) │
|
||||
│ Trapdoor expansion (86-pin) │
|
||||
│ Side expansion (86-pin) │
|
||||
│ Internal floppy bay (3.5") │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| Chip RAM | 512 KB stock, 1 MB max (trapdoor expansion) |
|
||||
| Slow RAM | 512 KB at `$C00000` via trapdoor (Agnus-dependent) |
|
||||
| Kickstart | 256 KB ROM in socket; upgradeable to 512 KB ECS ROM |
|
||||
| Trapdoor | 86-pin: Chip RAM expansion, RTC option, 68000 relocator for accelerators |
|
||||
| Side slot | 86-pin: Zorro II-style expansion (A590 HD, etc.) |
|
||||
|
||||
**Key jumper/revision note**: Early A500s (Rev 3–5) have the **Agnus 8361** (18-bit address, 512 KB max Chip). Later revisions (Rev 6+) sometimes ship with **Agnus 8372A** (Super Fat Agnus), allowing 1 MB or 2 MB Chip RAM with modifications. The Denise chip is always 8362 (OCS) unless swapped by user.
|
||||
|
||||
---
|
||||
|
||||
### A2000 (1987) — The Big Box
|
||||
|
||||
The A2000 is an A500 on a larger motherboard with five Zorro II slots, an ISA bridge, and a CPU slot. It was Commodore's attempt to capture the professional desktop market.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ A2000 Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68000 @ 7.09 MHz │
|
||||
│ 512 KB–1 MB Chip RAM │
|
||||
│ 256/512 KB Kickstart ROM │
|
||||
│ OCS → ECS (Rev 6.0+) │
|
||||
│ Gary + Buster (Zorro II arbiter) │
|
||||
│ 5× Zorro II slots │
|
||||
│ 4× ISA slots (bridge) │
|
||||
│ CPU slot (68000 relocator) │
|
||||
│ Internal drive bays (2× 3.5") │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| Chip RAM | 512 KB (early), 1 MB (Rev 6+ with Fat Agnus), up to 2 MB with Super Agnus mod |
|
||||
| Fast RAM | Zorro II cards up to 8 MB; CPU-slot accelerators up to 128 MB |
|
||||
| Kickstart | 256 KB (early), 512 KB (Rev 6+); socketed, easily swapped |
|
||||
| Buster | Zorro II bus arbiter chip; Rev 7–9 fix DMA bugs |
|
||||
| ISA bridge | Passive bridge; requires ISA card with Amiga driver |
|
||||
|
||||
**Revision history**:
|
||||
- **Rev 4.x**: OCS, 512 KB Chip RAM, 256 KB ROM
|
||||
- **Rev 6.x**: ECS (Fat Agnus 8372), 1 MB Chip RAM, 512 KB ROM
|
||||
- **Rev 6.5**: Minor Buster fix
|
||||
- **Rev 6.6**: Final Commodore revision; best compatibility
|
||||
|
||||
> **Developer note**: The A2000's Buster chip has known DMA arbitration bugs in early revisions. Zorro II cards that use DMA (e.g., GVP SCSI) may hang on Rev 4.x boards. Software cannot detect this; it is a hardware compatibility issue for users.
|
||||
|
||||
---
|
||||
|
||||
### A500+ (1991) — ECS in a Budget Shell
|
||||
|
||||
Essentially an A500 with ECS chipset and 1 MB Chip RAM standard. It was a stopgap model between the A500 and A600.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ A500+ Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68000 @ 7.09 MHz │
|
||||
│ 1 MB Chip RAM (max 2 MB) │
|
||||
│ 512 KB Kickstart ROM │
|
||||
│ ECS: Fat Agnus 8372, Denise 8373 │
|
||||
│ Gary (system glue) │
|
||||
│ Trapdoor + side expansion │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| Chip RAM | 1 MB stock; 2 MB max with trapdoor mod (requires Agnus 8372A) |
|
||||
| Kickstart | 512 KB ROM (Kickstart 2.04) |
|
||||
| Denise | 8373 (ECS) — supports 640×480 productivity modes |
|
||||
| Clock | Battery-backed RTC standard (not optional as in A500) |
|
||||
|
||||
**The "2 MB Chip" mod**: The A500+ can reach 2 MB Chip RAM by replacing the 8372 Fat Agnus with a **8372A Super Agnus** and adding 1 MB of RAM chips on the motherboard. The Fat Agnus supports only 1 MB addressing; Super Agnus extends to 2 MB.
|
||||
|
||||
---
|
||||
|
||||
### A600 (1992) — The Compact
|
||||
|
||||
Commodore's attempt at a portable-friendly Amiga. It removes the numeric keypad, uses a 2.5" hard drive, and adds PCMCIA and internal IDE.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ A600 Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68000 @ 7.09 MHz │
|
||||
│ 1 MB Chip RAM (max 2 MB) │
|
||||
│ 512 KB Kickstart ROM │
|
||||
│ ECS: Super Agnus 8372A, Denise 8373│
|
||||
│ Gayle (IDE + PCMCIA + RAM glue) │
|
||||
│ Internal 2.5" IDE bay │
|
||||
│ PCMCIA Type II slot │
|
||||
│ Trapdoor expansion (150-pin) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| Chip RAM | 1 MB stock; 2 MB max (trapdoor accelerator or Chip RAM expansion) |
|
||||
| IDE | Internal 2.5" IDE via **Gayle** chip; up to 2 devices |
|
||||
| PCMCIA | Type II slot; memory cards (up to 4 MB Fast RAM), Ethernet, modems |
|
||||
| Gayle | Replaces Gary; integrates IDE controller, PCMCIA interface, and memory decoding |
|
||||
| Kickstart | 512 KB ROM (Kickstart 2.05) |
|
||||
|
||||
> **Developer note**: The A600's Gayle chip has a known bug with IDE interrupt handling under heavy DMA load. Some CF card adapters require specific driver workarounds. PCMCIA memory cards map as Fast RAM at `$600000` and can conflict with accelerator memory.
|
||||
|
||||
---
|
||||
|
||||
### A3000 (1990) — The Professional
|
||||
|
||||
The first 32-bit Amiga with Zorro III, built-in SCSI, and a 68030 CPU. It was designed for the UNIX workstation market and is the most expandable ECS machine.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ A3000 Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68030 @ 16 or 25 MHz │
|
||||
│ 1–2 MB Chip RAM │
|
||||
│ 4–16 MB Fast RAM (on-board) │
|
||||
│ 512 KB Kickstart ROM │
|
||||
│ ECS: Super Agnus 8372A, Denise 8373│
|
||||
│ Buster III (Zorro III arbiter) │
|
||||
│ 4× Zorro III slots │
|
||||
│ 4× ISA slots (bridge) │
|
||||
│ Internal SCSI (WD33C93A) │
|
||||
│ CPU slot (68000-compatible) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| Chip RAM | 1 MB (early), 2 MB (later); fixed on-board |
|
||||
| Fast RAM | 4 MB standard, 16 MB max (ZIP chips on motherboard) |
|
||||
| Zorro III | 32-bit, up to 256 MB address space; backwards compatible with Zorro II |
|
||||
| SCSI | WD33C93A controller; DMA-capable; internal 50-pin + external DB25 |
|
||||
| Kickstart | 512 KB ROM; unique "superkickstart" mode for ROM development |
|
||||
| Video | 23-pin RGB + monochrome; optional framebuffer card in Zorro III slot |
|
||||
|
||||
**The "Big Box" advantage**: Unlike the A500/A600, the A3000 has no trapdoor. All expansion is via Zorro III slots or the CPU slot. The on-board Fast RAM uses **ZIP (zig-zag inline package)** DRAM, which is difficult to source today — many A3000 owners use Zorro III RAM cards instead.
|
||||
|
||||
---
|
||||
|
||||
### A1200 (1992) — The Affordable AGA
|
||||
|
||||
The most popular AGA model. It packs Alice, Lisa, and 2 MB Chip RAM into a compact keyboard case similar to the A600.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ A1200 Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68EC020 @ 14.18 MHz │
|
||||
│ 2 MB Chip RAM (fixed) │
|
||||
│ 512 KB Kickstart ROM │
|
||||
│ AGA: Alice 8374, Lisa 8375, Paula │
|
||||
│ Gayle (IDE + PCMCIA) │
|
||||
│ Internal 2.5" IDE bay │
|
||||
│ PCMCIA Type II slot │
|
||||
│ Trapdoor expansion (150-pin) │
|
||||
│ 22-pin RGB video (VGA-compatible) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| CPU | 68EC020 @ 14.18 MHz (no MMU, no FPU) |
|
||||
| Chip RAM | 2 MB fixed; not expandable on motherboard |
|
||||
| Fast RAM | Trapdoor accelerators (up to 256 MB); PCMCIA memory (up to 4 MB) |
|
||||
| IDE | Gayle-based, same as A600; 2.5" internal + CF adapter popular |
|
||||
| PCMCIA | Type II; same caveats as A600 regarding memory mapping |
|
||||
| Video | 23-pin RGB (VGA-compatible scan rates) + composite; supports all AGA modes |
|
||||
| Kickstart | 512 KB ROM (Kickstart 3.0/3.1) |
|
||||
|
||||
**The 150-pin trapdoor**: This connector carries the full 68020 bus, allowing accelerators to replace the CPU. Popular trapdoor cards include the Blizzard 1230/1240/1260 series, which provide 68030/040/060 CPUs, Fast RAM, and optional SCSI.
|
||||
|
||||
> **Developer note**: The A1200's 68EC020 has **no MMU**. Software using virtual memory (e.g., Enforcer, GigaMem) requires an accelerator with a full 68030/040/060. The `EC` designation means the on-chip MMU is absent.
|
||||
|
||||
---
|
||||
|
||||
### A4000 (1992) — The Desktop AGA
|
||||
|
||||
The A4000 is the big-box AGA counterpart to the A1200. It uses the same Alice/Lisa/Paula chipset but adds Zorro III slots, a desktop case, and either a 68030 or 68040 CPU.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ A4000 Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68030 @ 25 MHz or 68040 @ 25 MHz │
|
||||
│ 2 MB Chip RAM (fixed) │
|
||||
│ 4–16 MB Fast RAM (on-board) │
|
||||
│ 512 KB Kickstart ROM │
|
||||
│ AGA: Alice 8374, Lisa 8375, Paula │
|
||||
│ Buster III (Zorro III arbiter) │
|
||||
│ 5× Zorro III slots │
|
||||
│ 4× ISA slots (bridge) │
|
||||
│ Internal IDE (Gayle-based) │
|
||||
│ CPU slot (68000-compatible) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| CPU | 68030 @ 25 MHz (A4000/030) or 68040 @ 25 MHz (A4000/040) |
|
||||
| Chip RAM | 2 MB fixed |
|
||||
| Fast RAM | 4 MB (030 model) or 16 MB (040 model) on SIMM slots |
|
||||
| IDE | Gayle-based internal IDE; two 3.5" bays |
|
||||
| Zorro III | 5 slots; same 32-bit bus as A3000 |
|
||||
| Kickstart | 512 KB ROM; some models include 512 KB extended ROM for SCSI boot |
|
||||
| Video | 23-pin RGB + monochrome; supports all AGA modes |
|
||||
|
||||
**A4000/040 note**: The 68040 model includes a full MMU and FPU (Line-F emulation not required for basic FPU ops). However, the 68040's FPU lacks transcendental instructions; AmigaOS loads `68040.library` automatically to emulate these via Line-F traps.
|
||||
|
||||
---
|
||||
|
||||
### A4000T (1994) — The Tower
|
||||
|
||||
The final Commodore Amiga. A tower-case variant of the A4000 with SCSI replacing IDE as the primary storage interface.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ A4000T Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68040 @ 25 MHz or 68060 @ 50 MHz │
|
||||
│ 2 MB Chip RAM (fixed) │
|
||||
│ 16 MB Fast RAM (on-board SIMMs) │
|
||||
│ 512 KB Kickstart ROM │
|
||||
│ AGA: Alice 8374, Lisa 8375, Paula │
|
||||
│ Buster III + CyberStorm SCSI │
|
||||
│ 5× Zorro III slots │
|
||||
│ Internal SCSI-2 (NCR53C710) │
|
||||
│ CPU slot │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| CPU | 68040 @ 25 MHz or 68060 @ 50 MHz (late production) |
|
||||
| SCSI | NCR53C710 SCSI-2 controller; DMA-capable; internal + external |
|
||||
| IDE | Not present as primary; some third-party tower conversions add IDE |
|
||||
| Case | Full tower; 6× 5.25" + 3× 3.5" bays |
|
||||
| Kickstart | 512 KB ROM + optional extended ROM for SCSI boot |
|
||||
|
||||
> **Developer note**: The 68060 revision of the A4000T is extremely rare. Most A4000T units are 68040-based. The 68060 requires `68060.library` for FPU emulation and cache management, similar to the 68040.
|
||||
|
||||
---
|
||||
|
||||
### CDTV (1991) — The Multimedia Pioneer
|
||||
|
||||
Commodore's CD-ROM-based multimedia console, based on A2000 hardware. It predates the CD32 and was aimed at the living room rather than the desktop.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ CDTV Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68000 @ 7.09 MHz │
|
||||
│ 1 MB Chip RAM │
|
||||
│ 256/512 KB Kickstart ROM │
|
||||
│ OCS (Agnus 8361/8372) │
|
||||
│ Custom CD-ROM controller │
|
||||
│ NVRAM (clock + settings) │
|
||||
│ IR remote receiver │
|
||||
│ No keyboard/mouse ports (ext only) │
|
||||
│ Internal expansion (A2000-compat) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| Chip RAM | 1 MB stock; 2 MB with Super Agnus mod |
|
||||
| CD-ROM | Proprietary Commodore CD-ROM drive; caddy-loaded |
|
||||
| NVRAM | 32 bytes battery-backed RAM for settings |
|
||||
| IR | Infrared remote control; receiver on front panel |
|
||||
| Kickstart | Modified Kickstart with CDTV extensions |
|
||||
| Expansion | Internal A2000-compatible slots (memory, hard cards) |
|
||||
|
||||
---
|
||||
|
||||
### CD32 (1993) — The AGA Console
|
||||
|
||||
The world's first 32-bit game console. It is essentially an A1200 without a keyboard, with a CD-ROM drive, and the **Akiko** chip for chunky-to-planar conversion.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ CD32 Architecture │
|
||||
├─────────────────────────────────────┤
|
||||
│ 68EC020 @ 14.18 MHz │
|
||||
│ 2 MB Chip RAM (fixed) │
|
||||
│ 512 KB Kickstart ROM │
|
||||
│ AGA: Alice 8374, Lisa 8375, Paula │
|
||||
│ Akiko (C2P + CD-ROM + NVRAM glue) │
|
||||
│ Custom CD-ROM drive (tray) │
|
||||
│ No keyboard port (ext via SX-1) │
|
||||
│ FMV cartridge slot (MPEG-1) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Spec | Value |
|
||||
|---|---|
|
||||
| Chip RAM | 2 MB fixed |
|
||||
| Akiko | Chunky-to-planar converter (C2P); accelerates 3D rendering by ~4× |
|
||||
| CD-ROM | Double-speed; proprietary controller via Akiko |
|
||||
| NVRAM | 1 KB battery-backed for game saves |
|
||||
| Expansion | SX-1 or SX-32 add-on provides keyboard, floppy, IDE, and additional RAM |
|
||||
| FMV | Full Motion Video cartridge port for MPEG-1 decoding |
|
||||
|
||||
> **Developer note**: The CD32's Akiko C2P is a hardware chunky-to-planar converter that transforms 8-bit indexed pixels into Amiga bitplane format. Games using this (e.g., *Doom*, *Gloom*) achieve playable frame rates impossible on a stock A1200 without Akiko. See [akiko_cd32.md](../01_hardware/aga_a1200_a4000/akiko_cd32.md) for programming details.
|
||||
|
||||
---
|
||||
|
||||
## CPU Feature Matrix
|
||||
|
||||
| CPU | Bus | Address | I-Cache | D-Cache | MMU | FPU |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 68000 | 16-bit | 24-bit | — | — | — | External 68881 |
|
||||
| 68020 | 32-bit | 32-bit | 256 B direct | — | External 68851 | External 68881/2 |
|
||||
| 68030 | 32-bit | 32-bit | 256 B | 256 B | On-chip | External 68882 |
|
||||
| 68040 | 32-bit | 32-bit | 4 KB 4-way | 4 KB 4-way | On-chip | On-chip (partial) |
|
||||
| 68060 | 32-bit | 32-bit | 8 KB 4-way | 8 KB 4-way | On-chip | On-chip (partial) |
|
||||
| CPU | Bus | Address | I-Cache | D-Cache | MMU | FPU | Models |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 68000 | 16-bit | 24-bit | — | — | — | External 68881 | A1000, A500, A500+, A600, A2000, CDTV |
|
||||
| 68EC020 | 32-bit | 32-bit | 256 B direct | — | — | — | A1200, CD32 |
|
||||
| 68020 | 32-bit | 32-bit | 256 B direct | — | External 68851 | External 68881/2 | A1200 (accelerators) |
|
||||
| 68030 | 32-bit | 32-bit | 256 B | 256 B | On-chip | External 68882 | A3000, A4000/030 |
|
||||
| 68040 | 32-bit | 32-bit | 4 KB 4-way | 4 KB 4-way | On-chip | On-chip (partial) | A4000/040, A4000T |
|
||||
| 68060 | 32-bit | 32-bit | 8 KB 4-way | 8 KB 4-way | On-chip | On-chip (partial) | A4000T (rare) |
|
||||
|
||||
> [!NOTE]
|
||||
> 68040 and 68060 have on-chip FPUs that omit transcendental instructions. AmigaOS provides `68040.library` and `68060.library` to trap the missing opcodes via Line-F emulation.
|
||||
> 68040 and 68060 have on-chip FPUs that omit transcendental instructions. AmigaOS provides `68040.library` and `68060.library` to trap the missing opcodes via Line-F emulation. See [68040_68060_libraries.md](../15_fpu_mmu_cache/68040_68060_libraries.md).
|
||||
|
||||
## Kickstart ROM Sizes
|
||||
---
|
||||
|
||||
## Kickstart ROM Evolution
|
||||
|
||||
| OS Version | ROM Size | Part | Models |
|
||||
|---|---|---|---|
|
||||
| 1.2 / 1.3 | 256 KB | Single | A500, A2000 |
|
||||
| 1.0 / 1.1 | 256 KB | WCS only | A1000 |
|
||||
| 1.2 / 1.3 | 256 KB | Single | A500, A2000, CDTV |
|
||||
| 2.04 | 512 KB | Single | A500+, A600, A3000 |
|
||||
| 3.0 / 3.1 | 512 KB | Single | A1200, A4000 |
|
||||
| 3.1 | 512 KB + 512 KB Ext | Pair | A4000 (with ext ROM) |
|
||||
| 3.0 / 3.1 | 512 KB | Single | A1200, A4000, CD32 |
|
||||
| 3.1 | 512 KB + 512 KB Ext | Pair | A4000 (with ext ROM for SCSI boot) |
|
||||
|
||||
---
|
||||
|
||||
## Chipset Detection at Runtime
|
||||
|
||||
Software can detect the chipset generation programmatically:
|
||||
|
||||
```c
|
||||
#include <graphics/gfxbase.h>
|
||||
#include <proto/graphics.h>
|
||||
|
||||
struct GfxBase *GfxBase;
|
||||
|
||||
void DetectChipset(void)
|
||||
{
|
||||
GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0);
|
||||
if (!GfxBase) return;
|
||||
|
||||
ULONG chipRev = GfxBase->ChipRevBits;
|
||||
|
||||
if (chipRev & GFXF_AA_ALICE) /* AGA detected */
|
||||
{
|
||||
/* Alice + Lisa present: AGA features available */
|
||||
}
|
||||
else if (chipRev & GFXF_HR_DENISE) /* ECS detected */
|
||||
{
|
||||
/* ECS Denise present */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* OCS only */
|
||||
}
|
||||
|
||||
/* Chip RAM size via exec */
|
||||
ULONG chipTotal = AvailMem(MEMF_CHIP | MEMF_TOTAL);
|
||||
/* 256 KB, 512 KB, 1 MB, or 2 MB */
|
||||
|
||||
CloseLibrary((struct Library *)GfxBase);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decision Guide: Which Model to Target?
|
||||
|
||||
| Target Market | Minimum Model | Recommended | Notes |
|
||||
|---|---|---|---|
|
||||
| **Maximum compatibility** | A500 (OCS, 512 KB) | A500 + 1 MB Chip | Runs on every Amiga ever made |
|
||||
| **ECS features** | A500+ / A600 | A600 | 1 MB Chip, productivity modes |
|
||||
| **AGA required** | A1200 | A1200 + Fast RAM | 2 MB Chip is baseline for AGA |
|
||||
| **Professional/desktop** | A3000 | A4000 | Zorro III, SCSI, big box |
|
||||
| **Console/CD-only** | CD32 | CD32 | Akiko C2P, no keyboard |
|
||||
|
||||
---
|
||||
|
||||
## Historical Context & Competitive Landscape
|
||||
|
||||
### 1985: The A1000 vs. Its Rivals
|
||||
|
||||
| Platform | CPU | RAM | Custom Chips | Multitasking | Price (1985) |
|
||||
|---|---|---|---|---|---|
|
||||
| **Amiga 1000** | 68000 @ 7 MHz | 256 KB | OCS (Agnus/Denise/Paula) | Preemptive (Exec) | $1,295 |
|
||||
| **Atari ST** | 68000 @ 8 MHz | 512 KB | None (Shifter = simple shift register) | Cooperative (TOS) | $799 |
|
||||
| **Macintosh** | 68000 @ 7.8 MHz | 128 KB | None | Cooperative | $2,495 |
|
||||
| **IBM PC/AT** | 80286 @ 6 MHz | 256 KB | None (ISA bus) | None (MS-DOS) | $3,995 |
|
||||
|
||||
The A1000's custom chipset was unprecedented. No competitor offered hardware sprites, DMA audio, blitter graphics, and a copper coprocessor in a single integrated design. The trade-off was complexity — developers had to learn the custom chips to extract performance.
|
||||
|
||||
### 1992: The AGA Generation
|
||||
|
||||
By 1992, the market had shifted:
|
||||
|
||||
| Platform | CPU | RAM | Graphics | Audio | Price |
|
||||
|---|---|---|---|---|---|
|
||||
| **Amiga A1200** | 68EC020 @ 14 MHz | 2 MB | AGA (24-bit, HAM8) | 4ch DMA | $399 |
|
||||
| **PC (VGA)** | 386SX @ 25 MHz | 4 MB | VGA 256-color | Sound Blaster 8-bit | $1,200+ |
|
||||
| **Mac LC III** | 68030 @ 25 MHz | 4 MB | 512×384 16-bit | 8-bit mono | $1,349 |
|
||||
| **Atari Falcon** | 68030 @ 16 MHz | 1 MB | VIDEL (16-bit) | DSP 16-bit | $799 |
|
||||
|
||||
The A1200 was aggressively priced but underpowered for its era. The 68EC020 at 14 MHz struggled against 386DX and 68030 systems. AGA added color depth but not raw fill-rate — the blitter remained fundamentally an OCS-era design. The CD32 console failed to find a market against the SNES and Mega Drive, which were cheaper and had larger game libraries.
|
||||
|
||||
---
|
||||
|
||||
## Modern Analogies
|
||||
|
||||
| Amiga Model | Modern Equivalent | Shared Concept |
|
||||
|---|---|---|
|
||||
| **A1000** | Original Macintosh 128K | Groundbreaking but under-RAM'd; required expansion to be practical |
|
||||
| **A500** | Commodore 64 / NES | The "people's computer" — sold on price, expanded endlessly by users |
|
||||
| **A2000** | IBM PC/AT with ISA slots | Big-box expandability; professional positioning |
|
||||
| **A3000** | SGI Indy / NeXTStation | Professional workstation with UNIX aspirations |
|
||||
| **A1200** | PlayStation 1 / 3DO | Cutting-edge for its price class, but underpowered vs. PC |
|
||||
| **CD32** | 3DO / Philips CD-i | Early CD-ROM console; failed due to software library, not hardware |
|
||||
| **A4000T** | Sun Ultra 1 | Rare, expensive, professional tower; end of the line |
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Target A500 (OCS, 512 KB) for maximum reach** — every Amiga can run A500 software.
|
||||
2. **Detect AGA at runtime** — never assume; check `GfxBase->ChipRevBits`.
|
||||
3. **Probe Chip RAM size** — use `AvailMem(MEMF_CHIP | MEMF_TOTAL)` rather than hardcoding.
|
||||
4. **Account for missing MMU/FPU** — 68EC020 has neither; 68040 FPU is incomplete.
|
||||
5. **Test on real hardware** — emulation is accurate but timing-sensitive code (Copper, Blitter) behaves differently on real chips.
|
||||
6. **Document minimum requirements** — "Requires 1 MB Chip" or "AGA only" prevents user frustration.
|
||||
7. **Respect expansion slot differences** — A500 trapdoor ≠ A1200 trapdoor ≠ A600 trapdoor.
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls
|
||||
|
||||
### 1. Assuming All A1200s Have Fast RAM
|
||||
|
||||
The A1200 sold with 2 MB Chip RAM and no Fast RAM. Software that allocates large buffers with `MEMF_ANY` may exhaust Chip RAM on a stock A1200 even though the machine has 2 MB. Always check `AvailMem()` before large allocations.
|
||||
|
||||
### 2. The A600 PCMCIA Trap
|
||||
|
||||
PCMCIA memory on the A600/A1200 maps to `$600000`. Some accelerators also map Fast RAM to this region, causing a conflict that disables PCMCIA. If your software detects PCMCIA Fast RAM, warn the user about potential accelerator conflicts.
|
||||
|
||||
### 3. A4000/040 vs A4000/030 Differences
|
||||
|
||||
The A4000/040 has a 68040 with on-board FPU and MMU; the A4000/030 has a 68030 with external FPU optional. Software using FPU instructions will crash on a 030 unless it checks for FPU presence via `execbase->AttnFlags & AFF_68040`.
|
||||
|
||||
### 4. CD32 Without Akiko
|
||||
|
||||
Some third-party CD32 clones and emulators omit Akiko. Games that rely solely on Akiko C2P will fail. Always provide a software fallback path for C2P operations.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Commodore *A1200 Technical Reference Manual* — `Documentation/A1200/` local archive
|
||||
- Commodore *A4000 Technical Reference Manual* — `Documentation/A4000/` local archive
|
||||
- NDK 3.9: `graphics/gfxbase.h` — `ChipRevBits` definitions
|
||||
- ADCD 2.1 Hardware Manual: http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0000.html
|
||||
- Commodore *A1200 Technical Reference Manual*
|
||||
- Commodore *A4000 Technical Reference Manual*
|
||||
- Commodore *A3000 Technical Reference Manual*
|
||||
- See also: [memory_types.md](../01_hardware/common/memory_types.md) — Chip RAM, Fast RAM, Slow RAM architecture
|
||||
- See also: [address_space.md](../01_hardware/common/address_space.md) — Full 24-bit/32-bit memory maps
|
||||
- See also: [chipset_ocs.md](../01_hardware/ocs_a500/chipset_ocs.md) — OCS custom chip details
|
||||
- See also: [chipset_ecs.md](../01_hardware/ecs_a600_a3000/chipset_ecs.md) — ECS custom chip details
|
||||
- See also: [chipset_aga.md](../01_hardware/aga_a1200_a4000/chipset_aga.md) — AGA custom chip details
|
||||
- See also: [akiko_cd32.md](../01_hardware/aga_a1200_a4000/akiko_cd32.md) — CD32 Akiko C2P programming
|
||||
- See also: [zorro_bus.md](../01_hardware/common/zorro_bus.md) — Zorro II/III expansion bus
|
||||
- See also: [68040_68060_libraries.md](../15_fpu_mmu_cache/68040_68060_libraries.md) — FPU/MMU emulation on 040/060
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
The Amiga was designed by Jay Miner's team at Amiga Corporation (originally Hi-Toro), beginning in 1982 under the codename **Lorraine**. The primary design goal was a low-cost personal computer with dedicated custom silicon handling graphics, audio, and DMA — freeing the CPU for application code. Commodore Business Machines acquired Amiga Corporation in 1984, incorporating the technology into what would ship as the **Commodore Amiga 1000** in July 1985.
|
||||
|
||||
The core insight was the **coprocessor paradigm**: three custom chips (Agnus, Denise, Paula) operate concurrently with the M68000, driven by a shared DMA bus arbitrated by Agnus. This allowed the Amiga to demonstrate colour animation, digitised speech, and multitasking simultaneously — capabilities competitors would not match for years.
|
||||
The core insight was the **coprocessor paradigm**: three custom chips (Agnus, Denise, Paula) operate concurrently with the M68000, driven by a shared DMA bus arbitrated by Agnus. This allowed the Amiga to demonstrate color animation, digitised speech, and multitasking simultaneously — capabilities competitors would not match for years.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -17,12 +17,12 @@ The core insight was the **coprocessor paradigm**: three custom chips (Agnus, De
|
|||
| Component | Part Numbers | Role |
|
||||
|---|---|---|
|
||||
| **Agnus** | MOS 8361 (PAL), 8367 (NTSC) | DMA controller, Copper, Blitter, address gen |
|
||||
| **Denise** | MOS 8362 | Display: sprites, bitplanes, colour decode |
|
||||
| **Denise** | MOS 8362 | Display: sprites, bitplanes, color decode |
|
||||
| **Paula** | MOS 8364 | Audio DMA (4 channels), disk I/O, serial I/O |
|
||||
|
||||
Key characteristics:
|
||||
- **1 MB Chip RAM** maximum (512 KB in early A1000/A500 configs)
|
||||
- 6 bitplanes → 64 colours (EHB mode) or 4096 (HAM)
|
||||
- 6 bitplanes → 64 colors (EHB mode) or 4096 (HAM)
|
||||
- 8 hardware sprites (16px wide, 2bpp)
|
||||
- Copper coprocessor: 2 registers, WAIT/SKIP/MOVE instructions
|
||||
- Blitter: 3 source channels + destination, minterm logic, line mode
|
||||
|
|
@ -67,9 +67,9 @@ Machines using ECS:
|
|||
| **Paula** | MOS 8364 (unchanged) | Same as OCS/ECS |
|
||||
|
||||
Key enhancements over ECS:
|
||||
- **32-bit colour registers**: 24-bit palette (256 colours, HAM8)
|
||||
- **256 colour registers** (COLOR00–COLOR255)
|
||||
- HAM8 mode: 262,144 simultaneous colours
|
||||
- **32-bit color registers**: 24-bit palette (256 colors, HAM8)
|
||||
- **256 color registers** (COLOR00–COLOR255)
|
||||
- HAM8 mode: 262,144 simultaneous colors
|
||||
- **64-bit blitter bus** via `FMODE` register (1x/2x/4x word transfers)
|
||||
- **BPLCON3 / BPLCON4**: sprite palette bank, bitplane bank select
|
||||
- **DIWHIGH**: extended display window for overscan
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ This is the final OS release by Commodore before their 1994 bankruptcy. It ships
|
|||
**graphics.library (40.x):**
|
||||
- `AllocBitMap()` / `FreeBitMap()` — dynamic bitmap allocation
|
||||
- `GetBitMapAttr()` — bitmap attribute query
|
||||
- `ObtainBestPenA()` — closest-match colour allocation
|
||||
- `ObtainBestPenA()` — closest-match color allocation
|
||||
- `SetRPAttrsA()` / `GetRPAttrsA()` — RastPort attribute tags
|
||||
- AGA full colour support: `LoadRGB32()`, 256 colour tables
|
||||
- AGA full color support: `LoadRGB32()`, 256 color tables
|
||||
- RTG stubs present but not functional (RTG lives in 3.5+)
|
||||
|
||||
**intuition.library (40.x):**
|
||||
|
|
@ -89,7 +89,7 @@ OS 3.2 is a Hyperion-developed modernisation of the 3.1 codebase, first released
|
|||
|
||||
### intuition.library (47.x)
|
||||
- New Intuition prefs (IPrefs 47.x)
|
||||
- Better pen sharing and colour management
|
||||
- Better pen sharing and color management
|
||||
- `intuition.library` opens before `graphics.library` in new boot sequence
|
||||
- Visual prefs: themes, scaled UI elements
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
| Denise | 8362 | 8373 (ECS Denise) | Lisa |
|
||||
| Paula | 8364 | 8364 (unchanged) | 8364 (unchanged) |
|
||||
| Max Chip RAM | 512 KB–1 MB | 1–2 MB | 2 MB |
|
||||
| Colours (max normal) | 32 | 32 | 256 |
|
||||
| Colors (max normal) | 32 | 32 | 256 |
|
||||
| HAM | 12-bit HAM (6bpp) | 12-bit HAM | 24-bit HAM8 (8bpp) |
|
||||
| Sprites | 8 × 16px | 8 × 16px | 8 × 64px, 256 colours |
|
||||
| Sprites | 8 × 16px | 8 × 16px | 8 × 64px, 256 colors |
|
||||
| Blitter bus | 16-bit | 16-bit | 64-bit (FMODE) |
|
||||
| Display modes | NTSC/PAL | +Productivity, VGA | +Doublescan, 31kHz |
|
||||
| Machines | A500/A1000/A2000/**CDTV** | A600/A3000/A500+ | A1200/A4000/A4000T/**CD32** |
|
||||
|
|
@ -28,7 +28,7 @@ $DFF000 BLTDDAT Blitter destination early read
|
|||
$DFF002 DMACONR DMA control (read)
|
||||
$DFF004 VPOSR Vertical position (read, high)
|
||||
...
|
||||
$DFF180 COLOR00 Colour register 0
|
||||
$DFF180 COLOR00 Color register 0
|
||||
...
|
||||
$DFF1FE (last OCS/ECS register)
|
||||
$DFF1FC BEAMCON0 (ECS+) beam control
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
|
||||
## Overview
|
||||
|
||||
The **Advanced Graphics Architecture** (AGA) is the final custom chipset developed by Commodore, shipping from 1992. It dramatically expands colour depth, palette size, sprite capabilities, and blitter bandwidth while retaining full OCS/ECS backward compatibility.
|
||||
The **Advanced Graphics Architecture** (AGA) is the final custom chipset developed by Commodore, shipping from 1992. It dramatically expands color depth, palette size, sprite capabilities, and blitter bandwidth while retaining full OCS/ECS backward compatibility.
|
||||
|
||||
## Chip Summary
|
||||
|
||||
| Chip | Name | Changes from ECS |
|
||||
|---|---|---|
|
||||
| **Alice** | MOS 8374 | Super Agnus successor: 64-bit bus, FMODE register |
|
||||
| **Lisa** | (unnamed MOS) | ECS Denise successor: 8-bit palette, 256 colours |
|
||||
| **Lisa** | (unnamed MOS) | ECS Denise successor: 8-bit palette, 256 colors |
|
||||
| **Paula** | MOS 8364 | Unchanged from OCS/ECS |
|
||||
|
||||
## Contents
|
||||
|
|
@ -20,8 +20,8 @@ The **Advanced Graphics Architecture** (AGA) is the final custom chipset develop
|
|||
|---|---|
|
||||
| [chipset_aga.md](chipset_aga.md) | Alice and Lisa internals, AGA architecture |
|
||||
| [aga_registers_delta.md](aga_registers_delta.md) | New/changed registers vs ECS |
|
||||
| [aga_palette.md](aga_palette.md) | 24-bit colour system, 256 registers |
|
||||
| [aga_display_modes.md](aga_display_modes.md) | HAM8, 256-colour, doublescan, VGA |
|
||||
| [aga_palette.md](aga_palette.md) | 24-bit color system, 256 registers |
|
||||
| [aga_display_modes.md](aga_display_modes.md) | HAM8, 256-color, doublescan, VGA |
|
||||
| [aga_blitter.md](aga_blitter.md) | 64-bit blitter bus, FMODE |
|
||||
| [aga_copper.md](aga_copper.md) | AGA Copper programming guide |
|
||||
| [cpu_030_040.md](cpu_030_040.md) | 68030/040 on A3000/A4000: cache, MMU, FPU |
|
||||
|
|
@ -32,11 +32,11 @@ The **Advanced Graphics Architecture** (AGA) is the final custom chipset develop
|
|||
|
||||
| Feature | ECS | AGA |
|
||||
|---|---|---|
|
||||
| Colour registers | 32 (12-bit) | **256 (24-bit)** |
|
||||
| Max simultaneous colours | 64 EHB / HAM | **256** (or HAM8: 262,144) |
|
||||
| Color registers | 32 (12-bit) | **256 (24-bit)** |
|
||||
| Max simultaneous colors | 64 EHB / HAM | **256** (or HAM8: 262,144) |
|
||||
| Blitter bus | 16-bit | **64-bit** (FMODE) |
|
||||
| Sprite width | 16 px | **64 px** |
|
||||
| Sprite colours | 3+transparent | **15+transparent** (64-colour attached) |
|
||||
| Sprite colors | 3+transparent | **15+transparent** (64-color attached) |
|
||||
| Bitplane depth | 6 planes max | **8 planes** |
|
||||
| Palette select | 1 bank | **4 bitplane banks, 4 sprite banks** |
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Think of it as a **hardware GPU for 2D raster operations** — years before PC g
|
|||
| No rotation | Cannot rotate — must be pre-rendered in software |
|
||||
| No 3D | No perspective, texture mapping, or Z-buffer |
|
||||
| No chunky pixels | Operates on **planar** bitplanes only (1 plane at a time) |
|
||||
| No colour blending | Pure Boolean logic — no alpha, no transparency gradients |
|
||||
| No color blending | Pure Boolean logic — no alpha, no transparency gradients |
|
||||
| Word-aligned width | Minimum operation width is 16 pixels (1 word) |
|
||||
|
||||
### How Software Uses It
|
||||
|
|
@ -70,7 +70,7 @@ move.w #$0300, $DFF1FC ; BLT_FMODE = 10 (64-bit)
|
|||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> FMODE must be set **before** loading blitter registers and starting the blit. Changing FMODE mid-blit causes undefined behaviour.
|
||||
> FMODE must be set **before** loading blitter registers and starting the blit. Changing FMODE mid-blit causes undefined behavior.
|
||||
|
||||
## Width Calculation with FMODE
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
|
||||
## What Is the Copper?
|
||||
|
||||
The **Copper** (Co-Processor) is one of the most distinctive pieces of hardware in any computer ever built. It is a tiny, ultra-simple programmable DMA engine that executes a program — called a **copper list** — in perfect synchronisation with the video beam as it sweeps across the screen.
|
||||
The **Copper** (Co-Processor) is one of the most distinctive pieces of hardware in any computer ever built. It is a tiny, ultra-simple programmable DMA engine that executes a program — called a **copper list** — in perfect synchronization with the video beam as it sweeps across the screen.
|
||||
|
||||
The Copper watches the beam position and can **write any value to any custom chip register at any specific screen position**. This single capability enables an astonishing range of visual effects.
|
||||
|
||||
### Why Does It Matter?
|
||||
|
||||
On a conventional computer, changing display parameters (colours, scroll positions, resolutions) requires the CPU to execute code at precisely the right moment. This is fragile, wastes CPU time, and is limited by interrupt latency.
|
||||
On a conventional computer, changing display parameters (colors, scroll positions, resolutions) requires the CPU to execute code at precisely the right moment. This is fragile, wastes CPU time, and is limited by interrupt latency.
|
||||
|
||||
The Copper does this **automatically, for free, with perfect timing** — every single frame, without any CPU involvement at all.
|
||||
|
||||
|
|
@ -50,20 +50,20 @@ The Copper does this **automatically, for free, with perfect timing** — every
|
|||
│ │ │ │ │
|
||||
│ ┌─────┴──────────────────┴────────────────────────┴─────────┐ │
|
||||
│ │ BEAM COUNTER (V count, H count) │ │
|
||||
│ │ Increments every colour clock, resets each frame │ │
|
||||
│ │ Increments every color clock, resets each frame │ │
|
||||
│ │ PAL: 312 lines × 227 clocks NTSC: 262 × 227 │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
└──────────┬─────────────────────────────────────────────────────────┘
|
||||
└──────────┬──────────────────────────────────────────────────────────┘
|
||||
│ register writes ($DFF000–$DFF1FE)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ DENISE / LISA (Video Encoder) │
|
||||
│ │
|
||||
│ Receives bitplane data + sprite data + colour register values │
|
||||
│ Receives bitplane data + sprite data + color register values │
|
||||
│ Composites them into a final pixel stream: │
|
||||
│ │
|
||||
│ ┌────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
|
||||
│ │ Bitplane │ │ Sprite │ │ Colour │ │ Playfield │ │
|
||||
│ │ Bitplane │ │ Sprite │ │ Color │ │ Playfield │ │
|
||||
│ │ Decode │→ │ Priority │→ │ Palette │→ │ Priority & │ │
|
||||
│ │ (planar→ │ │ Merge │ │ Lookup │ │ Genlock Control │ │
|
||||
│ │ index) │ │ │ │ (32/256) │ │ │ │
|
||||
|
|
@ -90,10 +90,10 @@ The Copper does this **automatically, for free, with perfect timing** — every
|
|||
### Component Interactions
|
||||
|
||||
**Copper ↔ Chip RAM:**
|
||||
The Copper fetches its program (the copper list) from Chip RAM via DMA. It reads one instruction (2 words = 4 bytes) every 4 colour clocks. The copper list **must** reside in Chip RAM — it cannot be in Fast RAM because only Chip RAM is DMA-accessible.
|
||||
The Copper fetches its program (the copper list) from Chip RAM via DMA. It reads one instruction (2 words = 4 bytes) every 4 color clocks. The copper list **must** reside in Chip RAM — it cannot be in Fast RAM because only Chip RAM is DMA-accessible.
|
||||
|
||||
**Copper ↔ Custom Registers:**
|
||||
When the Copper executes a MOVE instruction, it writes directly to a custom chip register (`$DFF000`–`$DFF1FE`). This is the exact same register space the CPU writes to. The Copper can set colours, bitplane pointers, sprite pointers, display window, scroll offsets, DMA control, and audio parameters.
|
||||
When the Copper executes a MOVE instruction, it writes directly to a custom chip register (`$DFF000`–`$DFF1FE`). This is the exact same register space the CPU writes to. The Copper can set colors, bitplane pointers, sprite pointers, display window, scroll offsets, DMA control, and audio parameters.
|
||||
|
||||
**Copper ↔ Beam Counter:**
|
||||
The Copper continuously compares the current beam position (V count, H count) against WAIT instructions. When the beam reaches or passes the specified position, execution continues. This is a hardware comparator — no polling loop, no interrupt latency.
|
||||
|
|
@ -117,12 +117,12 @@ A WAIT instruction with bit 15 of the mask word cleared becomes a "blitter-finis
|
|||
|
||||
| Effect | How | Used In |
|
||||
|---|---|---|
|
||||
| **Per-line colour changes** | WAIT for line, MOVE colour register | Gradient skies, rainbow bars |
|
||||
| **Per-line color changes** | WAIT for line, MOVE color register | Gradient skies, rainbow bars |
|
||||
| **Split screens** | Change bitplane pointers mid-frame | Status bar + scrolling playfield |
|
||||
| **Parallax scrolling** | Change BPLCON1 (scroll offset) at different lines | Multi-layer side-scrollers |
|
||||
| **Resolution changes** | Change BPLCON0 mid-frame | HiRes menu + LoRes game area |
|
||||
| **Sprite multiplexing** | Repoint sprite DMA pointers after sprite finishes | More than 8 sprites per frame |
|
||||
| **Palette animation** | Modify colour registers each frame | Cycling colours, water shimmer |
|
||||
| **Palette animation** | Modify color registers each frame | Cycling colors, water shimmer |
|
||||
| **Display window tricks** | Change DIWSTRT/DIWSTOP | Overscan, letterbox |
|
||||
| **Interlace tricks** | Toggle LOF bit | Custom interlace effects |
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ A WAIT instruction with bit 15 of the mask word cleared becomes a "blitter-finis
|
|||
| No memory read | Can only WRITE to registers, never read |
|
||||
| Write-only to custom regs | Cannot write to CPU memory, CIA, or Fast RAM |
|
||||
| Limited register set | Protected registers ($000–$03E) need `COPCON` unlock |
|
||||
| No sub-pixel timing | Horizontal resolution is 4 colour clocks (~8 low-res pixels) |
|
||||
| No sub-pixel timing | Horizontal resolution is 4 color clocks (~8 low-res pixels) |
|
||||
| Vertical wrapping | V counter wraps at 255; PAL lines 256+ need two WAITs |
|
||||
|
||||
---
|
||||
|
|
@ -191,7 +191,7 @@ Example: Skip next if beam is past line 200:
|
|||
|
||||
## Your First Copper List
|
||||
|
||||
Here's the simplest possible copper list — it changes the background colour at line 128:
|
||||
Here's the simplest possible copper list — it changes the background color at line 128:
|
||||
|
||||
```asm
|
||||
SECTION copperlist,DATA_C ; *** MUST be in Chip RAM! ***
|
||||
|
|
@ -220,7 +220,7 @@ To activate it:
|
|||
|
||||
---
|
||||
|
||||
## Rainbow Gradient (Colour Per Scanline)
|
||||
## Rainbow Gradient (Color Per Scanline)
|
||||
|
||||
```asm
|
||||
RainbowCopper:
|
||||
|
|
@ -242,7 +242,7 @@ RainbowCopper:
|
|||
dc.w $FFFF,$FFFE
|
||||
```
|
||||
|
||||
This produces a smooth colour gradient down the screen — **zero CPU cost**.
|
||||
This produces a smooth color gradient down the screen — **zero CPU cost**.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -325,23 +325,23 @@ AGA (Alice chip) keeps the same 3-instruction Copper but gains access to the **e
|
|||
|
||||
| AGA Feature | Copper Can Set |
|
||||
|---|---|
|
||||
| 256-colour palette | `COLOR00–COLOR255` via BPLCON3 bank select |
|
||||
| Extended sprites | 64-colour sprites via palette banks |
|
||||
| 256-color palette | `COLOR00–COLOR255` via BPLCON3 bank select |
|
||||
| Extended sprites | 64-color sprites via palette banks |
|
||||
| FMODE | DMA fetch width (but careful — affects in-progress DMA) |
|
||||
| BPLCON3/BPLCON4 | AGA-specific bitplane/sprite control |
|
||||
|
||||
### AGA Palette via Copper
|
||||
|
||||
AGA has 256 colours but still only 32 colour registers visible at a time. To load all 256 colours, the Copper uses BPLCON3 to select palette banks:
|
||||
AGA has 256 colors but still only 32 color registers visible at a time. To load all 256 colors, the Copper uses BPLCON3 to select palette banks:
|
||||
|
||||
```asm
|
||||
; Load colours 0–31 (bank 0)
|
||||
; Load colors 0–31 (bank 0)
|
||||
dc.w $0106,$0000 ; BPLCON3: bank 0
|
||||
dc.w $0180,$0000 ; COLOR00
|
||||
dc.w $0182,$0111 ; COLOR01
|
||||
; ... all 32 colours ...
|
||||
; ... all 32 colors ...
|
||||
|
||||
; Switch to bank 1 (colours 32–63)
|
||||
; Switch to bank 1 (colors 32–63)
|
||||
dc.w $0106,$2000 ; BPLCON3: bank 1
|
||||
dc.w $0180,$0222 ; COLOR32
|
||||
dc.w $0182,$0333 ; COLOR33
|
||||
|
|
@ -354,9 +354,9 @@ AGA has 256 colours but still only 32 colour registers visible at a time. To loa
|
|||
|
||||
| Parameter | Value |
|
||||
|---|---|
|
||||
| Instruction time | 4 colour clocks (= 8 lo-res pixels = ~1.12 µs) |
|
||||
| Instruction time | 4 color clocks (= 8 lo-res pixels = ~1.12 µs) |
|
||||
| Max instructions per line | ~112 (NTSC) / ~114 (PAL) |
|
||||
| Horizontal resolution | 4 colour clocks (~8 lo-res pixels) |
|
||||
| Horizontal resolution | 4 color clocks (~8 lo-res pixels) |
|
||||
| Vertical range | 0–255 (wraps; use double-WAIT for PAL lines 256+) |
|
||||
| PAL visible lines | 44–300 (256 visible) |
|
||||
| NTSC visible lines | 44–244 (200 visible) |
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Mode flags (lower 16 bits):
|
|||
|
||||
## Standard AGA Modes
|
||||
|
||||
| Mode ID | Resolution | Colours | H rate |
|
||||
| Mode ID | Resolution | Colors | H rate |
|
||||
|---|---|---|---|
|
||||
| `PAL_MONITOR_ID \| LORES_KEY` | 320×256 | 256 | 15.6 kHz |
|
||||
| `PAL_MONITOR_ID \| HIRES_KEY` | 640×256 | 256 | 15.6 kHz |
|
||||
|
|
@ -55,13 +55,13 @@ Mode flags (lower 16 bits):
|
|||
|
||||
struct Screen *scr;
|
||||
|
||||
/* 256-colour AGA screen, PAL, 320×256 */
|
||||
/* 256-color AGA screen, PAL, 320×256 */
|
||||
scr = OpenScreenTags(NULL,
|
||||
SA_DisplayID, PAL_MONITOR_ID | LORES_KEY,
|
||||
SA_Width, 320,
|
||||
SA_Height, 256,
|
||||
SA_Depth, 8, /* 8 bitplanes = 256 colours */
|
||||
SA_Colors32, (ULONG)colour_table, /* LoadRGB32 format */
|
||||
SA_Depth, 8, /* 8 bitplanes = 256 colors */
|
||||
SA_Colors32, (ULONG)color_table, /* LoadRGB32 format */
|
||||
SA_Title, (ULONG)"My AGA Screen",
|
||||
SA_Quiet, TRUE,
|
||||
TAG_DONE);
|
||||
|
|
@ -79,7 +79,7 @@ scr = OpenScreenTags(NULL,
|
|||
```
|
||||
|
||||
> [!NOTE]
|
||||
> HAM8 screens require 8 bitplanes. The display system automatically programmes BPLCON0 with HAM=1. The first 64 colour registers are used as the HAM8 index palette.
|
||||
> HAM8 screens require 8 bitplanes. The display system automatically programmes BPLCON0 with HAM=1. The first 64 color registers are used as the HAM8 index palette.
|
||||
|
||||
## BestModeID() — Querying Available Modes
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
[← Home](../../README.md) · [Hardware](../README.md) · [AGA](README.md)
|
||||
|
||||
# AGA Palette & Colour System
|
||||
# AGA Palette & Color System
|
||||
|
||||
## Overview
|
||||
|
||||
AGA provides **256 colour registers** (COLOR00–COLOR255), each **24-bit RGB** (8 bits per channel). This replaces OCS/ECS's 32 registers with 12-bit colour.
|
||||
AGA provides **256 color registers** (COLOR00–COLOR255), each **24-bit RGB** (8 bits per channel). This replaces OCS/ECS's 32 registers with 12-bit color.
|
||||
|
||||
## Colour Register Layout
|
||||
## Color Register Layout
|
||||
|
||||
```
|
||||
ADDRESS: $DFF180 + (n × 2) for register n (0–255)
|
||||
|
|
@ -18,11 +18,11 @@ $DFF180–$DFF1BE COLOR00–COLOR31 (same addresses as OCS/ECS)
|
|||
$DFF180–$DFF3BE COLOR00–COLOR255 (full AGA range — needs BPLCON4 bank select)
|
||||
```
|
||||
|
||||
The 256 colour registers are accessed in 64-register **banks** selected by `BPLCON4`.
|
||||
The 256 color registers are accessed in 64-register **banks** selected by `BPLCON4`.
|
||||
|
||||
## Writing 24-bit Colours
|
||||
## Writing 24-bit Colors
|
||||
|
||||
Each colour register holds 12 bits directly (OCS/ECS compatible). The upper 12 bits (the "low nibble") are written via a second access with `LOCT` set in BPLCON3.
|
||||
Each color register holds 12 bits directly (OCS/ECS compatible). The upper 12 bits (the "low nibble") are written via a second access with `LOCT` set in BPLCON3.
|
||||
|
||||
### Manual 24-bit write sequence:
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ The preferred OS call:
|
|||
|
||||
/* Table: count, index, then 0x00RRGGBB values, terminated by ~0 */
|
||||
ULONG table[] = {
|
||||
4, 0, /* 4 colours starting at index 0 */
|
||||
4, 0, /* 4 colors starting at index 0 */
|
||||
0x00FF0000, /* COLOR00: red */
|
||||
0x0000FF00, /* COLOR01: green */
|
||||
0x000000FF, /* COLOR02: blue */
|
||||
|
|
@ -62,13 +62,13 @@ ULONG table[] = {
|
|||
LoadRGB32(viewport, table);
|
||||
```
|
||||
|
||||
`LoadRGB32()` (graphics.library LVO -$192) is the AGA-correct way to set colours. It handles the two-write LOCT protocol internally and is available from OS 3.0+.
|
||||
`LoadRGB32()` (graphics.library LVO -$192) is the AGA-correct way to set colors. It handles the two-write LOCT protocol internally and is available from OS 3.0+.
|
||||
|
||||
### Using LoadRGB4() — OCS/ECS compatible (12-bit)
|
||||
|
||||
```c
|
||||
UWORD colours[32] = { 0x000, 0xF00, 0x0F0, ... };
|
||||
LoadRGB4(viewport, colours, 32); /* sets 32 colours from 12-bit table */
|
||||
UWORD colors[32] = { 0x000, 0xF00, 0x0F0, ... };
|
||||
LoadRGB4(viewport, colors, 32); /* sets 32 colors from 12-bit table */
|
||||
```
|
||||
|
||||
`LoadRGB4()` is safe on all chipsets but only provides 12-bit precision on AGA.
|
||||
|
|
@ -82,9 +82,9 @@ HAM8 is the AGA extension of OCS's HAM6. It uses 8 bitplanes:
|
|||
- `01` = modify blue channel
|
||||
- `10` = modify red channel
|
||||
- `11` = modify green channel
|
||||
- **Bits 5-0**: 6-bit value for the selected channel (or 6-bit colour index)
|
||||
- **Bits 5-0**: 6-bit value for the selected channel (or 6-bit color index)
|
||||
|
||||
Result: 2^18 = **262,144 simultaneous colours** from adjacent-pixel modification.
|
||||
Result: 2^18 = **262,144 simultaneous colors** from adjacent-pixel modification.
|
||||
|
||||
Enabling HAM8:
|
||||
```asm
|
||||
|
|
@ -92,19 +92,19 @@ Enabling HAM8:
|
|||
move.w #$9811, BPLCON0+custom
|
||||
```
|
||||
|
||||
## Colour Modes Summary
|
||||
## Color Modes Summary
|
||||
|
||||
| Mode | Planes | Colours | Method |
|
||||
| Mode | Planes | Colors | Method |
|
||||
|---|---|---|---|
|
||||
| Standard | 1–8 | 2–256 | Direct palette lookup |
|
||||
| EHB | 6 | 64 | Extra Half-Brite (OCS/ECS compat) |
|
||||
| HAM6 | 6 | 4096 | Hold-and-modify 4-bit channels |
|
||||
| HAM8 | 8 | 262,144 | Hold-and-modify 6-bit channels |
|
||||
| Dual Playfield | 3+3 | 8+8 | Two independent 8-colour layers |
|
||||
| Dual Playfield | 3+3 | 8+8 | Two independent 8-color layers |
|
||||
|
||||
## Colour Bank Selection (BPLCON4)
|
||||
## Color Bank Selection (BPLCON4)
|
||||
|
||||
The 256 colour registers are split into 4 banks of 64:
|
||||
The 256 color registers are split into 4 banks of 64:
|
||||
|
||||
| BPLAM | Bank | Registers |
|
||||
|---|---|---|
|
||||
|
|
@ -113,15 +113,15 @@ The 256 colour registers are split into 4 banks of 64:
|
|||
| $80 | 2 | COLOR128–COLOR191 |
|
||||
| $C0 | 3 | COLOR192–COLOR255 |
|
||||
|
||||
Dual playfield can use BPLCON4 to give each playfield a different 64-colour bank.
|
||||
Dual playfield can use BPLCON4 to give each playfield a different 64-color bank.
|
||||
|
||||
## OS Colour Management
|
||||
## OS Color Management
|
||||
|
||||
`graphics.library` manages palette via the `ColorMap` structure attached to a `ViewPort`:
|
||||
|
||||
```c
|
||||
struct ColorMap *cm = GetColorMap(256); /* allocate 256-entry AGA map */
|
||||
SetRGB32(vp, n, r, g, b); /* set one colour (24-bit each) */
|
||||
SetRGB32(vp, n, r, g, b); /* set one color (24-bit each) */
|
||||
LoadRGB32(vp, table); /* bulk load */
|
||||
FreeColorMap(cm);
|
||||
```
|
||||
|
|
@ -131,4 +131,4 @@ FreeColorMap(cm);
|
|||
- NDK39: `graphics/view.h` — ColorMap, LoadRGB32
|
||||
- ADCD 2.1 Autodocs: graphics — LoadRGB32, SetRGB32
|
||||
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node02B4.html
|
||||
- AmigaMail Vol. 2 — AGA colour system articles
|
||||
- AmigaMail Vol. 2 — AGA color system articles
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@ DMA fetch mode — see [chipset_aga.md](chipset_aga.md) for full description.
|
|||
|
||||
### BPLCON4 — $DFF10C (AGA only)
|
||||
|
||||
Bitplane and sprite colour bank selection:
|
||||
Bitplane and sprite color bank selection:
|
||||
|
||||
```
|
||||
bits 15-8: BPLAM7-0 — Bitplane XOR pattern (AGA colour bank XOR)
|
||||
bits 7-4: ESPRM7-4 — Even sprite colour bank (bits 7:4 of colour reg index)
|
||||
bits 3-0: OSPRM7-4 — Odd sprite colour bank
|
||||
bits 15-8: BPLAM7-0 — Bitplane XOR pattern (AGA color bank XOR)
|
||||
bits 7-4: ESPRM7-4 — Even sprite color bank (bits 7:4 of color reg index)
|
||||
bits 3-0: OSPRM7-4 — Odd sprite color bank
|
||||
```
|
||||
|
||||
**Bitplane bank select via BPLAM:**
|
||||
- BPLAM provides an XOR mask applied to the 8-bit colour index before palette lookup
|
||||
- BPLAM provides an XOR mask applied to the 8-bit color index before palette lookup
|
||||
- BPLAM = $00 → use COLOR00–COLOR63 (bank 0)
|
||||
- BPLAM = $40 → use COLOR64–COLOR127 (bank 1)
|
||||
- BPLAM = $80 → use COLOR128–COLOR191 (bank 2)
|
||||
|
|
@ -27,14 +27,14 @@ bits 3-0: OSPRM7-4 — Odd sprite colour bank
|
|||
|
||||
### COLOR00–COLOR255 — $DFF180–$DFF3BE (AGA)
|
||||
|
||||
AGA extends the colour table from 32 registers (OCS/ECS) to **256 registers**.
|
||||
AGA extends the color table from 32 registers (OCS/ECS) to **256 registers**.
|
||||
|
||||
Each AGA colour register is 32 bits (accessed as two word writes via BPLCON3 latch):
|
||||
Each AGA color register is 32 bits (accessed as two word writes via BPLCON3 latch):
|
||||
|
||||
```asm
|
||||
; Write 24-bit colour to COLOR00:
|
||||
; Write 24-bit color to COLOR00:
|
||||
; First write sets high nibble, second sets low nibble
|
||||
move.w #$0000, BPLCON3+custom ; set LACE=0, select low colour word
|
||||
move.w #$0000, BPLCON3+custom ; set LACE=0, select low color word
|
||||
move.w #$0FFF, COLOR00+custom ; write $RGB (high 12 bits)
|
||||
bset #9, BPLCON3_shadow ; set LOCT (low nibble enable)
|
||||
move.w #$0FFF, COLOR00+custom ; write low nibble of each channel
|
||||
|
|
@ -47,7 +47,7 @@ The standard `LoadRGB32()` and `LoadRGB4()` graphics library calls manage this t
|
|||
### BPLCON2 — $DFF104 (AGA extended)
|
||||
|
||||
```
|
||||
bits 14-9: KILLEHB — kill EHB mode (AGA replaces EHB with 256 colour)
|
||||
bits 14-9: KILLEHB — kill EHB mode (AGA replaces EHB with 256 color)
|
||||
bit 6: RDRAM — read bitplane data from RAM (not registered in Lisa)
|
||||
bits 5-3: PF2PRI — playfield 2 priority
|
||||
bits 2-0: PF1PRI — playfield 1 priority + sprite priority
|
||||
|
|
@ -57,7 +57,7 @@ bits 2-0: PF1PRI — playfield 1 priority + sprite priority
|
|||
|
||||
Additional AGA bits:
|
||||
```
|
||||
bit 9: LOCT — low colour write enable (for 24-bit colour access)
|
||||
bit 9: LOCT — low color write enable (for 24-bit color access)
|
||||
bit 3: BRDSPRT — sprites visible in border
|
||||
```
|
||||
|
||||
|
|
@ -65,28 +65,28 @@ bit 3: BRDSPRT — sprites visible in border
|
|||
|
||||
See [chipset_aga.md](chipset_aga.md) — bit 4 is the MSB of the bitplane count for 7/8-plane modes.
|
||||
|
||||
## Colour Register Access — Low Nibble Protocol
|
||||
## Color Register Access — Low Nibble Protocol
|
||||
|
||||
Writing 24-bit colour to AGA registers requires two steps per colour:
|
||||
Writing 24-bit color to AGA registers requires two steps per color:
|
||||
|
||||
1. **Write high nibble** (standard): `COLOR00 = $0RGB` (bits [11:0] = R[3:0], G[3:0], B[3:0])
|
||||
2. **Set LOCT** in BPLCON3 (bit 9)
|
||||
3. **Write low nibble**: `COLOR00 = $0rgb` (bits [11:0] = R[3:0], G[3:0], B[3:0], these are the low 4 bits)
|
||||
|
||||
This two-write sequence gives 8 bits per channel (R[7:0], G[7:0], B[7:0]) = 24-bit colour.
|
||||
This two-write sequence gives 8 bits per channel (R[7:0], G[7:0], B[7:0]) = 24-bit color.
|
||||
|
||||
`LoadRGB32()` does this automatically:
|
||||
```c
|
||||
/* AGA 32-bit colour table format:
|
||||
Count, then pairs: [colour_index, 0x00RRGGBB] */
|
||||
ULONG colour_table[] = {
|
||||
32, 0, /* 32 colours starting at index 0 */
|
||||
/* AGA 32-bit color table format:
|
||||
Count, then pairs: [color_index, 0x00RRGGBB] */
|
||||
ULONG color_table[] = {
|
||||
32, 0, /* 32 colors starting at index 0 */
|
||||
0x00FF0000, /* COLOR00 = red */
|
||||
0x0000FF00, /* COLOR01 = green */
|
||||
/* ... */
|
||||
~0UL /* terminator */
|
||||
};
|
||||
LoadRGB32(vp, colour_table);
|
||||
LoadRGB32(vp, color_table);
|
||||
```
|
||||
|
||||
## References
|
||||
|
|
@ -94,4 +94,4 @@ LoadRGB32(vp, colour_table);
|
|||
- ADCD 2.1 Hardware Manual — AGA register appendix
|
||||
- NDK39: `hardware/custom.h`, `graphics/view.h`
|
||||
- Commodore A1200/A4000 Technical Reference Manuals
|
||||
- AmigaMail Vol. 2 — AGA colour programming
|
||||
- AmigaMail Vol. 2 — AGA color programming
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ Output: 8 longwords of planar data (1 longword per bitplane × 8 planes)
|
|||
move.l (a1), (a2)+ ; bitplane 7
|
||||
```
|
||||
|
||||
#### Optimised Loop (MOVEM)
|
||||
#### Optimized Loop (MOVEM)
|
||||
|
||||
In practice, the entire 32-pixel conversion is done with two MOVEM instructions:
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ In practice, the entire 32-pixel conversion is done with two MOVEM instructions:
|
|||
|
||||
#### Full-Screen Conversion Example
|
||||
|
||||
For a 320×256 screen at 8 bitplanes (256 colours):
|
||||
For a 320×256 screen at 8 bitplanes (256 colors):
|
||||
|
||||
```asm
|
||||
; Total pixels = 320 × 256 = 81,920
|
||||
|
|
@ -203,9 +203,9 @@ Communication with the drive is command/response based through the Akiko registe
|
|||
|
||||
The CD32 boots exclusively from CD-ROM (no floppy drive). The boot sequence:
|
||||
|
||||
1. Kickstart 3.1 initialises from ROM (`$F80000`)
|
||||
1. Kickstart 3.1 initializes from ROM (`$F80000`)
|
||||
2. Extended ROM at `$E00000` provides `cd.device` and the CD filesystem
|
||||
3. Akiko initialises the CD-ROM drive
|
||||
3. Akiko initializes the CD-ROM drive
|
||||
4. The system reads the TOC and looks for a boot block (Amiga executable format)
|
||||
5. If found, the boot executable is loaded and run — this is the game/application entry point
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ Alice is the successor to Super Agnus and is the DMA controller and Copper/Blitt
|
|||
Alice can fetch 2 or 4 words per DMA cycle via the `FMODE` register ($DFF1FC). This dramatically increases the bandwidth available to the blitter and bitplane DMA.
|
||||
|
||||
**Extended bitplane depth:**
|
||||
Alice supports up to **8 bitplanes** (256 colours), compared to OCS/ECS's 6-plane limit.
|
||||
Alice supports up to **8 bitplanes** (256 colors), compared to OCS/ECS's 6-plane limit.
|
||||
|
||||
**BPLCON4:**
|
||||
Alice adds `BPLCON4` to control bitplane bank selection — which 64-entry block of the 256-entry colour table is used by the bitplanes.
|
||||
Alice adds `BPLCON4` to control bitplane bank selection — which 64-entry block of the 256-entry color table is used by the bitplanes.
|
||||
|
||||
### ALICE_ID
|
||||
|
||||
|
|
@ -34,18 +34,18 @@ lsr.w #8, d0
|
|||
|
||||
## Lisa (AGA Denise)
|
||||
|
||||
Lisa is the display chip successor to ECS Denise, providing 8-bit colour output (256 colour registers) and extended sprite capabilities.
|
||||
Lisa is the display chip successor to ECS Denise, providing 8-bit color output (256 color registers) and extended sprite capabilities.
|
||||
|
||||
### Key Enhancements over ECS Denise
|
||||
|
||||
**256 colour registers:**
|
||||
**256 color registers:**
|
||||
Lisa provides COLOR00–COLOR255, each 24-bit (32-bit register with low byte unused).
|
||||
|
||||
**4 colour banks for bitplanes:**
|
||||
`BPLCON4` selects which 64-register bank (0–3) the bitplanes use for lookup. This allows dual-playfield each using a different 64-colour palette.
|
||||
**4 color banks for bitplanes:**
|
||||
`BPLCON4` selects which 64-register bank (0–3) the bitplanes use for lookup. This allows dual-playfield each using a different 64-color palette.
|
||||
|
||||
**Sprite bank selection:**
|
||||
`BPLCON3` bits select which colour bank sprite pairs use.
|
||||
`BPLCON3` bits select which color bank sprite pairs use.
|
||||
|
||||
**Extended sprite width:**
|
||||
Sprites can be 16 or 64 pixels wide in AGA mode.
|
||||
|
|
@ -105,7 +105,7 @@ bits 14-12: BPU2-0 — lower 3 bits of bitplane count
|
|||
bit 4: BPU3 — MSB of bitplane count (AGA: allows 7, 8 planes)
|
||||
```
|
||||
|
||||
To use 8 bitplanes (256 colours):
|
||||
To use 8 bitplanes (256 colors):
|
||||
```asm
|
||||
move.w #$9411, BPLCON0+custom ; HIRES=1 (if needed), BPU=8 (BPU3=1, BPU2-0=000), ECSENA=1
|
||||
```
|
||||
|
|
@ -123,4 +123,4 @@ move.w #$9411, BPLCON0+custom ; HIRES=1 (if needed), BPU=8 (BPU3=1, BPU2-0=000
|
|||
|
||||
- [Akiko — CD32 Custom Chip](akiko_cd32.md) — CD32-exclusive ASIC (C2P, CD-ROM, NVRAM) that sits alongside Alice/Lisa/Paula
|
||||
- [AGA Blitter](aga_blitter.md) — 64-bit FMODE blitter details
|
||||
- [AGA Palette](aga_palette.md) — 256-register 24-bit colour system
|
||||
- [AGA Palette](aga_palette.md) — 256-register 24-bit color system
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ When no Fast RAM is available, everything competes for the same 512 KB–2 MB:
|
|||
```c
|
||||
if (!hasFastRAM) {
|
||||
/* All memory is Chip RAM — conserve aggressively */
|
||||
numBitplanes = 4; /* Use 16 colours instead of 32 */
|
||||
numBitplanes = 4; /* Use 16 colors instead of 32 */
|
||||
useDoubleBuffer = FALSE; /* Single buffer saves 40 KB per plane */
|
||||
maxBOBs = 8; /* Fewer sprites = less Blitter work */
|
||||
musicQuality = QUALITY_LOW; /* 4-bit 11 kHz samples save Chip RAM */
|
||||
|
|
@ -312,7 +312,7 @@ if (!hasFastRAM) {
|
|||
}
|
||||
```
|
||||
|
||||
**Why**: On a Chip-only system, the CPU, Blitter, display, and audio all share one bus. Reducing display complexity (fewer bitplanes) frees DMA slots for the Blitter AND frees Chip RAM for audio/game data. This is why many A500 games use 4 bitplanes (16 colours) while the same game on an accelerated A1200 uses 5 or even 8.
|
||||
**Why**: On a Chip-only system, the CPU, Blitter, display, and audio all share one bus. Reducing display complexity (fewer bitplanes) frees DMA slots for the Blitter AND frees Chip RAM for audio/game data. This is why many A500 games use 4 bitplanes (16 colors) while the same game on an accelerated A1200 uses 5 or even 8.
|
||||
|
||||
### Strategy: Chip + Fast RAM Mode (Accelerated Systems)
|
||||
|
||||
|
|
@ -321,7 +321,7 @@ When Fast RAM is available, the architecture unlocks true parallelism:
|
|||
```c
|
||||
if (hasFastRAM) {
|
||||
/* CPU runs from Fast RAM at full speed; Chip RAM for DMA only */
|
||||
numBitplanes = 5; /* 32 colours — display looks better */
|
||||
numBitplanes = 5; /* 32 colors — display looks better */
|
||||
useDoubleBuffer = TRUE; /* Flicker-free, worth the Chip RAM */
|
||||
maxBOBs = 24; /* More BOBs — CPU can compute while Blitter blits */
|
||||
musicQuality = QUALITY_HIGH; /* 8-bit 22 kHz — Chip RAM freed by code in Fast RAM */
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ ULONG chip_free = AvailMem(MEMF_CHIP);
|
|||
ULONG chip_total = AvailMem(MEMF_CHIP | MEMF_TOTAL);
|
||||
```
|
||||
|
||||
The exec memory list is built at boot time from the chip RAM size detected by the ROM initialisation code, which queries Agnus's internal address counter.
|
||||
The exec memory list is built at boot time from the chip RAM size detected by the ROM initialization code, which queries Agnus's internal address counter.
|
||||
|
||||
## AmigaOS ROM Initialisation (Exec init)
|
||||
## AmigaOS ROM Initialization (Exec init)
|
||||
|
||||
During cold boot, the Kickstart ROM probes Chip RAM size:
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ During cold boot, the Kickstart ROM probes Chip RAM size:
|
|||
2. Read back — if the value matches, 2 MB Chip RAM is present
|
||||
3. The exec `MemHeader` for Chip RAM is extended to $1FFFFF
|
||||
|
||||
This is performed in the `RomBoot()` → `InitCode()` sequence before the exec memory system is fully initialised.
|
||||
This is performed in the `RomBoot()` → `InitCode()` sequence before the exec memory system is fully initialized.
|
||||
|
||||
## Implications for Programming
|
||||
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@ lsr.w #8, d0 ; shift to get Agnus ID in low byte
|
|||
|
||||
ECS Denise adds to OCS Denise (8362):
|
||||
|
||||
1. **BPLCON3** — new control register for border colour, sprite bank
|
||||
1. **BPLCON3** — new control register for border color, sprite bank
|
||||
2. **Sub-pixel scrolling** — additional scroll control bits
|
||||
3. **Genlock extensions** — improved external sync handling
|
||||
4. **Border blank** — BPLCON3 can blank the border area to colour 0
|
||||
4. **Border blank** — BPLCON3 can blank the border area to color 0
|
||||
|
||||
### DENISEID — Revision Register
|
||||
|
||||
|
|
@ -74,10 +74,10 @@ move.w $DFF07C, d0 ; read DENISEID
|
|||
New register at `$DFF106` (ECS only, must not be written on OCS):
|
||||
|
||||
```
|
||||
bit 15-13: BANK2-0 — sprite colour bank (AGA: upper 4 bits of colour reg)
|
||||
bit 12-10: PF2OF2-0 — playfield 2 colour offset (for dual playfield)
|
||||
bit 9: LOCT — low colour enable (AGA HAM8 mode)
|
||||
bit 6: BRDRBLNK — border blank: forces border area to colour 0
|
||||
bit 15-13: BANK2-0 — sprite color bank (AGA: upper 4 bits of color reg)
|
||||
bit 12-10: PF2OF2-0 — playfield 2 color offset (for dual playfield)
|
||||
bit 9: LOCT — low color enable (AGA HAM8 mode)
|
||||
bit 6: BRDRBLNK — border blank: forces border area to color 0
|
||||
bit 5: BRDNTRAN — border not-transparent (disable border transparency)
|
||||
bit 4: ZDCLKEN — horizontal/vertical count display
|
||||
bit 3: BRDSPRT — sprites in border area enable
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ bit 15: HARDDIS — disable hard limits on display window
|
|||
bit 14: LPENDIS — disable light pen latch
|
||||
bit 13: VARVBEN — enable variable VBlank
|
||||
bit 12: LOLDIS — disable long line sync
|
||||
bit 11: CSCBEN — composite sync on colour burst
|
||||
bit 11: CSCBEN — composite sync on color burst
|
||||
bit 10: VARVSYEN — variable vertical sync
|
||||
bit 9: VARHSYEN — variable horizontal sync
|
||||
bit 8: VARBEAMEN— variable beam enable
|
||||
|
|
@ -29,7 +29,7 @@ bit 1: HSYTRUE — horizontal sync polarity
|
|||
bit 0: MONCSYEN — monochrome composite sync enable
|
||||
```
|
||||
|
||||
**Default OCS behaviour** is replicated by writing $0000 to BEAMCON0 on ECS.
|
||||
**Default OCS behavior** is replicated by writing $0000 to BEAMCON0 on ECS.
|
||||
|
||||
**PAL/NTSC software switch:**
|
||||
```asm
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
- **SCSI interface glue**: Works with the A3000's built-in WD33C93 SCSI controller
|
||||
- **ROM decode**: Maps Kickstart ROM into the address space
|
||||
|
||||
Gary is not directly programmable by user software; its configuration is set by hardware strapping and the ROM initialisation sequence.
|
||||
Gary is not directly programmable by user software; its configuration is set by hardware strapping and the ROM initialization sequence.
|
||||
|
||||
## Bus Arbitration
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ The A4000 does **not** use Gary — it uses a different system controller chip c
|
|||
|
||||
- Commodore A3000 Technical Reference Manual
|
||||
- ADCD 2.1 — Hardware Manual, A3000 chapter
|
||||
- NDK39: hardware headers (community-documented Gary behaviour)
|
||||
- NDK39: hardware headers (community-documented Gary behavior)
|
||||
|
||||
## See Also
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ The **Original Chip Set** (OCS) ships in the Amiga 1000 (1985), A500 (1987), and
|
|||
| Chip | MOS Part | Primary Responsibilities |
|
||||
|---|---|---|
|
||||
| **Agnus** | 8361 (PAL), 8367 (NTSC) | DMA controller, Copper coprocessor, Blitter, address generation |
|
||||
| **Denise** | 8362 | Display: bitplane fetch decode, sprite decode, colour output |
|
||||
| **Denise** | 8362 | Display: bitplane fetch decode, sprite decode, color output |
|
||||
| **Paula** | 8364 | Audio DMA (4 channels), floppy disk I/O, serial port, interrupts |
|
||||
|
||||
## Contents
|
||||
|
|
@ -90,7 +90,7 @@ The CDTV is an A500-class OCS computer in a consumer set-top box form factor. Se
|
|||
- Max **512 KB Chip RAM** on A500 rev 5 and earlier (Agnus 8361/8367 addresses 512 KB only)
|
||||
- A500 rev 6+ allows 1 MB with Fat Agnus (part of later OCS run)
|
||||
- No productivity display modes (ECS adds BEAMCON0)
|
||||
- 32 colours max (or 64 EHB, or HAM 12-bit) in standard bitplane modes
|
||||
- 32 colors max (or 64 EHB, or HAM 12-bit) in standard bitplane modes
|
||||
- Blitter is 16-bit; no 64-bit fetch (AGA adds FMODE)
|
||||
- No ECS Denise border features
|
||||
|
||||
|
|
|
|||
|
|
@ -128,8 +128,8 @@ The Extended ROM is **not present** on standard A500/A2000 machines. Software th
|
|||
### Boot Sequence
|
||||
|
||||
1. Kickstart 1.3 loads from ROM (`$F80000`)
|
||||
2. Extended ROM at `$E00000` is detected and initialised
|
||||
3. `scsi.device` from Extended ROM initialises DMAC + WD33C93
|
||||
2. Extended ROM at `$E00000` is detected and initialized
|
||||
3. `scsi.device` from Extended ROM initializes DMAC + WD33C93
|
||||
4. CD-ROM drive is probed for a bootable disc
|
||||
5. If a valid Amiga boot block is found on the CD → boot from CD
|
||||
6. If no CD → fall through to standard floppy boot (if external floppy present)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ block-beta
|
|||
|
||||
## DMA Channels and Priorities
|
||||
|
||||
Agnus schedules DMA cycles across a fixed priority scheme within each horizontal raster line (228 colour clocks per line, PAL):
|
||||
Agnus schedules DMA cycles across a fixed priority scheme within each horizontal raster line (228 color clocks per line, PAL):
|
||||
|
||||
| Priority | DMA Channel | Register Bits |
|
||||
|---|---|---|
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
The **Copper** (co-processor) is built into Agnus. It executes a simple instruction list (the **copperlist**) in sync with the video beam, allowing precise per-scanline changes to any writable custom register — without CPU intervention.
|
||||
|
||||
The Copper can only write to custom registers (it cannot access Chip RAM directly), but it can change bitplane pointers, colours, BPLCON0, sprite pointers, and any other `$DFF0xx` register on a cycle-accurate basis.
|
||||
The Copper can only write to custom registers (it cannot access Chip RAM directly), but it can change bitplane pointers, colors, BPLCON0, sprite pointers, and any other `$DFF0xx` register on a cycle-accurate basis.
|
||||
|
||||
## Copper Instruction Set
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ A copperlist is an array of 32-bit instruction pairs in **Chip RAM**, terminated
|
|||
DC.W $FFFF, $FFFE
|
||||
```
|
||||
|
||||
Example — colour cycle on vertical blank:
|
||||
Example — color cycle on vertical blank:
|
||||
```asm
|
||||
Copperlist:
|
||||
DC.W $0180, $0000 ; COLOR00 = black
|
||||
|
|
@ -110,7 +110,7 @@ Common copper techniques:
|
|||
|
||||
**BPLCON0 mid-screen:** Switch between `HIRES` and `LORES`, or between 6-plane and 4-plane modes, on different lines.
|
||||
|
||||
**Raster bars:** Write a different colour to COLOR00 on every scanline using sequential WAIT+MOVE pairs.
|
||||
**Raster bars:** Write a different color to COLOR00 on every scanline using sequential WAIT+MOVE pairs.
|
||||
|
||||
## Graphics Library vs Direct Copper
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ bit 15: HIRES (1 = 640 pixel wide)
|
|||
bit 14-12: BPU2-0 (number of bitplanes: 0–6)
|
||||
bit 11: HAM (1 = Hold-And-Modify mode)
|
||||
bit 10: DPF (dual playfield)
|
||||
bit 9: COLOR (0 = monochrome, 1 = colour)
|
||||
bit 9: COLOR (0 = monochrome, 1 = color)
|
||||
bit 8: GAUD (genlock audio)
|
||||
bit 7-4: (various, OCS = 0)
|
||||
bit 1: ERSY (external sync)
|
||||
|
|
@ -165,16 +165,16 @@ bit 0: ECSENA (ECS enable — must be 0 on OCS)
|
|||
| $09E | ADKCON | W | Audio / disk control (write) |
|
||||
| $07C | DSKSYNC | W | Disk sync word |
|
||||
|
||||
## Colour Registers
|
||||
## Color Registers
|
||||
|
||||
| Offset | Name | Dir | Description |
|
||||
|---|---|---|---|
|
||||
| $180 | COLOR00 | W | Background / colour 0 |
|
||||
| $182 | COLOR01 | W | Colour 1 |
|
||||
| $180 | COLOR00 | W | Background / color 0 |
|
||||
| $182 | COLOR01 | W | Color 1 |
|
||||
| ... | | | |
|
||||
| $1BE | COLOR31 | W | Colour 31 |
|
||||
| $1BE | COLOR31 | W | Color 31 |
|
||||
|
||||
OCS colours: 12-bit RGB (4 bits per component, $0RGB format).
|
||||
OCS colors: 12-bit RGB (4 bits per component, $0RGB format).
|
||||
|
||||
## References
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ The OCS/ECS chipset provides **8 hardware sprites**, each 16 pixels wide and arb
|
|||
| Count | 8 sprites |
|
||||
| Width | 16 pixels fixed |
|
||||
| Height | Programmable (any number of lines) |
|
||||
| Colours | 3 (+1 transparent) per sprite |
|
||||
| Colour source | Sprite colour registers (COLOR16–COLOR31) |
|
||||
| Attach mode | Pairs 0/1, 2/3, 4/5, 6/7 → 15 colours |
|
||||
| Colors | 3 (+1 transparent) per sprite |
|
||||
| Color source | Sprite color registers (COLOR16–COLOR31) |
|
||||
| Attach mode | Pairs 0/1, 2/3, 4/5, 6/7 → 15 colors |
|
||||
|
||||
## Sprite Registers
|
||||
|
||||
|
|
@ -60,14 +60,14 @@ Each line of the sprite consists of two 16-bit words (DATA and DATB) fetched fro
|
|||
|
||||
```
|
||||
For each scanline of sprite:
|
||||
Word 1 (DATA): bit 15..0 → pixel bit 1 (colour bit 1)
|
||||
Word 2 (DATB): bit 15..0 → pixel bit 0 (colour bit 0)
|
||||
Word 1 (DATA): bit 15..0 → pixel bit 1 (color bit 1)
|
||||
Word 2 (DATB): bit 15..0 → pixel bit 0 (color bit 0)
|
||||
|
||||
Pixel colour:
|
||||
Pixel color:
|
||||
DATA[bit] = 0, DATB[bit] = 0 → transparent
|
||||
DATA[bit] = 0, DATB[bit] = 1 → colour 1 (COLOR17 for sprite 0)
|
||||
DATA[bit] = 1, DATB[bit] = 0 → colour 2 (COLOR18)
|
||||
DATA[bit] = 1, DATB[bit] = 1 → colour 3 (COLOR19)
|
||||
DATA[bit] = 0, DATB[bit] = 1 → color 1 (COLOR17 for sprite 0)
|
||||
DATA[bit] = 1, DATB[bit] = 0 → color 2 (COLOR18)
|
||||
DATA[bit] = 1, DATB[bit] = 1 → color 3 (COLOR19)
|
||||
```
|
||||
|
||||
## Sprite Data in Memory
|
||||
|
|
@ -85,29 +85,29 @@ Agnus DMA reads the sprite from a memory block structured as:
|
|||
Word: $0000 (SPRnCTL = 0)
|
||||
```
|
||||
|
||||
## Colour Mapping
|
||||
## Color Mapping
|
||||
|
||||
Sprites share colour registers with bitplanes:
|
||||
Sprites share color registers with bitplanes:
|
||||
|
||||
| Sprites | Colour Registers |
|
||||
| Sprites | Color Registers |
|
||||
|---|---|
|
||||
| 0 and 1 | COLOR16–COLOR19 |
|
||||
| 2 and 3 | COLOR20–COLOR23 |
|
||||
| 4 and 5 | COLOR24–COLOR27 |
|
||||
| 6 and 7 | COLOR28–COLOR31 |
|
||||
|
||||
COLOR16 (the first colour of sprite pair 0/1) is always transparent — the sprite background. Only COLOR17–COLOR19 are visible for sprites 0/1.
|
||||
COLOR16 (the first color of sprite pair 0/1) is always transparent — the sprite background. Only COLOR17–COLOR19 are visible for sprites 0/1.
|
||||
|
||||
## Attached Sprites (15 Colours)
|
||||
## Attached Sprites (15 Colors)
|
||||
|
||||
Pairing two sprites (`ATT` bit in SPRnCTL of the even sprite) combines their DATA/DATB bits to produce a 4-bit colour index (16 colours, one transparent):
|
||||
Pairing two sprites (`ATT` bit in SPRnCTL of the even sprite) combines their DATA/DATB bits to produce a 4-bit color index (16 colors, one transparent):
|
||||
|
||||
```
|
||||
4-bit colour = {SPR_even.DATA[bit], SPR_even.DATB[bit],
|
||||
4-bit color = {SPR_even.DATA[bit], SPR_even.DATB[bit],
|
||||
SPR_odd.DATA[bit], SPR_odd.DATB[bit]}
|
||||
```
|
||||
|
||||
This gives 15 visible colours per pair, using COLOR16–COLOR31 for pair 0/1.
|
||||
This gives 15 visible colors per pair, using COLOR16–COLOR31 for pair 0/1.
|
||||
|
||||
## BPLCON2 — Sprite Priority
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ Default: sprites appear in front of all bitplanes.
|
|||
|
||||
## OS Mouse Pointer
|
||||
|
||||
AmigaOS's Intuition uses sprite 0 (and 1 in attached mode for colour pointer) for the mouse pointer. Intuition calls `SetPointer()` / `ClearPointer()` on a Window to install custom pointer sprites.
|
||||
AmigaOS's Intuition uses sprite 0 (and 1 in attached mode for color pointer) for the mouse pointer. Intuition calls `SetPointer()` / `ClearPointer()` on a Window to install custom pointer sprites.
|
||||
|
||||
```c
|
||||
SetPointer(window, pointer_data, height, width, x_offset, y_offset);
|
||||
|
|
|
|||
|
|
@ -209,11 +209,11 @@ while ((cd = FindConfigDev(cd, -1, -1)) != NULL)
|
|||
|
||||
## Step 5: ROM Self-Test and Diagnostic Indicators
|
||||
|
||||
The Amiga has a multi-layered diagnostic system that uses screen colours, power LED patterns, and keyboard LED blink codes to communicate status. Understanding these signals is essential for FPGA core development and hardware debugging.
|
||||
The Amiga has a multi-layered diagnostic system that uses screen colors, power LED patterns, and keyboard LED blink codes to communicate status. Understanding these signals is essential for FPGA core development and hardware debugging.
|
||||
|
||||
### Normal Boot Colour Sequence
|
||||
### Normal Boot Color Sequence
|
||||
|
||||
A healthy boot cycles through these colours rapidly (total <1 second on 68000):
|
||||
A healthy boot cycles through these colors rapidly (total <1 second on 68000):
|
||||
|
||||
```
|
||||
┌─────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ ┌──────────┐ ┌─────────┐
|
||||
|
|
@ -223,16 +223,16 @@ A healthy boot cycles through these colours rapidly (total <1 second on 68000):
|
|||
└─────────┘ └──────────┘ └───────────┘ └───────────┘ └──────────┘ └─────────┘
|
||||
```
|
||||
|
||||
If the sequence **stops** at any colour, that colour identifies the failure point.
|
||||
If the sequence **stops** at any color, that color identifies the failure point.
|
||||
|
||||
### Screen Colour Diagnostic Table
|
||||
### Screen Color Diagnostic Table
|
||||
|
||||
| Colour | Hex | Phase | Indicates | What Failed |
|
||||
| Color | Hex | Phase | Indicates | What Failed |
|
||||
|---|---|---|---|---|
|
||||
| Black (stuck) | `$000` | Pre-init | CPU not executing | No clock, dead CPU, no ROM addressing |
|
||||
| Dark grey | `$444` | Post-checksum | ROM checksum passed | (Normal — transient) |
|
||||
| Medium grey | `$888` | Memory test | Chip RAM sized OK | (Normal — transient) |
|
||||
| Light grey | `$AAA` | Exec init | ExecBase created | (Normal — transient) |
|
||||
| Dark gray | `$444` | Post-checksum | ROM checksum passed | (Normal — transient) |
|
||||
| Medium gray | `$888` | Memory test | Chip RAM sized OK | (Normal — transient) |
|
||||
| Light gray | `$AAA` | Exec init | ExecBase created | (Normal — transient) |
|
||||
| White | `$FFF` | Resident scan | Modules initializing | (Normal — transient) |
|
||||
| Green flash | `$0F0` | DOS boot | dos.library starting | (Normal — transient) |
|
||||
| **Red** (stuck) | `$F00` | **Checksum** | **ROM checksum failed** | Bad ROM chip, wrong image, bit rot |
|
||||
|
|
@ -242,17 +242,17 @@ If the sequence **stops** at any colour, that colour identifies the failure poin
|
|||
| **Magenta** (stuck) | `$F0F` | **Exception** | **Hardware trap** | Unexpected interrupt or bus error |
|
||||
| **Cyan** (stuck) | `$0FF` | **Misc** | **CIA or clock** | Timer initialization failure |
|
||||
|
||||
### How the ROM Sets Diagnostic Colours
|
||||
### How the ROM Sets Diagnostic Colors
|
||||
|
||||
```asm
|
||||
; The boot code writes COLOR00 at each milestone:
|
||||
; After ROM checksum passes:
|
||||
MOVE.W #$0444,$DFF180 ; Dark grey → "ROM OK"
|
||||
MOVE.W #$0444,$DFF180 ; Dark gray → "ROM OK"
|
||||
|
||||
; After Chip RAM test passes:
|
||||
MOVE.W #$0888,$DFF180 ; Medium grey → "RAM OK"
|
||||
MOVE.W #$0888,$DFF180 ; Medium gray → "RAM OK"
|
||||
|
||||
; On failure — set error colour and halt:
|
||||
; On failure — set error color and halt:
|
||||
RomChecksumFailed:
|
||||
MOVE.W #$0F00,$DFF180 ; Red screen
|
||||
.hang:
|
||||
|
|
@ -264,7 +264,7 @@ RamTestFailed:
|
|||
BRA.S .hang
|
||||
```
|
||||
|
||||
### Power LED Behaviour
|
||||
### Power LED Behavior
|
||||
|
||||
| Pattern | Meaning |
|
||||
|---|---|
|
||||
|
|
@ -290,15 +290,15 @@ The Amiga keyboard has its own 6500/1 microcontroller that performs a self-test
|
|||
|
||||
The keyboard communicates with the main system via a synchronous serial protocol through CIA-A. If the keyboard passes self-test, it sends a power-up key stream (`$FD` = initiate power-up, then `$FE` = terminate power-up).
|
||||
|
||||
### Normal vs Abnormal Boot Behaviour
|
||||
### Normal vs Abnormal Boot Behavior
|
||||
|
||||
**Normal boot** (A500/A1200 with Kickstart 3.1):
|
||||
|
||||
```
|
||||
0 ms: Black screen
|
||||
~5 ms: Dark grey (ROM checksum calculating)
|
||||
~600 ms: Medium grey (checksum done, RAM test running)
|
||||
~650 ms: Light grey (ExecBase init)
|
||||
~5 ms: Dark gray (ROM checksum calculating)
|
||||
~600 ms: Medium gray (checksum done, RAM test running)
|
||||
~650 ms: Light gray (ExecBase init)
|
||||
~700 ms: White (resident scan starting)
|
||||
~900 ms: Colors flash rapidly (graphics.library init, display setup)
|
||||
~1.2 s: Kickstart hand/checkmark animation appears
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
After the Kickstart ROM initialises the kernel and resident modules, the `strap` (bootstrap) module takes over. It enumerates bootable devices, reads and executes the boot block, mounts filesystems, and runs the user's startup scripts. This phase transitions the system from "kernel initialized" to "fully running desktop."
|
||||
After the Kickstart ROM initializes the kernel and resident modules, the `strap` (bootstrap) module takes over. It enumerates bootable devices, reads and executes the boot block, mounts filesystems, and runs the user's startup scripts. This phase transitions the system from "kernel initialized" to "fully running desktop."
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Hold **both mouse buttons** (left + right) immediately after power-on or during
|
|||
|
||||
| Kickstart | When to Hold | Visual Cue |
|
||||
|---|---|---|
|
||||
| 2.0–3.0 | During boot colour sequence | Before "Insert Disk" or disk activity |
|
||||
| 2.0–3.0 | During boot color sequence | Before "Insert Disk" or disk activity |
|
||||
| 3.1 | During the hand/checkmark animation | While the Amiga hand appears |
|
||||
| 3.1.4 / 3.2 | Same as 3.1 | Same timing window |
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ if (leftButton && rightButton)
|
|||
|
||||
The ESC menu uses the lowest-level graphics possible:
|
||||
|
||||
- Opens a 640×200 (or 640×256 PAL) 2-colour screen
|
||||
- Opens a 640×200 (or 640×256 PAL) 2-color screen
|
||||
- Uses the Topaz 8 ROM font (always available — no disk access needed)
|
||||
- Renders using direct `RastPort` calls — no windows, no layers
|
||||
- Mouse pointer uses hardware sprite 0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[← Home](../README.md) · [Boot Sequence](README.md)
|
||||
|
||||
# Kickstart Initialisation — ExecBase, ROM Scan, Resident Modules
|
||||
# Kickstart Initialization — ExecBase, ROM Scan, Resident Modules
|
||||
|
||||
## Overview
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ Priority -120: ramlib / strap ← Last
|
|||
|
||||
---
|
||||
|
||||
## Initialisation Phases
|
||||
## Initialization Phases
|
||||
|
||||
### Phase 1: RTF_SINGLETASK
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
**Executable crunchers** (packers) compress AmigaOS executables while keeping them directly runnable. The crunched file is a valid HUNK executable — when launched, a tiny **decrunch stub** runs first, decompresses the original program in memory, then jumps to its real entry point. The user sees a brief colour-cycling delay (the "decrunch colours"), then the program runs normally.
|
||||
**Executable crunchers** (packers) compress AmigaOS executables while keeping them directly runnable. The crunched file is a valid HUNK executable — when launched, a tiny **decrunch stub** runs first, decompresses the original program in memory, then jumps to its real entry point. The user sees a brief color-cycling delay (the "decrunch colors"), then the program runs normally.
|
||||
|
||||
This was essential in the floppy era: a 200 KB program crunched to 120 KB loads significantly faster from a slow 880 KB floppy *and* frees disk space on capacity-constrained media.
|
||||
|
||||
|
|
@ -192,7 +192,7 @@ Sophisticated crunchers solve this by:
|
|||
| **Titanics Cruncher** (ATN!) | 1991–1993 | LZ77 | ~350 bytes | 55–65% | Fast decrunch |
|
||||
| **CrunchMania** (CrM!) | 1992–1995 | LZ + range coding | ~500 bytes | 40–50% | Many registered/customised versions — format variants |
|
||||
| **Shrinkler** | 2014+ | Context-model + range coder | ~250 bytes | 30–40% | Modern; best ratio; used in 4K/64K demo intros |
|
||||
| **PackFire** | 2016+ | Shrinkler derivative | ~200 bytes | 30–40% | Optimised for size-limited compos |
|
||||
| **PackFire** | 2016+ | Shrinkler derivative | ~200 bytes | 30–40% | Optimized for size-limited compos |
|
||||
| **XPK** | 1992+ | Framework (multiple sub-packers) | varies | varies | Library-based; supports NUKE, SMPL, SQSH, etc. |
|
||||
|
||||
---
|
||||
|
|
@ -266,13 +266,13 @@ The 4-byte efficiency table controls how many bits are used for offset/length en
|
|||
|
||||
The decompressor reads these 4 bytes to initialize its internal offset/length bit-allocation tables before starting the main decompression loop.
|
||||
|
||||
### Decrunch Colours
|
||||
### Decrunch Colors
|
||||
|
||||
The PowerPacker decrunch stub famously modifies custom chip colour registers during decompression to provide visual feedback — the background colour cycles through shades of grey or colour gradients, signalling that decrunching is in progress. This is the characteristic "decrunch effect" visible on real hardware:
|
||||
The PowerPacker decrunch stub famously modifies custom chip color registers during decompression to provide visual feedback — the background color cycles through shades of gray or color gradients, signalling that decrunching is in progress. This is the characteristic "decrunch effect" visible on real hardware:
|
||||
|
||||
```asm
|
||||
; Visual feedback during decrunch:
|
||||
MOVE.W D0, $DFF180 ; COLOR00 — background colour
|
||||
MOVE.W D0, $DFF180 ; COLOR00 — background color
|
||||
; D0 increments with each decompressed block
|
||||
```
|
||||
|
||||
|
|
@ -507,7 +507,7 @@ AFTER (stub jumps to original entry):
|
|||
1. **Tiny code hunk + large data hunk** — unusual ratio signals packing
|
||||
2. **AllocMem + decompression loop** at entry point — not the normal `c.o` startup pattern
|
||||
3. **No `MOVE.L 4.W,A6` / `OpenLibrary` sequence** — stub goes straight to decompression
|
||||
4. **Custom chip register writes** (`$DFF180` colour changes) — decrunch colour feedback
|
||||
4. **Custom chip register writes** (`$DFF180` color changes) — decrunch color feedback
|
||||
5. **Magic bytes** in the data hunk — scan for known signatures
|
||||
6. **Self-modifying code** — stub may overwrite its own memory during in-place decompression
|
||||
|
||||
|
|
@ -606,21 +606,6 @@ For unknown or custom crunchers, the most reliable method is to load the executa
|
|||
> sm $dest $dest+size "decrunched.bin" ; save memory
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact on FPGA / Emulation
|
||||
|
||||
| Concern | Detail |
|
||||
|---|---|
|
||||
| **Timing-sensitive stubs** | Imploder has tight loops that may fail on accelerated CPUs; some stubs poll `$DFF006` (VHPOSR) for timing |
|
||||
| **Memory allocation** | Stub requires working `exec.library AllocMem` — must have a functional memory list |
|
||||
| **Chip RAM specificity** | If original hunks need CHIP RAM, stub must request `MEMF_CHIP` — DMA-accessible memory required for graphics/audio |
|
||||
| **Self-modifying code** | In-place decompression writes over instruction bytes — 68020+ instruction cache must be invalidated (`CacheClearU`) |
|
||||
| **Custom chip access** | Decrunch colour writes to `$DFF180` require a working Denise/colour register |
|
||||
| **Boot-block crunchers** | Trackloaders (game boot blocks) use custom crunchers without HUNK format — completely different mechanism, no OS involvement |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- PowerPacker documentation (Nico François, 1989)
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ struct Process *proc = CreateNewProcTags(
|
|||
TAG_DONE);
|
||||
```
|
||||
|
||||
Internally this calls `exec.library MakeNode()` and initialises:
|
||||
Internally this calls `exec.library MakeNode()` and initializes:
|
||||
- `Process->pr_SegList` — the segment list BPTR
|
||||
- Stack: allocated via `AllocMem(NP_StackSize, MEMF_PUBLIC)`, stored in `pr_Stack`
|
||||
- `tc_SPLower` / `tc_SPUpper` — stack bounds
|
||||
|
|
|
|||
|
|
@ -422,7 +422,7 @@ HUNK_END ($3F2) End of this hunk
|
|||
HUNK_END ($3F2) End of this hunk
|
||||
```
|
||||
|
||||
No data follows — BSS is zero-initialised by the loader.
|
||||
No data follows — BSS is zero-initialized by the loader.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ extern struct DosLibrary *DOSBase;
|
|||
extern struct ExecBase *SysBase;
|
||||
```
|
||||
|
||||
These are initialised in `c.o` (the startup stub):
|
||||
These are initialized in `c.o` (the startup stub):
|
||||
```asm
|
||||
_c_start:
|
||||
MOVE.L 4.W, A6 ; SysBase from exception vector
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ The double-jump (JSR→JMP→function→RTS) costs approximately 12 extra cycles
|
|||
```c
|
||||
struct Library *MakeLibrary(
|
||||
APTR funcArray, /* A0: function pointer array */
|
||||
APTR structInit, /* A1: struct initialiser table (or NULL) */
|
||||
APTR structInit, /* A1: struct initializer table (or NULL) */
|
||||
APTR initFunc, /* A2: init function (or NULL) */
|
||||
ULONG dataSize, /* D0: sizeof(MyLibBase) */
|
||||
BPTR segList /* D1: segment list (for UnLoadSeg on expunge) */
|
||||
|
|
@ -409,8 +409,8 @@ void SumLibrary(struct Library *lib)
|
|||
stateDiagram-v2
|
||||
[*] --> OnDisk : LIBS:my.library file
|
||||
OnDisk --> Loading : OpenLibrary("my.library", 1)
|
||||
Loading --> Initialised : LoadSeg + MakeLibrary + LibInit
|
||||
Initialised --> Added : AddLibrary → SysBase→LibList
|
||||
Loading --> Initialized : LoadSeg + MakeLibrary + LibInit
|
||||
Initialized --> Added : AddLibrary → SysBase→LibList
|
||||
Added --> Open : OpenLibrary → Open() LVO → lib_OpenCnt++
|
||||
Open --> Open : More OpenLibrary calls
|
||||
Open --> Closing : CloseLibrary → Close() LVO → lib_OpenCnt--
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ void install_patch(void)
|
|||
|
||||
## Calling the Original (Chaining)
|
||||
|
||||
The replacement function **must** call the original to maintain correct library behaviour. The old function address returned by `SetFunction` is a raw pointer to the original function body (not the JMP slot):
|
||||
The replacement function **must** call the original to maintain correct library behavior. The old function address returned by `SetFunction` is a raw pointer to the original function body (not the JMP slot):
|
||||
|
||||
```c
|
||||
/* Jump to original using register convention */
|
||||
|
|
|
|||
|
|
@ -393,8 +393,8 @@ void TryFreeMemory(void)
|
|||
stateDiagram-v2
|
||||
[*] --> Unloaded : File on disk
|
||||
Unloaded --> Loading : OpenLibrary → ramlib
|
||||
Loading --> Initialised : LoadSeg + InitResident
|
||||
Initialised --> InLibList : AddLibrary → SysBase→LibList
|
||||
Loading --> Initialized : LoadSeg + InitResident
|
||||
Initialized --> InLibList : AddLibrary → SysBase→LibList
|
||||
InLibList --> Open : Open() LVO, lib_OpenCnt=1
|
||||
Open --> Open : OpenLibrary (lib_OpenCnt++)
|
||||
Open --> Closing : CloseLibrary (lib_OpenCnt--)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
The AmigaOS **startup code** is the first code that runs when an executable is launched. It bridges the OS loader (which jumps to hunk 0 offset 0) and the C `main()` function. Understanding it is critical for reverse engineering — it reveals the initialisation sequence, library opens, argument parsing, and the Workbench vs CLI detection path.
|
||||
The AmigaOS **startup code** is the first code that runs when an executable is launched. It bridges the OS loader (which jumps to hunk 0 offset 0) and the C `main()` function. Understanding it is critical for reverse engineering — it reveals the initialization sequence, library opens, argument parsing, and the Workbench vs CLI detection path.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ void rd_Read(struct IOStdReq *io) {
|
|||
|
||||
## Memory Allocation Strategy
|
||||
|
||||
On initialisation, `ramdrive.device` uses `AllocMem`:
|
||||
On initialization, `ramdrive.device` uses `AllocMem`:
|
||||
|
||||
```c
|
||||
rdbase->rd_RAMStart = AllocMem(rdbase->rd_RAMSize,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ graph LR
|
|||
| 10 | `$028` | Line-A Emulator | Unused (available for soft traps) |
|
||||
| 11 | `$02C` | Line-F Emulator | 68040/060.library FPU emulation |
|
||||
| 12–14 | `$030–$038` | Reserved | — |
|
||||
| 15 | `$03C` | Uninitialised Interrupt | Alert |
|
||||
| 15 | `$03C` | Uninitialized Interrupt | Alert |
|
||||
| 24 | `$060` | Spurious Interrupt | Ignored |
|
||||
| 25–31 | `$064–$07C` | Auto-vector interrupts 1–7 | Exec interrupt dispatcher |
|
||||
| 32–47 | `$080–$0BC` | TRAP #0–#15 | User-installable traps |
|
||||
|
|
|
|||
|
|
@ -117,9 +117,9 @@ if (!DOSBase)
|
|||
|
||||
1. **Scan `SysBase→LibList`** for a node whose `ln_Name` matches
|
||||
2. **If not found**: search the resident module list (`FindResident`)
|
||||
3. **If not resident**: search `LIBS:` assign path, `LoadSeg` the file, find RomTag, initialise
|
||||
3. **If not resident**: search `LIBS:` assign path, `LoadSeg` the file, find RomTag, initialize
|
||||
4. **Check version**: `lib_Version >= requestedVersion`?
|
||||
5. **Call library's `Open()` vector** — library-specific initialisation, `lib_OpenCnt++`
|
||||
5. **Call library's `Open()` vector** — library-specific initialization, `lib_OpenCnt++`
|
||||
6. **Return** library base pointer (or NULL on failure)
|
||||
|
||||
### Closing
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ struct Library *MakeLibrary(
|
|||
); /* LVO -84 */
|
||||
```
|
||||
|
||||
This combines allocation, JMP table construction, data initialisation, and init-function calling into one operation. Used by `RTF_AUTOINIT` modules and direct library creation.
|
||||
This combines allocation, JMP table construction, data initialization, and init-function calling into one operation. Used by `RTF_AUTOINIT` modules and direct library creation.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ struct MinList {
|
|||
|
||||
---
|
||||
|
||||
## Initialising a List
|
||||
## Initializing a List
|
||||
|
||||
```c
|
||||
struct List myList;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
AmigaOS ROM and disk-resident modules (libraries, devices, resources) identify themselves via a **RomTag** structure. At boot, exec scans the Kickstart ROM and any loaded segments for RomTags and initialises every module it finds. The RomTag system is how the kernel discovers and bootstraps itself — exec.library, graphics.library, dos.library, and all ROM-resident code use this mechanism.
|
||||
AmigaOS ROM and disk-resident modules (libraries, devices, resources) identify themselves via a **RomTag** structure. At boot, exec scans the Kickstart ROM and any loaded segments for RomTags and initializes every module it finds. The RomTag system is how the kernel discovers and bootstraps itself — exec.library, graphics.library, dos.library, and all ROM-resident code use this mechanism.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ struct Resident {
|
|||
UBYTE rt_Flags; /* RTF_* flags */
|
||||
UBYTE rt_Version; /* module version number */
|
||||
UBYTE rt_Type; /* NT_LIBRARY, NT_DEVICE, NT_RESOURCE, ... */
|
||||
BYTE rt_Pri; /* initialisation priority (higher = earlier) */
|
||||
BYTE rt_Pri; /* initialization priority (higher = earlier) */
|
||||
char *rt_Name; /* module name string, e.g. "dos.library" */
|
||||
char *rt_IdString; /* human-readable ID, e.g. "dos.library 40.1" */
|
||||
APTR rt_Init; /* init function or InitTable pointer */
|
||||
|
|
@ -97,7 +97,7 @@ struct Resident {
|
|||
|
||||
---
|
||||
|
||||
## RTF_AUTOINIT — Automatic Initialisation
|
||||
## RTF_AUTOINIT — Automatic Initialization
|
||||
|
||||
When `RTF_AUTOINIT` is set, `rt_Init` points to an `InitTable`:
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ UWORD dataTable[] = {
|
|||
|
||||
## ROM Scan at Boot
|
||||
|
||||
During exec initialisation:
|
||||
During exec initialization:
|
||||
|
||||
1. Walk from Kickstart base (`$F80000` for 512K ROM, `$FC0000` for 256K) upward
|
||||
2. Search for the `$4AFC` magic word at even addresses
|
||||
|
|
|
|||
|
|
@ -60,10 +60,10 @@ struct SignalSemaphore {
|
|||
|
||||
---
|
||||
|
||||
## Initialising a Semaphore
|
||||
## Initializing a Semaphore
|
||||
|
||||
```c
|
||||
/* Stack or heap — always initialise before use: */
|
||||
/* Stack or heap — always initialize before use: */
|
||||
struct SignalSemaphore sem;
|
||||
InitSemaphore(&sem); /* LVO -558 */
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ SetVBuf(fh, NULL, BUF_NONE, 0);
|
|||
Flush(fh);
|
||||
```
|
||||
|
||||
| Buffer Type | Constant | Behaviour |
|
||||
| Buffer Type | Constant | Behavior |
|
||||
|---|---|---|
|
||||
| `BUF_FULL` | 1 | Flush when buffer fills |
|
||||
| `BUF_LINE` | 0 | Flush on newline or buffer full |
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# Graphics Subsystem — Overview
|
||||
|
||||
The Amiga graphics system is built on custom DMA-driven hardware (Agnus/Alice + Denise/Lisa) managed through `graphics.library`. It supports planar bitmaps, hardware sprites, a Copper display coprocessor, and a Blitter for fast 2D operations. Three chipset generations (OCS → ECS → AGA) expanded resolution, colour depth, and bandwidth.
|
||||
The Amiga graphics system is built on custom DMA-driven hardware (Agnus/Alice + Denise/Lisa) managed through `graphics.library`. It supports planar bitmaps, hardware sprites, a Copper display coprocessor, and a Blitter for fast 2D operations. Three chipset generations (OCS → ECS → AGA) expanded resolution, color depth, and bandwidth.
|
||||
|
||||
## Section Index
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ The Amiga graphics system is built on custom DMA-driven hardware (Agnus/Alice +
|
|||
| [copper_programming.md](copper_programming.md) | Copper deep dive: architecture, copper list construction, gradient and raster effects |
|
||||
| [blitter.md](blitter.md) | Blitter DMA engine, minterms, BltBitMap |
|
||||
| [blitter_programming.md](blitter_programming.md) | Blitter deep dive: minterms, cookie-cut masking, line draw, fill mode |
|
||||
| [sprites.md](sprites.md) | Hardware sprites: DMA engine, data format, attached 15-colour sprites, multiplexing, AGA enhancements, priority control |
|
||||
| [sprites.md](sprites.md) | Hardware sprites: DMA engine, data format, attached 15-color sprites, multiplexing, AGA enhancements, priority control |
|
||||
| [rastport.md](rastport.md) | RastPort drawing context: draw modes, patterns, layer clipping, text pipeline, blitter minterms |
|
||||
| [views.md](views.md) | View, ViewPort, MakeVPort, display construction |
|
||||
| [text_fonts.md](text_fonts.md) | TextFont bitmap layout, baseline rendering, algorithmic styles, AvailFonts enumeration |
|
||||
|
|
|
|||
|
|
@ -292,11 +292,11 @@ saveSize += sizeof(WORD) * bob->BobVSprite->Height * bob->BobVSprite->Depth;
|
|||
bob->SaveBuffer = AllocMem(saveSize, MEMF_CHIP | MEMF_CLEAR);
|
||||
```
|
||||
|
||||
## GEL System Initialisation and Render Loop
|
||||
## GEL System Initialization and Render Loop
|
||||
|
||||
The GEL system requires explicit initialisation before use. The core lifecycle is: **init → add objects → sort → draw → sync → repeat → cleanup**.
|
||||
The GEL system requires explicit initialization before use. The core lifecycle is: **init → add objects → sort → draw → sync → repeat → cleanup**.
|
||||
|
||||
### Initialisation
|
||||
### Initialization
|
||||
|
||||
```c
|
||||
#include <graphics/gels.h>
|
||||
|
|
@ -771,7 +771,7 @@ myVS.PlaneOnOff = 0x00; /* planes 1-3 get 0 (transparent) */
|
|||
|
||||
| Function | Description |
|
||||
|---|---|
|
||||
| `InitGels(head, tail, gi)` | Initialise GEL list with sentinel VSprites |
|
||||
| `InitGels(head, tail, gi)` | Initialize GEL list with sentinel VSprites |
|
||||
| `AddVSprite(vs, rp)` | Add a VSprite to the GEL list |
|
||||
| `AddBob(bob, rp)` | Add a BOB (and its backing VSprite) |
|
||||
| `AddAnimOb(ao, head, rp)` | Add an AnimOb and all its components |
|
||||
|
|
|
|||
|
|
@ -1,17 +1,76 @@
|
|||
[← Home](../README.md) · [Graphics](README.md)
|
||||
|
||||
# BitMap — Planar Bitmap Structure and Layout
|
||||
# BitMap — Planar Layout, AllocBitMap, Interleaving, and the RastPort Relationship
|
||||
|
||||
## Overview
|
||||
|
||||
Amiga display memory uses **planar** layout: each bitplane is a separate contiguous memory region. A pixel's colour index is formed by reading one bit from each plane at the same x,y position. This is fundamentally different from chunky (packed-pixel) or interleaved formats.
|
||||
The Amiga's display system is built on **planar bitmaps**: rather than storing each pixel's color in a contiguous byte or word (chunky), the display hardware reads one bit from each of several independent memory regions called **bitplanes**. A pixel's color index is the concatenation of bits at the same (x, y) coordinate across all planes. This design was chosen in 1985 because it minimizes DMA bandwidth: a 16-color screen needs only 4 bits per pixel across the bus, and the Copper can manipulate individual bitplanes with simple pointer changes. The trade-off is software complexity — drawing a single pixel requires read-modify-write across multiple planes, and modern developers accustomed to RGB framebuffers must rethink their mental model. The `struct BitMap` is the central data structure that describes this layout, and `graphics.library` provides `AllocBitMap()` (OS 3.0+) to manage its allocation, alignment, and Chip RAM requirements automatically.
|
||||
|
||||
---
|
||||
|
||||
## struct BitMap
|
||||
## Architecture
|
||||
|
||||
### BitMap in the Display Pipeline
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "Software"
|
||||
APP["Application\nDraw into BitMap"]
|
||||
RP["RastPort\nDrawing context"]
|
||||
end
|
||||
|
||||
subgraph "graphics.library"
|
||||
AB["AllocBitMap()\nChip RAM allocation\nAlignment enforcement"]
|
||||
BL["BltBitMap()\nBlitter DMA copy"]
|
||||
end
|
||||
|
||||
subgraph "Hardware"
|
||||
BP["BitPlane DMA\n(Agnus/Alice)"]
|
||||
DEN["Denise/Lisa\nPixel compositor"]
|
||||
OUT["Video output"]
|
||||
end
|
||||
|
||||
APP --> RP
|
||||
RP --> AB
|
||||
AB --> BL
|
||||
BL --> BP
|
||||
BP --> DEN
|
||||
DEN --> OUT
|
||||
|
||||
style BP fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style DEN fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
```
|
||||
|
||||
### Standard vs Interleaved Layout
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Standard Planar"
|
||||
SP0["Plane 0\n10,240 bytes"]
|
||||
SP1["Plane 1\n10,240 bytes"]
|
||||
SP2["Plane 2\n10,240 bytes"]
|
||||
SP3["Plane 3\n10,240 bytes"]
|
||||
end
|
||||
|
||||
subgraph "Interleaved"
|
||||
I0["Row 0: P0 P1 P2 P3\n160 bytes"]
|
||||
I1["Row 1: P0 P1 P2 P3\n160 bytes"]
|
||||
I2["Row 2: P0 P1 P2 P3\n160 bytes"]
|
||||
end
|
||||
|
||||
style I0 fill:#e8f5e9,stroke:#4caf50,color:#333
|
||||
```
|
||||
|
||||
In **standard planar**, each plane is a contiguous block. In **interleaved**, planes are striped row-by-row within a single allocation. Interleaving improves cache locality and allows the Blitter to fetch all planes for a given row in one pass.
|
||||
|
||||
---
|
||||
|
||||
## Data Structures
|
||||
|
||||
### struct BitMap
|
||||
|
||||
```c
|
||||
/* graphics/gfx.h — NDK39 */
|
||||
/* graphics/gfx.h — NDK 3.9 */
|
||||
struct BitMap {
|
||||
UWORD BytesPerRow; /* bytes per row per plane (must be even) */
|
||||
UWORD Rows; /* height in pixels */
|
||||
|
|
@ -22,30 +81,40 @@ struct BitMap {
|
|||
};
|
||||
```
|
||||
|
||||
---
|
||||
### Field Reference
|
||||
|
||||
## BMF_ Flags
|
||||
| Field | Type | Description | Constraints |
|
||||
|---|---|---|---|
|
||||
| `BytesPerRow` | `UWORD` | Bytes per scanline **per plane** | Must be even; minimum 2; typically `width / 8` rounded up to even |
|
||||
| `Rows` | `UWORD` | Height in pixels | Maximum 1024 on OCS/ECS; 2048+ on AGA with large-modulo tricks |
|
||||
| `Flags` | `UBYTE` | `BMF_*` allocation flags | Set by `AllocBitMap`; do not modify directly |
|
||||
| `Depth` | `UBYTE` | Number of bitplanes | 1–8 (AGA); 1–6 (OCS/ECS practical limit) |
|
||||
| `Planes[]` | `PLANEPTR` | Pointers to each plane's base address | All must be in Chip RAM if displayable; `NULL` for unused planes |
|
||||
|
||||
### BMF_ Flags
|
||||
|
||||
```c
|
||||
#define BMF_CLEAR (1<<0) /* clear planes on allocation */
|
||||
#define BMF_DISPLAYABLE (1<<1) /* allocated in displayable (Chip) RAM */
|
||||
#define BMF_INTERLEAVED (1<<2) /* planes are interleaved in memory */
|
||||
#define BMF_STANDARD (1<<3) /* use standard allocation */
|
||||
#define BMF_MINPLANES (1<<4) /* minimum number of planes */
|
||||
#define BMF_CLEAR (1<<0) /* Zero-fill planes on allocation */
|
||||
#define BMF_DISPLAYABLE (1<<1) /* Allocate in Chip RAM (DMA-visible) */
|
||||
#define BMF_INTERLEAVED (1<<2) /* Row-interleaved plane layout */
|
||||
#define BMF_STANDARD (1<<3) /* Use standard (non-super) allocation */
|
||||
#define BMF_MINPLANES (1<<4) /* Allocate minimum planes; rest NULL */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Planar Memory Layout
|
||||
|
||||
For a 320×256×4 display (16 colours):
|
||||
### Standard Planar
|
||||
|
||||
For a 320×256×4 display (16 colors):
|
||||
|
||||
```
|
||||
BytesPerRow = 320/8 = 40 bytes
|
||||
Rows = 256
|
||||
Depth = 4
|
||||
|
||||
Plane 0: 40 × 256 = 10,240 bytes (bit 0 of colour index)
|
||||
Plane 0: 40 × 256 = 10,240 bytes (bit 0 of color index)
|
||||
Plane 1: 40 × 256 = 10,240 bytes (bit 1)
|
||||
Plane 2: 40 × 256 = 10,240 bytes (bit 2)
|
||||
Plane 3: 40 × 256 = 10,240 bytes (bit 3)
|
||||
|
|
@ -53,66 +122,477 @@ Plane 3: 40 × 256 = 10,240 bytes (bit 3)
|
|||
Total = 4 × 10,240 = 40,960 bytes
|
||||
```
|
||||
|
||||
Pixel colour at (x, y):
|
||||
```
|
||||
bit0 = (Planes[0][y * BytesPerRow + x/8] >> (7 - x%8)) & 1
|
||||
bit1 = (Planes[1][y * BytesPerRow + x/8] >> (7 - x%8)) & 1
|
||||
bit2 = (Planes[2][y * BytesPerRow + x/8] >> (7 - x%8)) & 1
|
||||
bit3 = (Planes[3][y * BytesPerRow + x/8] >> (7 - x%8)) & 1
|
||||
colour_index = (bit3 << 3) | (bit2 << 2) | (bit1 << 1) | bit0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Allocation
|
||||
|
||||
Pixel color at (x, y):
|
||||
```c
|
||||
/* OS 3.0+ — AllocBitMap: */
|
||||
struct BitMap *bm = AllocBitMap(320, 256, 4,
|
||||
BMF_CLEAR | BMF_DISPLAYABLE, NULL);
|
||||
/* Always in Chip RAM when BMF_DISPLAYABLE */
|
||||
|
||||
/* Manual allocation (OS 1.x compatible): */
|
||||
struct BitMap bm;
|
||||
InitBitMap(&bm, 4, 320, 256);
|
||||
for (int i = 0; i < 4; i++)
|
||||
bm.Planes[i] = AllocRaster(320, 256); /* MEMF_CHIP */
|
||||
|
||||
/* Free: */
|
||||
FreeBitMap(bm); /* or FreeRaster per plane */
|
||||
UBYTE byte =Planes[p][y * bm->BytesPerRow + x / 8];
|
||||
UBYTE bit = (byte >> (7 - (x & 7))) & 1;
|
||||
```
|
||||
|
||||
---
|
||||
> **Big-Endian note**: The 68000 stores the leftmost pixel of each byte in bit 7, not bit 0. `(7 - (x & 7))` extracts the correct bit. Modern little-endian developers often get this backwards.
|
||||
|
||||
## Interleaved BitMaps
|
||||
### Interleaved Planar
|
||||
|
||||
With `BMF_INTERLEAVED`, memory is organized as:
|
||||
|
||||
With `BMF_INTERLEAVED`, all planes are stored sequentially row by row:
|
||||
```
|
||||
Row 0, Plane 0: 40 bytes
|
||||
Row 0, Plane 1: 40 bytes
|
||||
Row 0, Plane 2: 40 bytes
|
||||
Row 0, Plane 3: 40 bytes
|
||||
Row 1, Plane 0: 40 bytes
|
||||
BytesPerRow = 40 * 4 = 160 /* covers all planes for one row */
|
||||
|
||||
Row 0: [Plane0 bytes][Plane1 bytes][Plane2 bytes][Plane3 bytes]
|
||||
Row 1: [Plane0 bytes][Plane1 bytes][Plane2 bytes][Plane3 bytes]
|
||||
...
|
||||
```
|
||||
|
||||
BytesPerRow becomes `40 × Depth = 160`, and each `Planes[i]` pointer is offset by `i * 40` from the base. This layout is more cache-friendly and allows single-pass blits.
|
||||
Pointer arithmetic:
|
||||
```c
|
||||
/* Planes[i] points to Plane i's data within the interleaved block */
|
||||
Planes[0] = base;
|
||||
Planes[1] = base + 40;
|
||||
Planes[2] = base + 80;
|
||||
Planes[3] = base + 120;
|
||||
|
||||
/* Accessing pixel (x, y) in plane p: */
|
||||
UBYTE *row = base + y * 160;
|
||||
UBYTE byte = row[p * 40 + x / 8];
|
||||
```
|
||||
|
||||
**Why interleave?**
|
||||
- **Cache efficiency**: All planes for a row are contiguous
|
||||
- **Blitter speed**: Single modulo value advances to next row; fewer setup registers
|
||||
- **ScrollRaster**: Hardware scroll works correctly with interleaved layout
|
||||
|
||||
**Trade-off**: Interleaved BitMaps are harder to manipulate with custom CPU rendering because plane pointers are not independent.
|
||||
|
||||
---
|
||||
|
||||
## AGA 8-Bit Bitmaps
|
||||
## API Reference
|
||||
|
||||
### Allocation
|
||||
|
||||
AGA (A1200/A4000) supports up to 8 bitplanes = 256 colours:
|
||||
```c
|
||||
struct BitMap *bm = AllocBitMap(320, 256, 8, BMF_CLEAR | BMF_DISPLAYABLE, NULL);
|
||||
/* 8 planes × 10,240 = 81,920 bytes of Chip RAM */
|
||||
/* OS 3.0+ — preferred method */
|
||||
struct BitMap *AllocBitMap(ULONG width, ULONG height, ULONG depth,
|
||||
ULONG flags, struct BitMap *friend);
|
||||
|
||||
/* Free */
|
||||
void FreeBitMap(struct BitMap *bm);
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|---|---|
|
||||
| `width` | Width in pixels |
|
||||
| `height` | Height in pixels |
|
||||
| `depth` | Number of bitplanes (1–8) |
|
||||
| `flags` | `BMF_*` flags |
|
||||
| `friend` | Optional "friend" BitMap for compatibility (usually `NULL` or a screen's BitMap) |
|
||||
|
||||
> [!WARNING]
|
||||
> **Requires Chip RAM**: When `BMF_DISPLAYABLE` is set, `AllocBitMap()` allocates from Chip RAM (`MEMF_CHIP`). The Blitter, Copper, bitplane DMA, and sprite DMA cannot access Fast RAM. Pointing display hardware at a Fast RAM BitMap produces silent corruption.
|
||||
|
||||
### Manual Allocation (OS 1.x Compatible)
|
||||
|
||||
```c
|
||||
struct BitMap bm;
|
||||
InitBitMap(&bm, 4, 320, 256);
|
||||
for (int i = 0; i < 4; i++)
|
||||
bm.Planes[i] = AllocRaster(320, 256); /* AllocMem(..., MEMF_CHIP) */
|
||||
|
||||
/* Free: */
|
||||
for (int i = 0; i < 4; i++)
|
||||
FreeRaster(bm.Planes[i], 320, 256);
|
||||
```
|
||||
|
||||
### RastPort Relationship
|
||||
|
||||
A `RastPort` is the drawing context; it contains a pointer to a `BitMap` plus pen, draw mode, and layer state:
|
||||
|
||||
```c
|
||||
/* graphics/rastport.h — NDK 3.9 */
|
||||
struct RastPort {
|
||||
struct Layer *Layer;
|
||||
struct BitMap *BitMap; /* Target bitmap for drawing */
|
||||
UWORD cp_x, cp_y; /* Current pen position */
|
||||
UBYTE DrawMode; /* JAM1, JAM2, COMPLEMENT, INVERSVID */
|
||||
UBYTE AreaPtrn; /* Areafill pattern pointer */
|
||||
UBYTE linpatcnt;
|
||||
UBYTE dummy;
|
||||
UWORD Flags;
|
||||
UWORD LinePtrn;
|
||||
SHORT cp_minx, cp_maxx;
|
||||
SHORT cp_miny, cp_maxy;
|
||||
UBYTE APen, BPen;
|
||||
UBYTE AlphaThreshold;
|
||||
/* ... additional fields ... */
|
||||
};
|
||||
```
|
||||
|
||||
```c
|
||||
/* Initialize a RastPort for a BitMap */
|
||||
struct RastPort rp;
|
||||
InitRastPort(&rp);
|
||||
rp.BitMap = myBitMap;
|
||||
|
||||
/* Now draw: */
|
||||
SetAPen(&rp, 3);
|
||||
Move(&rp, 10, 10);
|
||||
Draw(&rp, 100, 50); /* Line rendered into myBitMap */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decision Guide: Standard vs Interleaved
|
||||
|
||||
| Criterion | Standard Planar | Interleaved |
|
||||
|---|---|---|
|
||||
| **When to use** | Custom CPU rendering, per-plane effects, easy pointer math | Blitter-heavy code, scrolling, OS-friendly rendering |
|
||||
| **BytesPerRow** | `width / 8` (rounded up) | `width / 8 * depth` |
|
||||
| `AllocBitMap()` flag | None (default) | `BMF_INTERLEAVED` |
|
||||
| `ScrollRaster()` | Requires manual plane loop | Works with single call |
|
||||
| `BltBitMap()` | Multiple blits or per-plane loops | Single blit with modulo |
|
||||
| **CPU pixel access** | Simple: `Planes[p][offset]` | Complex: `base + row * bpr * depth + p * bpr` |
|
||||
| **Memory fragmentation** | `depth` separate allocations | One contiguous block |
|
||||
| **Display hardware** | Identical — DMA doesn't care | Identical — DMA doesn't care |
|
||||
|
||||
---
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Example 1: Allocate and Clear a Displayable BitMap
|
||||
|
||||
```c
|
||||
#include <graphics/gfx.h>
|
||||
#include <proto/graphics.h>
|
||||
|
||||
struct BitMap *CreateDisplayBitMap(ULONG width, ULONG height, ULONG depth)
|
||||
{
|
||||
struct BitMap *bm = AllocBitMap(width, height, depth,
|
||||
BMF_CLEAR | BMF_DISPLAYABLE,
|
||||
NULL);
|
||||
if (!bm)
|
||||
{
|
||||
/* Out of Chip RAM — this is common on stock A500 */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Verify allocation succeeded for all requested planes */
|
||||
for (int i = 0; i < depth; i++)
|
||||
{
|
||||
if (!bm->Planes[i])
|
||||
{
|
||||
FreeBitMap(bm);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return bm;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: CPU Pixel Plot (Standard Planar)
|
||||
|
||||
```c
|
||||
void PutPixel(struct BitMap *bm, WORD x, WORD y, UBYTE color)
|
||||
{
|
||||
if (x < 0 || x >= bm->BytesPerRow * 8 || y < 0 || y >= bm->Rows)
|
||||
return;
|
||||
|
||||
UWORD byteOffset = y * bm->BytesPerRow + (x >> 3);
|
||||
UBYTE bitMask = 0x80 >> (x & 7); /* bit 7 = leftmost pixel */
|
||||
|
||||
for (int p = 0; p < bm->Depth; p++)
|
||||
{
|
||||
if (color & (1 << p))
|
||||
bm->Planes[p][byteOffset] |= bitMask; /* Set bit */
|
||||
else
|
||||
bm->Planes[p][byteOffset] &= ~bitMask; /* Clear bit */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: CPU Pixel Plot (Interleaved)
|
||||
|
||||
```c
|
||||
void PutPixelInterleaved(struct BitMap *bm, WORD x, WORD y, UBYTE color)
|
||||
{
|
||||
if (!(bm->Flags & BMF_INTERLEAVED)) return;
|
||||
|
||||
UWORD rowBytes = bm->BytesPerRow; /* total per row, all planes */
|
||||
UWORD planeBytes = rowBytes / bm->Depth; /* bytes per plane per row */
|
||||
UWORD byteOffset = y * rowBytes + (x >> 3);
|
||||
UBYTE bitMask = 0x80 >> (x & 7);
|
||||
|
||||
for (int p = 0; p < bm->Depth; p++)
|
||||
{
|
||||
UBYTE *planePtr = bm->Planes[0] + byteOffset + p * planeBytes;
|
||||
if (color & (1 << p))
|
||||
*planePtr |= bitMask;
|
||||
else
|
||||
*planePtr &= ~bitMask;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Blitter Copy Between BitMaps
|
||||
|
||||
```c
|
||||
/* Copy a rectangle from source to destination */
|
||||
void CopyRect(struct BitMap *src, WORD sx, WORD sy,
|
||||
struct BitMap *dst, WORD dx, WORD dy,
|
||||
WORD width, WORD height)
|
||||
{
|
||||
BltBitMap(src, sx, sy, dst, dx, dy, width, height,
|
||||
0xC0, /* minterm: D = C (straight copy) */
|
||||
0x01, /* mask: all planes */
|
||||
NULL); /* no temporary mask */
|
||||
}
|
||||
```
|
||||
|
||||
### Example 5: Attach BitMap to ViewPort
|
||||
|
||||
```c
|
||||
struct ViewPort vp;
|
||||
struct BitMap *bm = CreateDisplayBitMap(320, 256, 5);
|
||||
|
||||
InitVPort(&vp);
|
||||
vp.DWidth = 320;
|
||||
vp.DHeight = 256;
|
||||
vp.DxOffset = 0;
|
||||
vp.DyOffset = 0;
|
||||
vp.RasInfo = &ri;
|
||||
vp.Modes = HIRES | SPRITES;
|
||||
|
||||
ri.BitMap = bm;
|
||||
ri.RxOffset = 0;
|
||||
ri.RyOffset = 0;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Use / When NOT to Use
|
||||
|
||||
### When to Use AllocBitMap
|
||||
|
||||
| Scenario | Why It Fits |
|
||||
|---|---|
|
||||
| **OS 3.0+ application** | `AllocBitMap()` handles alignment, Chip RAM, and friend-BitMap compatibility |
|
||||
| **Off-screen buffers** | Allocate non-displayable BitMaps for pre-rendering, then blit to screen |
|
||||
| **Double buffering** | Two displayable BitMaps swapped per frame via `ChangeVPBitMap()` |
|
||||
| **Interleaved scrolling** | `BMF_INTERLEAVED` + `ScrollRaster()` is the correct path for smooth scroll |
|
||||
|
||||
### When NOT to Use AllocBitMap / Manual BitMaps
|
||||
|
||||
| Scenario | Problem | Better Approach |
|
||||
|---|---|---|
|
||||
| **Direct hardware banging** | `AllocBitMap()` may allocate structures you don't need | Direct `AllocMem(MEMF_CHIP)` and manual `Planes[]` setup |
|
||||
| **Copper-only displays** | If the CPU never draws, a raw bitplane array is sufficient | Manual `AllocRaster()` per plane |
|
||||
| **Chunky-to-planar rendering** | C2P output needs specific plane alignment; `AllocBitMap` may not match | Allocate manually with `MEMF_CHIP` and verify alignment |
|
||||
| **Custom DMA tricks** | Some demo effects need non-standard `BytesPerRow` or plane spacing | Manual allocation with exact sizes |
|
||||
|
||||
---
|
||||
|
||||
## Best Practices & Antipatterns
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always check `AllocBitMap()` return value** — Chip RAM exhaustion is common on stock machines.
|
||||
2. **Verify all `Planes[]` are non-NULL** — `BMF_MINPLANES` can leave upper planes unset.
|
||||
3. **Use `TypeOfMem(bm->Planes[0])` to confirm Chip RAM** when debugging DMA issues.
|
||||
4. **Round width up to 16-pixel boundaries** for Blitter efficiency: `width = ((width + 15) / 16) * 16`.
|
||||
5. **Prefer interleaved for scrolling games** — `ScrollRaster()` and the Blitter work optimally.
|
||||
6. **Use standard planar for per-plane effects** — color cycling, parallax, and palette tricks are easier.
|
||||
7. **Free with `FreeBitMap()`** if allocated with `AllocBitMap()` — do not mix manual and automatic free.
|
||||
8. **Set `friend` BitMap when possible** — improves compatibility with graphics cards and RTG systems.
|
||||
|
||||
### Antipatterns
|
||||
|
||||
#### 1. The Odd-Width Trap
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — BytesPerRow not even */
|
||||
struct BitMap bm;
|
||||
InitBitMap(&bm, 4, 321, 200); /* 321 pixels = 41 bytes (odd!) */
|
||||
/* Blitter requires even alignment; DMA may corrupt adjacent memory */
|
||||
|
||||
/* CORRECT — round up to even bytes */
|
||||
InitBitMap(&bm, 4, 320, 200); /* 40 bytes — even, safe */
|
||||
```
|
||||
|
||||
#### 2. The Fast RAM BitMap
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — allocating display BitMap in Fast RAM */
|
||||
struct BitMap bm;
|
||||
InitBitMap(&bm, 4, 320, 256);
|
||||
for (int i = 0; i < 4; i++)
|
||||
bm.Planes[i] = AllocMem(10240, MEMF_ANY); /* May return Fast RAM! */
|
||||
|
||||
/* CORRECT — force Chip RAM for displayable bitmaps */
|
||||
for (int i = 0; i < 4; i++)
|
||||
bm.Planes[i] = AllocMem(10240, MEMF_CHIP | MEMF_CLEAR);
|
||||
```
|
||||
|
||||
#### 3. The Uninitialized RastPort
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — using a RastPort without initialization */
|
||||
struct RastPort rp;
|
||||
rp.BitMap = myBitMap;
|
||||
Move(&rp, 0, 0); /* rp contains garbage pen, mode, layer ptr → crash */
|
||||
|
||||
/* CORRECT — always InitRastPort() */
|
||||
struct RastPort rp;
|
||||
InitRastPort(&rp);
|
||||
rp.BitMap = myBitMap;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls & Common Mistakes
|
||||
|
||||
### 1. Modulo Misalignment in Blitter Operations
|
||||
|
||||
The Blitter uses **word-aligned** addressing. If `BytesPerRow` is odd, or if you compute offsets incorrectly for interleaved BitMaps, the Blitter wraps to the wrong memory location.
|
||||
|
||||
```c
|
||||
/* PITFALL — wrong modulo for interleaved BitMap */
|
||||
struct BitMap *bm = AllocBitMap(320, 256, 4, BMF_INTERLEAVED, NULL);
|
||||
/* BytesPerRow = 40 * 4 = 160 */
|
||||
|
||||
/* If you tell the Blitter modulo = 40 (per-plane), it advances
|
||||
by 40 bytes per row — landing in the middle of the next plane. */
|
||||
|
||||
/* CORRECT — modulo for interleaved is total row bytes */
|
||||
UWORD modulo = bm->BytesPerRow; /* 160, not 40 */
|
||||
```
|
||||
|
||||
### 2. Depth Mismatch in BltBitMap
|
||||
|
||||
```c
|
||||
/* PITFALL — copying from 5-plane to 3-plane BitMap */
|
||||
BltBitMap(src, 0, 0, dst, 0, 0, 320, 200, 0xC0, 0x1F, NULL);
|
||||
/* If dst->Depth < 5, BltBitMap writes past Plane[] array → crash */
|
||||
|
||||
/* CORRECT — ensure destination depth >= source depth, or mask
|
||||
to only the planes that exist: */
|
||||
UBYTE planeMask = (1 << dst->Depth) - 1;
|
||||
BltBitMap(src, 0, 0, dst, 0, 0, 320, 200, 0xC0, planeMask, NULL);
|
||||
```
|
||||
|
||||
### 3. Forgetting PlaneClear on Manual Allocations
|
||||
|
||||
```c
|
||||
/* PITFALL — uninitialized planes contain garbage */
|
||||
struct BitMap bm;
|
||||
InitBitMap(&bm, 4, 320, 256);
|
||||
for (int i = 0; i < 4; i++)
|
||||
bm.Planes[i] = AllocRaster(320, 256);
|
||||
/* Planes contain random data → visual garbage on first display */
|
||||
|
||||
/* CORRECT — clear after allocation */
|
||||
for (int i = 0; i < 4; i++)
|
||||
memset(bm.Planes[i], 0, bm.BytesPerRow * bm.Rows);
|
||||
```
|
||||
|
||||
### 4. Width vs BytesPerRow Confusion
|
||||
|
||||
```c
|
||||
/* PITFALL — using width in bytes where pixels are expected */
|
||||
UWORD widthInBytes = bm->BytesPerRow; /* 40 bytes = 320 pixels */
|
||||
BltBitMap(src, 0, 0, dst, 0, 0, widthInBytes, 200, ...);
|
||||
/* BltBitMap expects PIXELS, not bytes! Copies only 40 pixels. */
|
||||
|
||||
/* CORRECT */
|
||||
BltBitMap(src, 0, 0, dst, 0, 0, 320, 200, ...);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
| Software Pattern | BitMap Approach | Why |
|
||||
|---|---|---|
|
||||
| **Scrolling platformer** | Interleaved, `ScrollRaster()` | Single hardware scroll register update per frame |
|
||||
| **Double-buffered animation** | Two standard BitMaps + `ChangeVPBitMap()` | Clean VBlank swap with no tearing |
|
||||
| **Parallax background** | Multiple standard BitMaps at different depths | Independent scroll per layer via separate ViewPorts |
|
||||
| **Off-screen sprite preshift** | Standard planar, 16 copies per frame | CPU pre-renders shifted frames for fast Blitter copy |
|
||||
| **Chunky-rendered 3D** | Standard planar + C2P conversion | Render in Fast RAM chunky buffer, convert to BitMap planes |
|
||||
| **Color-cycling water/sky** | Standard planar, modify one plane only | Animate palette index via plane manipulation |
|
||||
|
||||
---
|
||||
|
||||
## Historical Context & Modern Analogies
|
||||
|
||||
### Why Planar?
|
||||
|
||||
In 1985, DRAM bandwidth was the bottleneck. A 320×200 display at 60 Hz requires:
|
||||
|
||||
| Format | Bits/Pixel | Bytes/Frame | DMA Bandwidth |
|
||||
|---|---|---|---|
|
||||
| **Chunky 256-color** | 8 | 64,000 | ~3.8 MB/s |
|
||||
| **Planar 32-color (5 planes)** | 5 | 40,000 | ~2.4 MB/s |
|
||||
| **Planar 16-color (4 planes)** | 4 | 32,000 | ~1.9 MB/s |
|
||||
| **Planar 8-color (3 planes)** | 3 | 24,000 | ~1.4 MB/s |
|
||||
|
||||
Planar layout reduces DMA bandwidth proportionally to color depth. It also enables tricks impossible in chunky:
|
||||
- **Color cycling**: Change palette entries, not pixels
|
||||
- **Parallax**: Scroll one plane while others stay fixed
|
||||
- **Transparency**: Omit a plane to see through to background
|
||||
|
||||
### Modern Analogies
|
||||
|
||||
| Amiga Planar Concept | Modern Equivalent | Shared Concept |
|
||||
|---|---|---|
|
||||
| `BitMap` + `Planes[]` | **OpenGL texture array / Vulkan image layers** | Multiple memory planes composited into final pixel |
|
||||
| Interleaved planar | **GPU tile-based render target** | Contiguous per-row layout for cache efficiency |
|
||||
| `BytesPerRow` | **Vulkan ` VkSubresourceLayout.rowPitch`** | Stride between scanlines, often larger than width |
|
||||
| `AllocBitMap(BMF_DISPLAYABLE)` | **`vkAllocateMemory` with `DEVICE_LOCAL_BIT`** | Explicit allocation in GPU-visible (DMA-able) memory |
|
||||
| `BltBitMap()` | **OpenGL `glBlitFramebuffer`** | Hardware-accelerated rectangular copy between surfaces |
|
||||
| Planar color cycling | **Palette-based texture animation** | Modify lookup table instead of texels for animated effects |
|
||||
|
||||
### Where Analogies Break Down
|
||||
|
||||
- **No alpha channel**: Planar pixels are color indices, not RGBA. Transparency requires hold-and-modify (HAM) or sprite overlay.
|
||||
- **No arbitrary pixel writes**: Drawing a single pixel requires RMW across all planes — no simple `framebuffer[y*w+x] = color`.
|
||||
- **Blitter as coprocessor**: Unlike modern GPUs, the Blitter is a fixed-function DMA engine with no shader programmability.
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Can I use `AllocBitMap()` on OS 1.3?**
|
||||
> No. `AllocBitMap()` was introduced in OS 2.0 (V36). On 1.3, use `InitBitMap()` + `AllocRaster()` / `AllocMem(MEMF_CHIP)`.
|
||||
|
||||
**Q: How do I detect if a BitMap is interleaved?**
|
||||
> Check `bm->Flags & BMF_INTERLEAVED`. If set, `bm->BytesPerRow` is the total bytes per row across all planes.
|
||||
|
||||
**Q: Why does my Blitter copy look garbled?**
|
||||
> Most likely causes: (1) `BytesPerRow` is odd, (2) source/destination BitMaps are not in Chip RAM, (3) modulo values are wrong for interleaved layout, (4) plane mask includes nonexistent planes.
|
||||
|
||||
**Q: Can I draw directly into a screen's BitMap?**
|
||||
> Yes, but only through a `RastPort` obtained from the window (`win->RastPort`) or by creating your own RastPort pointing to the screen's BitMap. Never write to `screen->RastPort.BitMap` directly without proper locking — it bypasses layers clipping.
|
||||
|
||||
**Q: What is the maximum BitMap size?**
|
||||
> Theoretical: 32767×32767 (16-bit Rows/BytesPerRow). Practical: Chip RAM limits. A 640×512×8 BitMap consumes 320 KB — large but feasible on a 2 MB Chip RAM AGA machine.
|
||||
|
||||
---
|
||||
|
||||
## Impact on FPGA/Emulation
|
||||
|
||||
For MiSTer and emulator developers, planar BitMap emulation has specific requirements:
|
||||
|
||||
- **Bitplane DMA must respect `BytesPerRow`**: The Agnus/Alice DMA controller fetches one row per bitplane per scanline using `BytesPerRow` as the stride. Emulators must implement this correctly, not assume contiguous layout.
|
||||
- **Interleaved is a software convention**: The hardware does not distinguish interleaved from standard — it only sees plane pointers. Interleaving is achieved by software setting `Planes[i]` to offsets within a single block.
|
||||
- **Alignment enforcement**: `AllocBitMap()` ensures even `BytesPerRow` and proper Chip RAM alignment. Emulators need not enforce this for manually constructed BitMaps, but should document that misaligned BitMaps produce undefined behavior on real hardware.
|
||||
- **AGA 64-bit fetches**: Alice can fetch 64 bits (8 bytes) per DMA cycle when `FMODE` is set. Emulators must support wider fetches for correct AGA high-resolution modes.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `graphics/gfx.h`
|
||||
- ADCD 2.1: `AllocBitMap`, `FreeBitMap`, `InitBitMap`
|
||||
- HRM: *Amiga Hardware Reference Manual* — bitplane DMA chapter
|
||||
- See also: [memory_types.md](../01_hardware/common/memory_types.md) — why bitmaps must be in Chip RAM (DMA accessibility)
|
||||
- NDK 3.9: `graphics/gfx.h`, `graphics/rastport.h`
|
||||
- ADCD 2.1: `AllocBitMap()`, `FreeBitMap()`, `InitBitMap()`, `InitRastPort()`, `BltBitMap()`
|
||||
- *Amiga Hardware Reference Manual* — Bitplane DMA chapter
|
||||
- See also: [memory_types.md](../01_hardware/common/memory_types.md) — Chip RAM requirements for DMA-visible BitMaps
|
||||
- See also: [blitter.md](blitter.md) — Blitter DMA operations on BitMaps
|
||||
- See also: [blitter_programming.md](blitter_programming.md) — Advanced Blitter minterms and cookie-cut
|
||||
- See also: [views.md](views.md) — Attaching BitMaps to ViewPorts for display
|
||||
- See also: [rastport.md](rastport.md) — RastPort drawing context and primitives
|
||||
|
|
|
|||
|
|
@ -568,7 +568,7 @@ BltBitMap(srcBM, 0, 0, /* source bitmap, x, y */
|
|||
NULL); /* no temp buffer needed */
|
||||
|
||||
/* Draw a filled rectangle (uses the Blitter internally): */
|
||||
SetAPen(rp, 3); /* Set pen colour to index 3 */
|
||||
SetAPen(rp, 3); /* Set pen color to index 3 */
|
||||
RectFill(rp, 10, 10, 100, 50); /* Filled rectangle */
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
The **Copper** is a simple coprocessor in the Amiga custom chips that executes a list of instructions synchronised to the video beam. It can write to any custom chip register at any beam position, enabling per-scanline colour changes, split screens, and hardware-level display effects without CPU intervention.
|
||||
The **Copper** is a simple coprocessor in the Amiga custom chips that executes a list of instructions synchronized to the video beam. It can write to any custom chip register at any beam position, enabling per-scanline color changes, split screens, and hardware-level display effects without CPU intervention.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ If the beam has already passed the specified position, skip the next instruction
|
|||
|
||||
## Standard Copper Patterns
|
||||
|
||||
### Per-Scanline Colour Change (Rainbow)
|
||||
### Per-Scanline Color Change (Rainbow)
|
||||
|
||||
```
|
||||
WAIT $2C01,$FFFE ; wait for line $2C (44)
|
||||
|
|
@ -75,7 +75,7 @@ The OS manages copper lists through `GfxBase`:
|
|||
|
||||
| Pointer | Description |
|
||||
|---|---|
|
||||
| `GfxBase->copinit` | System initialisation copper list |
|
||||
| `GfxBase->copinit` | System initialization copper list |
|
||||
| `GfxBase->LOFlist` | Long-frame copper list (even fields) |
|
||||
| `GfxBase->SHFlist` | Short-frame copper list (odd fields, interlace) |
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ graph LR
|
|||
Beam["Beam Counter"]
|
||||
end
|
||||
subgraph Denise/Lisa ["Denise / Lisa Chip"]
|
||||
Palette["Colour Registers"]
|
||||
Palette["Color Registers"]
|
||||
BPL["Bitplane Control"]
|
||||
SPR["Sprite Control"]
|
||||
end
|
||||
|
|
@ -37,14 +37,14 @@ graph LR
|
|||
**Key points:**
|
||||
- The Copper reads its program from **Chip RAM** via DMA — no CPU involvement
|
||||
- It writes directly to custom chip registers (the same `$DFF000–$DFF1FE` space)
|
||||
- It synchronises with the **beam counter** — it knows exactly where the electron beam is
|
||||
- It synchronizes with the **beam counter** — it knows exactly where the electron beam is
|
||||
- The CPU can modify the copper list in memory at any time; changes take effect next frame
|
||||
|
||||
### What the Copper Can Do
|
||||
|
||||
| Capability | How | Typical Use |
|
||||
|---|---|---|
|
||||
| **Per-line colour changes** | WAIT for line → MOVE to COLORxx | Gradient skies, rainbow bars, water effects |
|
||||
| **Per-line color changes** | WAIT for line → MOVE to COLORxx | Gradient skies, rainbow bars, water effects |
|
||||
| **Split-screen displays** | Change bitplane pointers mid-frame | Status bar + scrolling game area |
|
||||
| **Parallax scrolling** | Change BPLCON1 scroll offset at different lines | Multi-layer side-scrollers |
|
||||
| **Resolution mixing** | Change BPLCON0 mid-frame | HiRes title bar + LoRes gameplay |
|
||||
|
|
@ -61,7 +61,7 @@ graph LR
|
|||
| No branching/loops | Executes linearly top-to-bottom; no jumps or calls |
|
||||
| No memory read | Can only WRITE to registers — cannot read anything |
|
||||
| No CPU memory access | Writes only to custom chip registers (`$DFF000`+), not RAM or CIA |
|
||||
| No sub-pixel timing | Horizontal resolution: 4 colour clocks (~8 low-res pixels) |
|
||||
| No sub-pixel timing | Horizontal resolution: 4 color clocks (~8 low-res pixels) |
|
||||
| V counter wraps at 255 | PAL lines 256–311 require a double-WAIT trick |
|
||||
| Chip RAM only | The copper list itself must reside in Chip RAM (DMA-accessible) |
|
||||
|
||||
|
|
@ -69,9 +69,9 @@ graph LR
|
|||
|
||||
**AmigaOS** — `graphics.library` builds the system copper list automatically when you call `MakeVPort()` / `LoadView()`. This list sets up bitplane pointers, sprite pointers, display window, and palette for every ViewPort. User code adds instructions via `UCopList`.
|
||||
|
||||
**Games (system takeover)** — Disable the OS display system, point COP1LC to your own copper list, and have total control. The copper list typically sets up the display, changes colours per line, and handles sprite multiplexing.
|
||||
**Games (system takeover)** — Disable the OS display system, point COP1LC to your own copper list, and have total control. The copper list typically sets up the display, changes colors per line, and handles sprite multiplexing.
|
||||
|
||||
**Demos** — Push the Copper to its limits: hundreds of colour changes per frame, dynamic copper list generation, and tricks like "copper bars" (changing colours mid-scanline using horizontal WAITs).
|
||||
**Demos** — Push the Copper to its limits: hundreds of color changes per frame, dynamic copper list generation, and tricks like "copper bars" (changing colors mid-scanline using horizontal WAITs).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -150,15 +150,15 @@ Full PAL: 312 lines, but copper V wraps at 256
|
|||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: Rainbow Bars (Colour Per Scanline)
|
||||
### Example 1: Rainbow Bars (Color Per Scanline)
|
||||
|
||||
```asm
|
||||
; copperlist.s — 256-colour rainbow using Copper
|
||||
; copperlist.s — 256-color rainbow using Copper
|
||||
SECTION copperlist,DATA_C ; MUST be in Chip RAM!
|
||||
|
||||
CopperList:
|
||||
; Set up a basic display first
|
||||
dc.w $0100, $1200 ; BPLCON0: 1 bitplane, colour on
|
||||
dc.w $0100, $1200 ; BPLCON0: 1 bitplane, color on
|
||||
dc.w $0092, $0038 ; DDFSTRT
|
||||
dc.w $0094, $00D0 ; DDFSTOP
|
||||
dc.w $008E, $2C81 ; DIWSTRT
|
||||
|
|
@ -177,7 +177,7 @@ CopperList:
|
|||
dc.w $2F01, $FFFE ; WAIT line 47
|
||||
dc.w $0180, $0C30 ; COLOR00 = yellow-orange
|
||||
|
||||
; ... repeat for each line with incrementing colours ...
|
||||
; ... repeat for each line with incrementing colors ...
|
||||
|
||||
dc.w $FFFF, $FFFE ; end of copper list
|
||||
|
||||
|
|
@ -275,8 +275,8 @@ FreeCopList(ucl);
|
|||
|
||||
| Item | Cycles |
|
||||
|---|---|
|
||||
| Each Copper instruction | 4 colour clocks (= 8 low-res pixels) |
|
||||
| WAIT resolution (horizontal) | 4 colour clocks minimum |
|
||||
| Each Copper instruction | 4 color clocks (= 8 low-res pixels) |
|
||||
| WAIT resolution (horizontal) | 4 color clocks minimum |
|
||||
| Maximum instructions per line | ~112 (NTSC) / ~114 (PAL) |
|
||||
| PAL visible lines | 256 (lines 44–300) |
|
||||
| NTSC visible lines | 200 (lines 44–244) |
|
||||
|
|
@ -304,7 +304,7 @@ Reposition sprites mid-frame to display more than 8 sprites:
|
|||
### Copper-Driven Palette Animation
|
||||
|
||||
```asm
|
||||
; Animate copper list by modifying colour values each frame
|
||||
; Animate copper list by modifying color values each frame
|
||||
; (DMA reads new values each frame automatically)
|
||||
; Just update the data words in the copper list in Chip RAM
|
||||
move.w d0, CopperList+6 ; modify the MOVE data word
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
The Amiga's display system evolved through three generations of custom chips: **OCS** (Original Chip Set, A1000/A500/A2000), **ECS** (Enhanced, A3000/A600), and **AGA** (Advanced Graphics Architecture, A1200/A4000). Each generation expanded resolution, colour depth, and display flexibility while maintaining backward compatibility.
|
||||
The Amiga's display system evolved through three generations of custom chips: **OCS** (Original Chip Set, A1000/A500/A2000), **ECS** (Enhanced, A3000/A600), and **AGA** (Advanced Graphics Architecture, A1200/A4000). Each generation expanded resolution, color depth, and display flexibility while maintaining backward compatibility.
|
||||
|
||||
OS 3.0+ provides a **display database** that abstracts these capabilities. Applications query available modes by `ModeID` rather than hardcoding chipset-specific flags.
|
||||
|
||||
|
|
@ -15,15 +15,15 @@ OS 3.0+ provides a **display database** that abstracts these capabilities. Appli
|
|||
| Feature | OCS (Agnus/Denise) | ECS (Fat Agnus/Super Denise) | AGA (Alice/Lisa) |
|
||||
|---|---|---|---|
|
||||
| **Max Chip RAM** | 512 KB (8372) / 1 MB (8372A) | 2 MB (8375) | 2 MB (8374) |
|
||||
| **Bitplanes** | 6 (32 colours, lowres) | 6 | 8 (256 colours) |
|
||||
| **Bitplanes** | 6 (32 colors, lowres) | 6 | 8 (256 colors) |
|
||||
| **Palette entries** | 32 (4096 total, 12-bit RGB) | 32 (4096) | 256 (16.7M, 24-bit RGB) |
|
||||
| **Max lowres** | 320×256 (PAL) | 320×256 | 320×256 |
|
||||
| **Max hires** | 640×256 | 640×256 | 640×256 |
|
||||
| **Super hires** | — | 1280×256 | 1280×256 |
|
||||
| **Scan-doubled** | — | — | 640×512 non-interlaced |
|
||||
| **HAM** | HAM6 (4096 colours) | HAM6 | HAM8 (262,144 colours) |
|
||||
| **EHB** | EHB (64 colours) | EHB | EHB (superseded by 8 planes) |
|
||||
| **Sprites** | 8 × 16px × 3 colours | 8 × 16px × 3 colours | 8 × 16/32/64px × 3/15 colours |
|
||||
| **HAM** | HAM6 (4096 colors) | HAM6 | HAM8 (262,144 colors) |
|
||||
| **EHB** | EHB (64 colors) | EHB | EHB (superseded by 8 planes) |
|
||||
| **Sprites** | 8 × 16px × 3 colors | 8 × 16px × 3 colors | 8 × 16/32/64px × 3/15 colors |
|
||||
| **Fetch modes** | 1× | 1× | 1×, 2×, 4× (wider data bus) |
|
||||
| **Bandwidth** | 3.58 MHz pixel clock | 3.58/7.16/14.32 MHz | Up to 28.64 MHz (4× fetch) |
|
||||
|
||||
|
|
@ -40,8 +40,8 @@ Line frequency: 15,625 Hz
|
|||
Frame frequency: 50 Hz (25 Hz interlaced)
|
||||
Lines per frame: 312.5 (625 interlaced)
|
||||
Active lines: ~256 (non-interlaced) / ~512 (interlaced)
|
||||
Colour clock: 3,546,895 Hz
|
||||
Pixel clock (lores): 7,093,790 Hz (1 pixel = 2 colour clocks)
|
||||
Color clock: 3,546,895 Hz
|
||||
Pixel clock (lores): 7,093,790 Hz (1 pixel = 2 color clocks)
|
||||
Pixel clock (hires): 14,187,580 Hz
|
||||
```
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ Line frequency: 15,734 Hz
|
|||
Frame frequency: 60 Hz (30 Hz interlaced)
|
||||
Lines per frame: 262.5 (525 interlaced)
|
||||
Active lines: ~200 (non-interlaced) / ~400 (interlaced)
|
||||
Colour clock: 3,579,545 Hz
|
||||
Color clock: 3,579,545 Hz
|
||||
Pixel clock (lores): 7,159,090 Hz
|
||||
Pixel clock (hires): 14,318,180 Hz
|
||||
```
|
||||
|
|
@ -161,7 +161,7 @@ while ((modeID = NextDisplayInfo(modeID)) != INVALID_ID)
|
|||
GetDisplayInfoData(NULL, (UBYTE *)&mon, sizeof(mon),
|
||||
DTAG_MNTR, modeID);
|
||||
|
||||
Printf("$%08lx: %ldx%ld, %ld colours, %s\n",
|
||||
Printf("$%08lx: %ldx%ld, %ld colors, %s\n",
|
||||
modeID,
|
||||
dims.Nominal.MaxX - dims.Nominal.MinX + 1,
|
||||
dims.Nominal.MaxY - dims.Nominal.MinY + 1,
|
||||
|
|
@ -203,7 +203,7 @@ The display system shares DMA bandwidth with other custom chips. Each scanline h
|
|||
| Blitter | Variable (steals from CPU) |
|
||||
| CPU | Whatever is left |
|
||||
|
||||
> In high-resolution 4-plane mode, bitplane DMA alone consumes 80 words per line — nearly the entire available bandwidth. This is why OCS/ECS hires is limited to 4 planes (16 colours) and AGA needed wider fetch modes.
|
||||
> In high-resolution 4-plane mode, bitplane DMA alone consumes 80 words per line — nearly the entire available bandwidth. This is why OCS/ECS hires is limited to 4 planes (16 colors) and AGA needed wider fetch modes.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ flowchart TD
|
|||
struct GfxBase {
|
||||
struct Library LibNode;
|
||||
struct View *ActiView; /* currently active View */
|
||||
struct copinit *copinit; /* system copper list initialisation */
|
||||
struct copinit *copinit; /* system copper list initialization */
|
||||
LONG *cia; /* CIA base (deprecated) */
|
||||
LONG *blitter; /* blitter base (deprecated) */
|
||||
UWORD *LOFlist; /* long-frame copper list pointer */
|
||||
|
|
@ -101,7 +101,7 @@ struct GfxBase *GfxBase = (struct GfxBase *)
|
|||
if (GfxBase->ChipRevBits0 & GFXF_AA_ALICE)
|
||||
{
|
||||
/* AGA chipset (A1200/A4000) */
|
||||
/* 8-bit planar, 256 colours, 24-bit palette */
|
||||
/* 8-bit planar, 256 colors, 24-bit palette */
|
||||
}
|
||||
else if (GfxBase->ChipRevBits0 & GFXF_HR_DENISE)
|
||||
{
|
||||
|
|
@ -116,7 +116,7 @@ else if (GfxBase->ChipRevBits0 & GFXF_HR_AGNUS)
|
|||
else
|
||||
{
|
||||
/* OCS chipset (original A500/A1000/A2000) */
|
||||
/* 512 KB Chip RAM, 4096 colour palette */
|
||||
/* 512 KB Chip RAM, 4096 color palette */
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
The Amiga offers two unique display modes that squeeze many more colours from limited bitplane hardware: **EHB** (Extra Half-Brite) and **HAM** (Hold-And-Modify). These modes have no direct equivalent on other platforms and are critical for understanding Amiga graphics capability and for FPGA implementation.
|
||||
The Amiga offers two unique display modes that squeeze many more colors from limited bitplane hardware: **EHB** (Extra Half-Brite) and **HAM** (Hold-And-Modify). These modes have no direct equivalent on other platforms and are critical for understanding Amiga graphics capability and for FPGA implementation.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -13,8 +13,8 @@ The Amiga offers two unique display modes that squeeze many more colours from li
|
|||
### How It Works
|
||||
|
||||
Uses **6 bitplanes** (64 possible values):
|
||||
- Bitplane values 0–31: index into the 32-colour palette normally
|
||||
- Bitplane values 32–63: display the colour from register (value − 32) at **half brightness** (all RGB components shifted right by 1)
|
||||
- Bitplane values 0–31: index into the 32-color palette normally
|
||||
- Bitplane values 32–63: display the color from register (value − 32) at **half brightness** (all RGB components shifted right by 1)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
|
|
@ -32,12 +32,12 @@ flowchart LR
|
|||
Example pixel value = 37 (binary: 100101):
|
||||
Bit 5 = 1 → half-brite
|
||||
Bits 4-0 = 00101 = palette index 5
|
||||
Output colour = palette[5] >> 1 (each R,G,B component halved)
|
||||
Output color = palette[5] >> 1 (each R,G,B component halved)
|
||||
|
||||
Example pixel value = 5 (binary: 000101):
|
||||
Bit 5 = 0 → normal
|
||||
Bits 4-0 = 00101 = palette index 5
|
||||
Output colour = palette[5] (full brightness)
|
||||
Output color = palette[5] (full brightness)
|
||||
```
|
||||
|
||||
### Programming EHB
|
||||
|
|
@ -51,16 +51,16 @@ struct Screen *scr = OpenScreenTags(NULL,
|
|||
SA_DisplayID, EXTRAHALFBRITE_KEY,
|
||||
TAG_DONE);
|
||||
|
||||
/* Set the 32 base colours: */
|
||||
ULONG colours32[32 * 3 + 2];
|
||||
colours32[0] = 32 << 16; /* count = 32, first = 0 */
|
||||
/* Set the 32 base colors: */
|
||||
ULONG colors32[32 * 3 + 2];
|
||||
colors32[0] = 32 << 16; /* count = 32, first = 0 */
|
||||
/* ... fill RGB values ... */
|
||||
colours32[32 * 3 + 1] = 0; /* terminator */
|
||||
LoadRGB32(&scr->ViewPort, colours32);
|
||||
colors32[32 * 3 + 1] = 0; /* terminator */
|
||||
LoadRGB32(&scr->ViewPort, colors32);
|
||||
|
||||
/* Pixels 0–31 use base palette directly.
|
||||
Pixels 32–63 are automatically half-brightness versions.
|
||||
No need to set colours 32–63 — hardware does it. */
|
||||
No need to set colors 32–63 — hardware does it. */
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -111,25 +111,25 @@ flowchart LR
|
|||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Each scanline starts fresh** — the first pixel of each line has no "previous pixel" to modify. The hardware resets to the background colour (register 0) at the start of each line. This is why HAM images often have a visible "colour ramp" at the left edge.
|
||||
> **Each scanline starts fresh** — the first pixel of each line has no "previous pixel" to modify. The hardware resets to the background color (register 0) at the start of each line. This is why HAM images often have a visible "color ramp" at the left edge.
|
||||
|
||||
### Practical Example — Encoding a HAM6 Scanline
|
||||
|
||||
Suppose we want to display these colours on a scanline:
|
||||
Suppose we want to display these colors on a scanline:
|
||||
|
||||
```
|
||||
Target: RGB(A,7,3) → RGB(A,7,F) → RGB(F,7,F) → RGB(F,0,8)
|
||||
|
||||
Encoding:
|
||||
Pixel 0: 00 xxxx (SET palette[n] = A,7,3) → SET to base colour
|
||||
Pixel 0: 00 xxxx (SET palette[n] = A,7,3) → SET to base color
|
||||
Pixel 1: 01 1111 (MOD BLUE = F) → A,7,3 → A,7,F ✓
|
||||
Pixel 2: 10 1111 (MOD RED = F) → A,7,F → F,7,F ✓
|
||||
Pixel 3: 00 xxxx (SET palette[m] = F,0,8) → SET to nearest base colour
|
||||
Pixel 3: 00 xxxx (SET palette[m] = F,0,8) → SET to nearest base color
|
||||
|
||||
Note: pixel 3 needs to change ALL THREE components.
|
||||
Since HAM can only modify ONE component per pixel, we must either:
|
||||
a) Use 3 pixels to transition (changing R, G, B separately) → "fringing"
|
||||
b) Pick a base palette colour that's close to the target → "SET"
|
||||
b) Pick a base palette color that's close to the target → "SET"
|
||||
```
|
||||
|
||||
### The Fringing Problem
|
||||
|
|
@ -150,24 +150,24 @@ flowchart LR
|
|||
style H4 fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
```
|
||||
|
||||
Pixels H3 and H4 are **fringing artifacts** — wrong colours visible during the transition. The encoder must change R, G, B individually (one per pixel), so sharp multi-component transitions always produce visible intermediate colours.
|
||||
Pixels H3 and H4 are **fringing artifacts** — wrong colors visible during the transition. The encoder must change R, G, B individually (one per pixel), so sharp multi-component transitions always produce visible intermediate colors.
|
||||
|
||||
The encoder (usually offline) optimises palette choice and pixel encoding to minimise fringing. Common strategies:
|
||||
- Choose 16 base palette colours via **median-cut** from the image histogram
|
||||
The encoder (usually offline) optimizes palette choice and pixel encoding to minimize fringing. Common strategies:
|
||||
- Choose 16 base palette colors via **median-cut** from the image histogram
|
||||
- Use SET pixels at strong edges
|
||||
- Sequence MODIFY commands to approach target in fewest steps
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
IMG["Source Image<br/>(24-bit RGB)"] --> HIST["Histogram Analysis"]
|
||||
HIST --> MEDCUT["Median-Cut<br/>Select 16 base colours"]
|
||||
HIST --> MEDCUT["Median-Cut<br/>Select 16 base colors"]
|
||||
MEDCUT --> PAL["Optimal 16-entry palette"]
|
||||
|
||||
IMG --> SCAN["Process scanlines<br/>left to right"]
|
||||
PAL --> SCAN
|
||||
|
||||
SCAN --> DECIDE{"Distance to target?"}
|
||||
DECIDE -->|"Close base colour exists"| SET["SET command<br/>(no fringing)"]
|
||||
DECIDE -->|"Close base color exists"| SET["SET command<br/>(no fringing)"]
|
||||
DECIDE -->|"Only 1 component differs"| MOD["MODIFY command<br/>(no fringing)"]
|
||||
DECIDE -->|"2-3 components differ"| FRINGE["2-3 MODIFY sequence<br/>(fringing visible)"]
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ struct Screen *scr = OpenScreenTags(NULL,
|
|||
SA_DisplayID, HAM_KEY,
|
||||
TAG_DONE);
|
||||
|
||||
/* Set the 16 base palette colours: */
|
||||
/* Set the 16 base palette colors: */
|
||||
ULONG hamPalette[16 * 3 + 2];
|
||||
hamPalette[0] = 16 << 16; /* count=16, first=0 */
|
||||
/* Palette entry 0: R=$A0, G=$70, B=$30 (12-bit values scaled to 32-bit) */
|
||||
|
|
@ -221,7 +221,7 @@ void SetHAMPixel(UBYTE *plane[], int x, int y, UBYTE cmd, UBYTE data)
|
|||
}
|
||||
}
|
||||
|
||||
/* Example: SET colour 5, then modify blue to $F: */
|
||||
/* Example: SET color 5, then modify blue to $F: */
|
||||
SetHAMPixel(plane, 0, 0, 0x00, 5); /* 00 0101 = SET palette[5] */
|
||||
SetHAMPixel(plane, 1, 0, 0x01, 0xF); /* 01 1111 = MOD BLUE = $F */
|
||||
```
|
||||
|
|
@ -246,15 +246,15 @@ Uses **8 bitplanes**. Same principle, wider data:
|
|||
| Aspect | HAM6 | HAM8 |
|
||||
|---|---|---|
|
||||
| Base palette entries | 16 | 64 |
|
||||
| Colour component precision | 4-bit (16 levels) | 6-bit (64 levels) |
|
||||
| Total colour space | 12-bit (4,096) | 18-bit (262,144) |
|
||||
| Fringing severity | Severe | Mild (more base colours to SET from) |
|
||||
| Color component precision | 4-bit (16 levels) | 6-bit (64 levels) |
|
||||
| Total color space | 12-bit (4,096) | 18-bit (262,144) |
|
||||
| Fringing severity | Severe | Mild (more base colors to SET from) |
|
||||
| Memory per 320×256 screen | 6 × 40 × 256 = 60 KB | 8 × 40 × 256 = 80 KB |
|
||||
|
||||
### HAM8 Palette Setup
|
||||
|
||||
```c
|
||||
/* HAM8 uses 64 of the 256 AGA palette entries as base colours: */
|
||||
/* HAM8 uses 64 of the 256 AGA palette entries as base colors: */
|
||||
struct Screen *scr = OpenScreenTags(NULL,
|
||||
SA_Width, 320,
|
||||
SA_Height, 256,
|
||||
|
|
@ -319,10 +319,10 @@ DMA slots consumed per bitplane per lowres line:
|
|||
The HAM decoder operates **one pixel clock behind** the bitplane data output:
|
||||
|
||||
```
|
||||
Bitplane DMA → Bitplane shift registers → HAM decoder → Colour register → DAC → Video out
|
||||
Bitplane DMA → Bitplane shift registers → HAM decoder → Color register → DAC → Video out
|
||||
↑
|
||||
1-pixel delay
|
||||
(needs previous pixel's colour)
|
||||
(needs previous pixel's color)
|
||||
```
|
||||
|
||||
For FPGA implementation, the HAM decoder is a simple combinational circuit:
|
||||
|
|
@ -359,20 +359,20 @@ end
|
|||
|
||||
## Standard Palette Modes — For Comparison
|
||||
|
||||
### Setting Palette Colours (Non-HAM)
|
||||
### Setting Palette Colors (Non-HAM)
|
||||
|
||||
```c
|
||||
/* OS 3.0+ — 24-bit precision (AGA): */
|
||||
ULONG colours[3 * 3 + 2]; /* 3 colours */
|
||||
colours[0] = 3 << 16; /* count=3, first entry=0 */
|
||||
ULONG colors[3 * 3 + 2]; /* 3 colors */
|
||||
colors[0] = 3 << 16; /* count=3, first entry=0 */
|
||||
/* Entry 0: black */
|
||||
colours[1] = 0x00000000; colours[2] = 0x00000000; colours[3] = 0x00000000;
|
||||
colors[1] = 0x00000000; colors[2] = 0x00000000; colors[3] = 0x00000000;
|
||||
/* Entry 1: bright red */
|
||||
colours[4] = 0xFF000000; colours[5] = 0x00000000; colours[6] = 0x00000000;
|
||||
colors[4] = 0xFF000000; colors[5] = 0x00000000; colors[6] = 0x00000000;
|
||||
/* Entry 2: pure blue */
|
||||
colours[7] = 0x00000000; colours[8] = 0x00000000; colours[9] = 0xFF000000;
|
||||
colours[10] = 0; /* terminator */
|
||||
LoadRGB32(vp, colours);
|
||||
colors[7] = 0x00000000; colors[8] = 0x00000000; colors[9] = 0xFF000000;
|
||||
colors[10] = 0; /* terminator */
|
||||
LoadRGB32(vp, colors);
|
||||
|
||||
/* OCS/ECS — 12-bit precision: */
|
||||
UWORD oldPalette[] = { 0x000, 0xF00, 0x00F }; /* 4 bits per channel */
|
||||
|
|
@ -385,7 +385,7 @@ custom->color[2] = 0x00F; /* $DFF184: COLOR02 */
|
|||
/* AGA: extra bits via BPLCON3 bank select */
|
||||
```
|
||||
|
||||
### Colour Cycling (Palette Animation)
|
||||
### Color Cycling (Palette Animation)
|
||||
|
||||
```c
|
||||
/* Rotate palette entries for animation — common demo/game technique: */
|
||||
|
|
@ -413,7 +413,7 @@ void CyclePalette(struct ViewPort *vp, int first, int last)
|
|||
```
|
||||
|
||||
> [!TIP]
|
||||
> Colour cycling is extremely cheap — only palette registers change, not pixel data. A single `SetRGB32` call costs a few microseconds vs redrawing the entire screen. This is why palette animation was so popular on the Amiga.
|
||||
> Color cycling is extremely cheap — only palette registers change, not pixel data. A single `SetRGB32` call costs a few microseconds vs redrawing the entire screen. This is why palette animation was so popular on the Amiga.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -423,9 +423,9 @@ void CyclePalette(struct ViewPort *vp, int first, int last)
|
|||
|---|---|---|---|---|
|
||||
| Bitplanes | 5 | 6 | 6 | 8 |
|
||||
| Chipset | OCS/ECS/AGA | OCS/ECS/AGA | OCS/ECS/AGA | AGA only |
|
||||
| Programmable colours | 32 | 32 | 16 | 64 |
|
||||
| Programmable colors | 32 | 32 | 16 | 64 |
|
||||
| Total on-screen | 32 | 64 | 4,096 | 262,144 |
|
||||
| Colour depth | 12-bit (OCS) / 24-bit (AGA) | 12/24-bit | 12-bit | 18-bit |
|
||||
| Color depth | 12-bit (OCS) / 24-bit (AGA) | 12/24-bit | 12-bit | 18-bit |
|
||||
| Fringing | None | None | Significant | Mild |
|
||||
| Good for | GUI, games | GUI with shadows | Photos, static art | Photos, video stills |
|
||||
| Bad for | Photo-realism | Limited palette control | Animation, scrolling | Memory: 80 KB/frame |
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ A chunky buffer is the **natural intermediate format** for a GPU-style rendering
|
|||
|
||||
### Chunky (Packed Pixel)
|
||||
|
||||
Every pixel's complete colour index is stored contiguously. For 8-bit (256 colour) pixels:
|
||||
Every pixel's complete color index is stored contiguously. For 8-bit (256 color) pixels:
|
||||
|
||||
```
|
||||
Address: $0000 $0001 $0002 $0003 $0004 $0005 $0006 $0007
|
||||
|
|
@ -123,7 +123,7 @@ Each byte = one pixel. Linear, simple, cache-friendly for rendering. This is how
|
|||
|
||||
### Planar (Bitplane)
|
||||
|
||||
Each pixel's colour index is **split across N separate memory regions** (bitplanes). For 8-bit pixels (8 bitplanes), each bitplane stores one bit of every pixel:
|
||||
Each pixel's color index is **split across N separate memory regions** (bitplanes). For 8-bit pixels (8 bitplanes), each bitplane stores one bit of every pixel:
|
||||
|
||||
```
|
||||
Bitplane 0: 1 0 1 1 0 0 1 0 ← bit 0 of pixels 0–7
|
||||
|
|
@ -136,7 +136,7 @@ Bitplane 6: 0 0 1 0 0 0 0 1 ← bit 6
|
|||
Bitplane 7: 0 0 0 0 1 0 1 0 ← bit 7
|
||||
```
|
||||
|
||||
To read pixel 0's colour: collect bit 0 from each of the 8 planes → `10101100` = `$AC`. The 8 planes are **not interleaved** in standard Amiga layout — each is a separate contiguous memory block.
|
||||
To read pixel 0's color: collect bit 0 from each of the 8 planes → `10101100` = `$AC`. The 8 planes are **not interleaved** in standard Amiga layout — each is a separate contiguous memory block.
|
||||
|
||||
> [!WARNING]
|
||||
> The Amiga's planar format means memory addresses in bitplane memory don't correspond to pixel positions linearly. Plane 0 byte 0 contains bits for pixels 0–7. Plane 1 byte 0 contains bits for the same pixels 0–7. The byte offset for pixel N is `(N / 8)` in **every** plane. The bit position is `7 - (N mod 8)`. This is the fundamental indirection all planar-format API developers must internalize.
|
||||
|
|
@ -817,10 +817,15 @@ The CD32's Akiko chip implements C2P in dedicated silicon. The CPU feeds 8 longw
|
|||
| CPU load | 100% | 100% | ~50% (register I/O) | **2x CPU freed** |
|
||||
| 320x256x8bpl | ~1.1 s | ~35 ms | ~35 ms | **~31x** |
|
||||
|
||||
Akiko's throughput is approximately the same as optimised software C2P on the 68020 because both are limited by the Chip RAM bus bandwidth (~3.5 MB/s shared). On faster CPUs (68040/060), software C2P **outperforms** Akiko because the CPU can process data faster than the register interface can shuttle it.
|
||||
Akiko's throughput is approximately the same as optimized software C2P on the 68020 because both are limited by the Chip RAM bus bandwidth (~3.5 MB/s shared). On faster CPUs (68040/060), software C2P **outperforms** Akiko because the CPU can process data faster than the register interface can shuttle it.
|
||||
|
||||
Full Akiko protocol: [Akiko — CD32 C2P Hardware](../01_hardware/aga_a1200_a4000/akiko_cd32.md#chunky-to-planar-c2p-conversion)
|
||||
|
||||
> [!NOTE]
|
||||
> **FPGA Implementation**: On MiSTer, Akiko C2P must be implemented as a state machine triggered by register writes to `$B80030`. The CPU writes 8 longwords to the same address; the state machine reads them sequentially, performs bit transposition in hardware, and presents the 8 planar longwords on subsequent reads from `$B80030`. Throughput is bounded by Chip RAM bus bandwidth (~3.5 MB/s shared), not by the state machine speed — a naive FGPA Akiko implementation that runs at bus speed is already cycle-accurate.
|
||||
>
|
||||
> **Reference**: MiSTer Minimig-AGA Akiko implementation — [`rtl/akiko.v`](https://github.com/MiSTer-devel/Minimig-AGA_MiSTer/blob/MiSTer/rtl/akiko.v) (Verilog)
|
||||
|
||||
---
|
||||
|
||||
## Solution 4 — Blitter-Assisted C2P
|
||||
|
|
@ -912,6 +917,9 @@ Most games used a hybrid: 1-2 bitplanes for UI/HUD elements, reserving `COLOR00`
|
|||
> [!NOTE]
|
||||
> Copper Chunky and C2P are not mutually exclusive. Some demos use Copper Chunky for one screen region while simultaneously using C2P for another. The Copperlist can intermix WAIT/MOVE instructions with normal bitplane display controls.
|
||||
|
||||
> [!WARNING]
|
||||
> **FPGA/Emulation Timing Sensitivity**: Copper Chunky is extremely sensitive to Copper timing accuracy. Each `WAIT` must compare against the exact beam counter value, and each `MOVE` to `COLOR00` must take effect at the correct pixel position. DMA contention between Copper and bitplane fetches shifts pixel placement, and emulators must model the Copper's 2-cycle instruction latency (WAIT=2 cycles, MOVE=2 cycles). A one-pixel offset produces visible image shearing. The Minimig-AGA core on MiSTer implements this, but early UAE versions did not — if your Copper Chunky output shows "striped" patterns under emulation, test on MiSTer or real hardware before debugging the algorithm.
|
||||
|
||||
---
|
||||
|
||||
## Solution 5 — WriteChunkyPixels (AmigaOS)
|
||||
|
|
@ -1038,8 +1046,8 @@ The Amiga's planar format is **SoA**: each bitplane is an array of one field (on
|
|||
| **Amiga graphics** | Bitplanes (Agnus DMA) | Chunky pixel buffer (CPU render) | C2P algorithm |
|
||||
| **GPU compute shaders** | SoA buffer layouts (SSBO) | Vertex attributes (interleaved VBO) | Shader transpose |
|
||||
| **SIMD / AVX-512** | Separate float arrays (vectorisable) | Struct arrays (gather/scatter) | `_mm512_transpose` intrinsics |
|
||||
| **Database engines** | Columnar storage (Parquet, Arrow) | Row-oriented storage (MySQL) | Column↔row materialisation |
|
||||
| **Image compression** | Colour planes (JPEG YCbCr) | RGB pixels (BMP) | MCU block decomposition |
|
||||
| **Database engines** | Columnar storage (Parquet, Arrow) | Row-oriented storage (MySQL) | Column↔row materialization |
|
||||
| **Image compression** | Color planes (JPEG YCbCr) | RGB pixels (BMP) | MCU block decomposition |
|
||||
| **GPU texture memory** | Block-compressed (BC/ASTC) | Linear RGBA | Hardware texture unit decode |
|
||||
| **Neural network inference** | NCHW tensor layout (channels first) | NHWC (channels last) | Layout transposition kernel |
|
||||
|
||||
|
|
@ -1047,10 +1055,10 @@ The Amiga's planar format is **SoA**: each bitplane is an array of one field (on
|
|||
|
||||
| Layout | Optimal For | Reason |
|
||||
|---|---|---|
|
||||
| **SoA / Planar** | Streaming one field across many elements | Maximises cache line utilisation, enables SIMD vectorisation |
|
||||
| **SoA / Planar** | Streaming one field across many elements | Maximizes cache line utilization, enables SIMD vectorization |
|
||||
| **AoS / Chunky** | Random-access to complete elements | All fields of one element in one cache line |
|
||||
|
||||
The Amiga's custom DMA engine streams bitplane data to the display sequentially — plane 0 for the whole line, then plane 1, etc. This is a **SoA access pattern**, perfectly matched by the planar layout. The CPU, which wants to set a single pixel's complete colour, has the opposite need — it wants **AoS**.
|
||||
The Amiga's custom DMA engine streams bitplane data to the display sequentially — plane 0 for the whole line, then plane 1, etc. This is a **SoA access pattern**, perfectly matched by the planar layout. The CPU, which wants to set a single pixel's complete color, has the opposite need — it wants **AoS**.
|
||||
|
||||
### Modern Hardware Parallels
|
||||
|
||||
|
|
@ -1059,7 +1067,7 @@ The Amiga's custom DMA engine streams bitplane data to the display sequentially
|
|||
| **Akiko C2P register** | GPU texture swizzle unit | Hardware layout transposition |
|
||||
| **Blitter + merge algorithm** | CUDA shared memory transpose kernel | CPU/coprocessor-assisted transpose |
|
||||
| **RTG (planar bypass)** | Unified chunky framebuffer (since VGA) | Eliminates the problem entirely |
|
||||
| **Copper palette cycling** | GPU palette shader / LUT texture | Colour manipulation without pixel writes |
|
||||
| **Copper palette cycling** | GPU palette shader / LUT texture | Color manipulation without pixel writes |
|
||||
| **FMODE (fetch width)** | GPU memory bus width (256/384/512-bit) | Wider bus = more data per DMA cycle |
|
||||
|
||||
### GPU Texture Swizzle — The Modern Akiko
|
||||
|
|
@ -1086,7 +1094,7 @@ When you call `glTexImage2D()` or `vkCmdCopyBufferToImage()`, the GPU driver per
|
|||
| A1200 (1992, 14 MHz 68020) | C2P 320×256×8bpp | ~1.5 MB/s | CPU merge, 8 planes |
|
||||
| CD32 (1993, 14 MHz + Akiko) | C2P 320×256×8bpp | ~1.5 MB/s | Akiko hardware |
|
||||
| 486 DX2/66 (1992) | No conversion needed | N/A | VGA Mode 13h = chunky |
|
||||
| Pentium MMX (1997) | Colour space (YUV→RGB) | ~200 MB/s | MMX SIMD |
|
||||
| Pentium MMX (1997) | Color space (YUV→RGB) | ~200 MB/s | MMX SIMD |
|
||||
| GTX 1080 (2016) | Texture swizzle (linear→tiled) | ~300 GB/s | Hardware TMU |
|
||||
| Apple M2 (2022) | SoA↔AoS for ML tensors | ~100 GB/s | Hardware AMX |
|
||||
|
||||
|
|
@ -1100,9 +1108,9 @@ The throughput gap tells the story: what consumed 100% of a 68020's capability i
|
|||
|---|---|
|
||||
| 1985 | Amiga launches with planar display. C2P not needed — all software renders directly to bitplanes |
|
||||
| 1989 | First 3D demos appear (Juggler, etc.). Rendering in chunky buffers starts |
|
||||
| 1991 | Demoscene coders develop first optimised C2P routines for 68000 |
|
||||
| 1991 | Demoscene coders develop first optimized C2P routines for 68000 |
|
||||
| 1992 | AGA ships (A1200/A4000). 8 bitplanes = C2P problem gets 2× harder |
|
||||
| 1993 | CD32 ships with Akiko — first hardware C2P. Mikael Kalms publishes optimised CPU routines |
|
||||
| 1993 | CD32 ships with Akiko — first hardware C2P. Mikael Kalms publishes optimized CPU routines |
|
||||
| 1994 | Kalms C2P library becomes the de facto standard. Multiple variants for 020/030/040/060 |
|
||||
| 1995 | RTG cards (Picasso II, CyberVision 64) begin to make C2P irrelevant for productivity |
|
||||
| 1996 | CyberVision 64 ships with Roxxler P2C chip — the reverse problem, solved in hardware |
|
||||
|
|
@ -1432,41 +1440,6 @@ ULONG measure_c2p_time(void) {
|
|||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact on FPGA/Emulation — MiSTer & UAE Developers
|
||||
|
||||
Since this knowledge base targets MiSTer FPGA core developers, here are implementation concerns specific to hardware reproduction:
|
||||
|
||||
### C2P in FPGA Cores
|
||||
|
||||
The Minimig-AGA core on MiSTer provides both:
|
||||
- **Native planar output** — matches real Amiga bitplane DMA timing
|
||||
- **RTG framebuffer via uaegfx** — chunky framebuffer in DDR memory, no C2P needed
|
||||
|
||||
When running software that uses C2P on the MiSTer:
|
||||
1. The CPU merge algorithm runs on the emulated 68020 (TG68K or fx68k core)
|
||||
2. Memory timing must accurately model Chip RAM vs Fast RAM contention
|
||||
3. The Blitter must be cycle-accurate for Blitter-assisted C2P variants
|
||||
4. Akiko C2P must be implemented as a state machine triggered by register writes to `$B80030`
|
||||
|
||||
### Copper Chunky Accuracy
|
||||
|
||||
Copper Chunky is extremely sensitive to Copper timing:
|
||||
- Each WAIT must compare against the exact beam counter value
|
||||
- MOVE to COLOR00 must take effect at the correct pixel
|
||||
- DMA contention between Copper and bitplane fetches affects pixel placement
|
||||
- Emulators must model the Copper's 2-cycle instruction latency
|
||||
|
||||
### 68040/060 Cache Coherency
|
||||
|
||||
On FPGA cores implementing 68040+, the data cache must be coherent with DMA writes:
|
||||
- `MOVE16` writes should bypass or update the data cache
|
||||
- `CACR` flush instructions must invalidate cache lines matching DMA-visible addresses
|
||||
- Missed coherency bugs manifest as "shimmering" pixels in C2P output
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why not just use the Blitter for C2P?
|
||||
|
|
@ -1479,7 +1452,7 @@ Bitplane modulo calculations on non-aligned rows force the display DMA controlle
|
|||
|
||||
### Can I use Akiko on non-CD32 hardware?
|
||||
|
||||
No. Akiko is a custom ASIC that physically only exists in the CD32; it is integrated with the CD-ROM controller on the same die. There is no expansion card addressing `$B80000` on any other Amiga model. On MiSTer, Akiko can be implemented as a soft peripheral in the FPGA core.
|
||||
No. Akiko is a custom ASIC that physically only exists in the CD32; it is integrated with the CD-ROM controller on the same die. There is no expansion card addressing `$B80000` on any other Amiga model. On MiSTer, Akiko can be implemented as a soft peripheral in the FPGA core — see the FPGA implementation note in [Solution 3](#solution-3--akiko-hardware-c2p-cd32-only).
|
||||
|
||||
### Why doesn't C2P scale linearly with 68060 clock speed?
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`RastPort` is the primary drawing context in AmigaOS — the equivalent of a "device context" (Windows) or "graphics context" (X11). All graphics primitives (pixel, line, rectangle, polygon, text) operate through a RastPort, which bundles together a target `BitMap`, drawing pen colours, patterns, font, draw mode, and an optional `Layer` for clipping.
|
||||
`RastPort` is the primary drawing context in AmigaOS — the equivalent of a "device context" (Windows) or "graphics context" (X11). All graphics primitives (pixel, line, rectangle, polygon, text) operate through a RastPort, which bundles together a target `BitMap`, drawing pen colors, patterns, font, draw mode, and an optional `Layer` for clipping.
|
||||
|
||||
Every Intuition window and screen has its own RastPort. When you draw into a window, you're drawing through its RastPort.
|
||||
|
||||
|
|
@ -44,8 +44,8 @@ struct RastPort {
|
|||
struct AreaInfo *AreaInfo; /* area fill vertex buffer */
|
||||
struct GelsInfo *GelsInfo; /* GEL (BOB/VSprite) list */
|
||||
UBYTE Mask; /* plane mask (which planes to draw to) */
|
||||
BYTE FgPen; /* foreground pen colour index */
|
||||
BYTE BgPen; /* background pen colour index */
|
||||
BYTE FgPen; /* foreground pen color index */
|
||||
BYTE BgPen; /* background pen color index */
|
||||
BYTE AOlPen; /* area outline pen */
|
||||
BYTE DrawMode; /* JAM1, JAM2, COMPLEMENT, INVERSVID */
|
||||
BYTE AreaPtSz; /* area pattern size (log2) */
|
||||
|
|
@ -111,12 +111,12 @@ flowchart LR
|
|||
### Pen and Position Setup
|
||||
|
||||
```c
|
||||
/* Set pen colours: */
|
||||
SetAPen(rp, 1); /* foreground = colour register 1 */
|
||||
SetBPen(rp, 0); /* background = colour register 0 */
|
||||
/* Set pen colors: */
|
||||
SetAPen(rp, 1); /* foreground = color register 1 */
|
||||
SetBPen(rp, 0); /* background = color register 0 */
|
||||
SetDrMd(rp, JAM1); /* transparent background mode */
|
||||
|
||||
/* OS 3.0+ — use named pen for correct Workbench colours: */
|
||||
/* OS 3.0+ — use named pen for correct Workbench colors: */
|
||||
SetAPen(rp, screen->RastPort.BitMap->Depth > 1 ?
|
||||
ObtainBestPen(screen->ViewPort.ColorMap,
|
||||
0xFF000000, 0x00000000, 0x00000000, /* red */
|
||||
|
|
@ -153,7 +153,7 @@ Draw(rp, 10, 10); /* close: left edge */
|
|||
|
||||
/* Single pixel: */
|
||||
WritePixel(rp, 160, 120);
|
||||
LONG colour = ReadPixel(rp, 160, 120);
|
||||
LONG color = ReadPixel(rp, 160, 120);
|
||||
|
||||
/* Dashed lines: */
|
||||
SetDrPt(rp, 0xF0F0); /* 16-bit pattern: 1111000011110000 */
|
||||
|
|
@ -240,8 +240,8 @@ FreeRaster(tmpRasData, 320, 256);
|
|||
/* Flood fill from a seed point: */
|
||||
/* Requires TmpRas (same setup as area fills) */
|
||||
Flood(rp, 1, 50, 50);
|
||||
/* mode 1 = fill until FgPen colour boundary */
|
||||
/* mode 0 = fill all connected pixels of same colour as seed */
|
||||
/* mode 1 = fill until FgPen color boundary */
|
||||
/* mode 0 = fill all connected pixels of same color as seed */
|
||||
```
|
||||
|
||||
### Blitting (Block Transfer)
|
||||
|
|
@ -384,9 +384,9 @@ rp->Mask = 0xFF; /* all planes — default */
|
|||
rp->Mask = 0x01; /* only plane 0 — fast for single-plane effects */
|
||||
rp->Mask = 0x03; /* planes 0 and 1 only */
|
||||
|
||||
/* Use case: draw a 2-colour overlay without disturbing other planes: */
|
||||
/* Use case: draw a 2-color overlay without disturbing other planes: */
|
||||
rp->Mask = 0x04; /* only plane 2 */
|
||||
SetAPen(rp, 4); /* colour index with bit 2 set */
|
||||
SetAPen(rp, 4); /* color index with bit 2 set */
|
||||
RectFill(rp, 0, 0, 319, 255);
|
||||
rp->Mask = 0xFF; /* restore */
|
||||
```
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
The Amiga has **8 hardware sprites**, each 16 pixels wide with 3 colours + transparent. Sprites are entirely DMA-driven — the custom chips fetch sprite data from Chip RAM and composite them over the playfield with zero CPU overhead. The Copper reloads sprite pointers every frame.
|
||||
The Amiga has **8 hardware sprites**, each 16 pixels wide with 3 colors + transparent. Sprites are entirely DMA-driven — the custom chips fetch sprite data from Chip RAM and composite them over the playfield with zero CPU overhead. The Copper reloads sprite pointers every frame.
|
||||
|
||||
Sprite 0 is reserved by Intuition for the **mouse pointer**. Sprites 1–7 are available for application use.
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ flowchart LR
|
|||
subgraph "Custom Chips (Denise/Lisa)"
|
||||
DMA["Sprite DMA<br/>(8 channels)"] --> MUX["Priority MUX"]
|
||||
PF["Playfield<br/>(bitplane data)"] --> MUX
|
||||
MUX --> DAC["Colour DAC<br/>→ Video Out"]
|
||||
MUX --> DAC["Color DAC<br/>→ Video Out"]
|
||||
end
|
||||
|
||||
SD0 --> DMA
|
||||
|
|
@ -58,15 +58,15 @@ Each sprite is stored as a contiguous block in Chip RAM:
|
|||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Pixel Colour Encoding
|
||||
### Pixel Color Encoding
|
||||
|
||||
```
|
||||
Pixel colour = (DATB_bit << 1) | DATA_bit
|
||||
Pixel color = (DATB_bit << 1) | DATA_bit
|
||||
|
||||
00 = transparent (playfield shows through)
|
||||
01 = sprite colour 1
|
||||
10 = sprite colour 2
|
||||
11 = sprite colour 3
|
||||
01 = sprite color 1
|
||||
10 = sprite color 2
|
||||
11 = sprite color 3
|
||||
```
|
||||
|
||||
### Header Bit Layout
|
||||
|
|
@ -93,11 +93,11 @@ Word 1 (SPRxCTL):
|
|||
|
||||
---
|
||||
|
||||
## Sprite Colour Palette
|
||||
## Sprite Color Palette
|
||||
|
||||
Each pair of sprites shares 3 colour registers (colour 0 = transparent for all):
|
||||
Each pair of sprites shares 3 color registers (color 0 = transparent for all):
|
||||
|
||||
| Sprite Pair | Colour Registers | Custom Addresses | Notes |
|
||||
| Sprite Pair | Color Registers | Custom Addresses | Notes |
|
||||
|---|---|---|---|
|
||||
| 0–1 | `COLOR17`–`COLOR19` | `$DFF1A2`–`$DFF1A6` | Pair with mouse pointer |
|
||||
| 2–3 | `COLOR21`–`COLOR23` | `$DFF1AA`–`$DFF1AE` | |
|
||||
|
|
@ -105,7 +105,7 @@ Each pair of sprites shares 3 colour registers (colour 0 = transparent for all):
|
|||
| 6–7 | `COLOR29`–`COLOR31` | `$DFF1BA`–`$DFF1BE` | |
|
||||
|
||||
```c
|
||||
/* Set sprite 0-1 colours directly: */
|
||||
/* Set sprite 0-1 colors directly: */
|
||||
custom->color[17] = 0xF00; /* red */
|
||||
custom->color[18] = 0x0F0; /* green */
|
||||
custom->color[19] = 0xFFF; /* white */
|
||||
|
|
@ -113,24 +113,24 @@ custom->color[19] = 0xFFF; /* white */
|
|||
|
||||
---
|
||||
|
||||
## Attached Sprites — 15 Colours
|
||||
## Attached Sprites — 15 Colors
|
||||
|
||||
Two sprites from the same pair can be **attached** to form a single 15-colour (+ transparent) sprite:
|
||||
Two sprites from the same pair can be **attached** to form a single 15-color (+ transparent) sprite:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Normal (2 independent sprites)"
|
||||
S0["Sprite 0<br/>3 colours"] --- S1["Sprite 1<br/>3 colours"]
|
||||
S0["Sprite 0<br/>3 colors"] --- S1["Sprite 1<br/>3 colors"]
|
||||
end
|
||||
|
||||
subgraph "Attached (1 wide-colour sprite)"
|
||||
SA["Sprites 0+1 attached<br/>4 bits per pixel<br/>15 colours + transparent"]
|
||||
subgraph "Attached (1 wide-color sprite)"
|
||||
SA["Sprites 0+1 attached<br/>4 bits per pixel<br/>15 colors + transparent"]
|
||||
end
|
||||
|
||||
style SA fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
When attached, the even sprite provides bits 0–1 and the odd sprite provides bits 2–3 of the colour index. The 4-bit value indexes into colour registers 16–31.
|
||||
When attached, the even sprite provides bits 0–1 and the odd sprite provides bits 2–3 of the color index. The 4-bit value indexes into color registers 16–31.
|
||||
|
||||
```c
|
||||
/* Enable attachment: set bit 0 of odd sprite's CTL word */
|
||||
|
|
@ -178,8 +178,8 @@ The Copper waits for a line after one sprite ends, then reprograms the sprite po
|
|||
| Feature | OCS/ECS | AGA |
|
||||
|---|---|---|
|
||||
| Width | 16 pixels | 16, 32, or 64 pixels (via FMODE) |
|
||||
| Colours (single) | 3 + transparent | 3 + transparent |
|
||||
| Colours (attached) | 15 + transparent | 15 + transparent |
|
||||
| Colors (single) | 3 + transparent | 3 + transparent |
|
||||
| Colors (attached) | 15 + transparent | 15 + transparent |
|
||||
| Horizontal resolution | Low-res ÷ 2 | Same (unchanged) |
|
||||
|
||||
```c
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ struct ViewPort {
|
|||
struct ColorMap *ColorMap; /* palette for this viewport */
|
||||
struct CopList *DspIns; /* display copper instructions */
|
||||
struct CopList *SprIns; /* sprite copper instructions */
|
||||
struct CopList *ClrIns; /* colour copper instructions */
|
||||
struct CopList *ClrIns; /* color copper instructions */
|
||||
struct CopList *UCopIns; /* user copper instructions */
|
||||
WORD DWidth; /* display width */
|
||||
WORD DHeight; /* display height */
|
||||
|
|
@ -54,8 +54,8 @@ struct RasInfo {
|
|||
/* graphics/view.h */
|
||||
#define HIRES 0x8000 /* 640 pixel mode */
|
||||
#define LACE 0x0004 /* interlaced */
|
||||
#define HAM 0x0800 /* Hold-And-Modify (4096 colours) */
|
||||
#define EXTRA_HALFBRITE 0x0080 /* Extra Half-Brite (64 colours) */
|
||||
#define HAM 0x0800 /* Hold-And-Modify (4096 colors) */
|
||||
#define EXTRA_HALFBRITE 0x0080 /* Extra Half-Brite (64 colors) */
|
||||
#define DUALPF 0x0400 /* dual playfield */
|
||||
#define PFBA 0x0040 /* playfield B has priority */
|
||||
#define SUPERHIRES 0x0020 /* 1280 pixel mode (ECS+) */
|
||||
|
|
@ -81,7 +81,7 @@ vp.DWidth = 320;
|
|||
vp.DHeight = 256;
|
||||
vp.Modes = 0; /* lores */
|
||||
|
||||
/* Build colour map: */
|
||||
/* Build color map: */
|
||||
vp.ColorMap = GetColorMap(32);
|
||||
|
||||
/* Compile to copper: */
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ Intuition is the AmigaOS windowing system and user interface manager. It sits be
|
|||
| [idcmp.md](idcmp.md) | IDCMP — event architecture, class reference, shared ports, antipatterns, use-case cookbook |
|
||||
| [boopsi.md](boopsi.md) | BOOPSI — OOP dispatcher model, ICA interconnection, custom class tutorial, class hierarchy |
|
||||
| [input_events.md](input_events.md) | Input Events — handler chain, QoS/priority, Commodities, latency analysis, game input |
|
||||
| [commodities.md](commodities.md) | Commodities Exchange — background input filtering, hotkeys, CxObjects, Exchange control |
|
||||
| **[frameworks/](frameworks/)** | **GUI Frameworks: MUI, ReAction, BGUI** |
|
||||
|
||||
---
|
||||
|
|
@ -246,9 +247,9 @@ graph LR
|
|||
```mermaid
|
||||
graph TB
|
||||
subgraph "Screens (front to back)"
|
||||
S1["Screen 1: Custom (640×256 4-colour)<br/>sc_ViewPort → Copper List 1"]
|
||||
S2["Screen 2: Workbench (640×256 8-colour)<br/>sc_ViewPort → Copper List 2"]
|
||||
S3["Screen 3: Game (320×256 32-colour)<br/>sc_ViewPort → Copper List 3"]
|
||||
S1["Screen 1: Custom (640×256 4-color)<br/>sc_ViewPort → Copper List 1"]
|
||||
S2["Screen 2: Workbench (640×256 8-color)<br/>sc_ViewPort → Copper List 2"]
|
||||
S3["Screen 3: Game (320×256 32-color)<br/>sc_ViewPort → Copper List 3"]
|
||||
end
|
||||
|
||||
subgraph "Copper"
|
||||
|
|
|
|||
769
09_intuition/commodities.md
Normal file
769
09_intuition/commodities.md
Normal file
|
|
@ -0,0 +1,769 @@
|
|||
[← Home](../README.md) · [Intuition](README.md)
|
||||
|
||||
# Commodities Exchange — Background Input Filtering and Hotkey System
|
||||
|
||||
## Overview
|
||||
|
||||
The Commodities Exchange system, introduced in AmigaOS 2.0 (V37), provides a safe, standardized framework for installing background input handlers that monitor, filter, translate, or respond to user input across all applications. Rather than installing a raw `input.device` handler — which risks priority conflicts, antisocial behavior, and system instability — a commodity registers a **broker** with `commodities.library`. The library maintains a single input handler just before Intuition in the handler chain and routes matching events through a tree of **CxObjects** (Commodities Exchange Objects) attached to each broker. This design allows multiple hotkey utilities, screen blankers, mouse enhancers, and macro tools to coexist without fighting over the input stream. The system includes the **Exchange** controller utility (OS 3.0+) that lets users enable, disable, or kill running commodities from a single interface. For developers, Commodities Exchange is the only supported method for global hotkeys and input filtering; raw input handlers should be reserved for games and system-level tools that genuinely need priority access.
|
||||
|
||||
> **Key constraint**: A commodity is a background utility, not an application. Using the Commodities framework merely to give a normal application a keyboard shortcut is an architectural misuse.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Where Commodities Sits in the Input Chain
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
HW["Hardware<br/>CIA / Gameport"] --> KBD["keyboard.device<br/>gameport.device"]
|
||||
KBD --> INP["input.device<br/>handler chain"]
|
||||
INP --> RAW["Raw handlers<br/>(priority 60–52)"]
|
||||
RAW --> CX["Commodities Exchange<br/>(priority 51)"]
|
||||
CX --> B1["Broker: FKey"]
|
||||
CX --> B2["Broker: ClickToFront"]
|
||||
CX --> B3["Broker: ScreenBlanker"]
|
||||
B1 --> F1["CxFilter<br/>'ctrl alt f'"]
|
||||
F1 --> S1["CxSender<br/>→ MsgPort"]
|
||||
B2 --> F2["CxFilter<br/>'lcommand m'"]
|
||||
F2 --> T2["CxTranslate<br/>→ window-to-front"]
|
||||
CX --> INT["Intuition<br/>(priority 50)"]
|
||||
INT --> APP["Application<br/>IDCMP"]
|
||||
|
||||
style CX fill:#e8f5e9,stroke:#4caf50,color:#333
|
||||
style B1 fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
style B2 fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
style B3 fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
```
|
||||
|
||||
### CxObject Types
|
||||
|
||||
A commodity is constructed by linking CxObjects into a tree under a broker:
|
||||
|
||||
| Type | Creator | Purpose |
|
||||
|---|---|---|
|
||||
| **Broker** | `CxBroker()` | Root object; registers the commodity with the Exchange network |
|
||||
| **Filter** | `CxFilter()` | Matches input events based on description string or `IX` structure |
|
||||
| **Sender** | `CxSender()` | Sends a `CxMsg` to a user-supplied `MsgPort` |
|
||||
| **Signal** | `CxSignal()` | Signals a task when a matching event arrives |
|
||||
| **Translate** | `CxTranslate()` | Replaces the matched input event with a different event |
|
||||
| **Custom** | `CxCustom()` | Calls a user-supplied callback function |
|
||||
| **Debug** | `CxDebug()` | Sends debug info to the serial port |
|
||||
|
||||
### Message Flow
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
IN["InputEvent from input.device"] --> CX["Commodities handler"]
|
||||
CX --> BROKER{"Broker active?"}
|
||||
BROKER -->|"No"| PASS["Return to input stream"]
|
||||
BROKER -->|"Yes"| FILTER{"CxFilter matches?"}
|
||||
FILTER -->|"No"| PASS
|
||||
FILTER -->|"Yes"| ACTION["Execute CxObject action<br/>Sender / Signal / Translate / Custom"]
|
||||
ACTION --> DELETE{"Delete event?"}
|
||||
DELETE -->|"Yes"| CONSUME["Event removed from stream<br/>(Intuition never sees it)"]
|
||||
DELETE -->|"No"| PASS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Structures
|
||||
|
||||
### NewBroker
|
||||
|
||||
```c
|
||||
/* libraries/commodities.h — NDK 3.9 */
|
||||
struct NewBroker {
|
||||
BYTE nb_Version; /* NB_VERSION */
|
||||
BYTE nb_Pad;
|
||||
STRPTR nb_Name; /* Name shown in Exchange */
|
||||
STRPTR nb_Title; /* Window title (if applicable) */
|
||||
STRPTR nb_Descr; /* Description shown in Exchange */
|
||||
SWORD nb_Unique; /* NBU_* uniqueness flags */
|
||||
SWORD nb_Flags; /* NB_* flags */
|
||||
BYTE nb_Pri; /* Broker priority (-128 to 127) */
|
||||
BYTE nb_Pad2;
|
||||
struct MsgPort * nb_Port; /* Port for CXCMD_* messages */
|
||||
WORD nb_ReservedChannel; /* Must be zero */
|
||||
};
|
||||
```
|
||||
|
||||
### Uniqueness Flags
|
||||
|
||||
| Flag | Value | Meaning |
|
||||
|---|---|---|
|
||||
| `NBU_DUPLICATE` | 0 | Allow multiple instances |
|
||||
| `NBU_UNIQUE` | 1 | Only one instance; subsequent `CxBroker()` calls fail |
|
||||
| `NBU_NOTIFY` | 2 | If unique and duplicate starts, send `CXCMD_UNIQUE` to existing instance |
|
||||
| `NBU_FIXED` | 4 | Fixed priority — Exchange cannot change it |
|
||||
|
||||
### Broker Error Codes
|
||||
|
||||
| Code | Name | Meaning |
|
||||
|---|---|---|
|
||||
| `CBERR_OK` | 0 | Success |
|
||||
| `CBERR_SYSERR` | 1 | System error (out of memory) |
|
||||
| `CBERR_DUP` | 2 | Uniqueness violation |
|
||||
| `CBERR_VERSION` | 3 | `nb_Version` mismatch |
|
||||
|
||||
### IX — Input Expression Structure
|
||||
|
||||
For programmatic filter construction without parsing a string:
|
||||
|
||||
```c
|
||||
struct IX {
|
||||
UBYTE ix_Version; /* IX_VERSION */
|
||||
UBYTE ix_Class; /* IECLASS_* to match */
|
||||
UWORD ix_Code; /* Code mask (e.g., rawkey scancode) */
|
||||
UWORD ix_CodeMask; /* Bits that must match in ix_Code */
|
||||
WORD ix_CodeLess; /* Match if code <= this */
|
||||
WORD ix_CodeGreater; /* Match if code >= this */
|
||||
UWORD ix_Qualifier; /* Qualifier mask (Shift, Alt, etc.) */
|
||||
UWORD ix_QualMask; /* Bits that must match in ix_Qualifier */
|
||||
UWORD ix_QualSame; /* Bits that must be identical (both set or both clear) */
|
||||
LONG ix_QualLess; /* Not typically used */
|
||||
LONG ix_QualGreater; /* Not typically used */
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Broker Lifecycle
|
||||
|
||||
```c
|
||||
#include <libraries/commodities.h>
|
||||
#include <proto/commodities.h>
|
||||
|
||||
/* Create and register a broker */
|
||||
CxObj *CxBroker(struct NewBroker *nb, LONG *error);
|
||||
|
||||
/* Activate or deactivate a CxObject (including broker) */
|
||||
void ActivateCxObj(CxObj *co, LONG true);
|
||||
|
||||
/* Delete a single CxObject */
|
||||
void DeleteCxObj(CxObj *co);
|
||||
|
||||
/* Delete a CxObject and all objects attached to it (tree walk) */
|
||||
void DeleteCxObjAll(CxObj *co);
|
||||
```
|
||||
|
||||
### CxObject Creators
|
||||
|
||||
```c
|
||||
/* Filter: match input events by description string */
|
||||
CxObj *CxFilter(STRPTR description);
|
||||
|
||||
/* Filter: match by IX structure */
|
||||
CxObj *CxFilterIX(struct IX *ix);
|
||||
|
||||
/* Sender: send CxMsg to a MsgPort */
|
||||
CxObj *CxSender(struct MsgPort *port, LONG id);
|
||||
|
||||
/* Signal: signal a task */
|
||||
CxObj *CxSignal(struct Task *task, LONG signal);
|
||||
|
||||
/* Translate: replace event with a new InputEvent chain */
|
||||
CxObj *CxTranslate(struct InputEvent *ie);
|
||||
|
||||
/* Custom: call user function */
|
||||
CxObj *CxCustom(VOID (*func)(CxMsg *msg, CxObj *co), LONG id);
|
||||
|
||||
/* Debug: dump to serial port */
|
||||
CxObj *CxDebug(LONG id);
|
||||
```
|
||||
|
||||
### Object Tree Manipulation
|
||||
|
||||
```c
|
||||
/* Attach co to parent's list (appended) */
|
||||
void AttachCxObj(CxObj *parent, CxObj *co);
|
||||
|
||||
/* Insert co at head of parent's list */
|
||||
void InsertCxObj(CxObj *parent, CxObj *co);
|
||||
|
||||
/* Enqueue co by priority */
|
||||
void EnqueueCxObj(CxObj *parent, CxObj *co);
|
||||
|
||||
/* Remove co from its parent (does not free) */
|
||||
void RemoveCxObj(CxObj *co);
|
||||
|
||||
/* Set an error code on a CxObject */
|
||||
void SetCxObjErr(CxObj *co, LONG error);
|
||||
|
||||
/* Get error code from a CxObject */
|
||||
LONG GetCxObjErr(CxObj *co);
|
||||
```
|
||||
|
||||
### Message Inspection
|
||||
|
||||
```c
|
||||
/* Get the type of a CxMsg */
|
||||
ULONG CxMsgType(CxMsg *cxm);
|
||||
|
||||
/* Get the ID of a CxMsg */
|
||||
LONG CxMsgID(CxMsg *cxm);
|
||||
|
||||
/* Get the associated InputEvent (for CXM_IEVENT) */
|
||||
struct InputEvent *CxMsgData(CxMsg *cxm);
|
||||
|
||||
/* Get the blurred/fuzzed qualifier (for wildcard matching) */
|
||||
ULONG CxMsgQualifier(CxMsg *cxm);
|
||||
```
|
||||
|
||||
### CxMsg Types
|
||||
|
||||
| Type | Description |
|
||||
|---|---|
|
||||
| `CXM_IEVENT` | Derived from an input event (reaches Filter → Sender/Signal/Translate) |
|
||||
| `CXM_COMMAND` | Control command from Exchange or another controller |
|
||||
|
||||
### Controller Commands (CXM_COMMAND IDs)
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `CXCMD_DISABLE` | Deactivate the commodity |
|
||||
| `CXCMD_ENABLE` | Activate the commodity |
|
||||
| `CXCMD_KILL` | Shut down the commodity |
|
||||
| `CXCMD_UNIQUE` | Another instance tried to start (only with `NBU_NOTIFY`) |
|
||||
| `CXCMD_APPEAR` | Show the commodity's window (if it has one) |
|
||||
| `CXCMD_DISAPPEAR` | Hide the commodity's window |
|
||||
|
||||
### Convenience Function
|
||||
|
||||
```c
|
||||
/* Create a complete hotkey triad: Filter + Sender, attached to broker */
|
||||
CxObj *HotKey(STRPTR description, struct MsgPort *port, LONG id);
|
||||
```
|
||||
|
||||
> **Note**: `HotKey()` is a convenience — it creates a `CxFilter`, a `CxSender`, links them, and attaches to the broker in one call. Most simple hotkey commodities should use it.
|
||||
|
||||
---
|
||||
|
||||
## Filter Description String Syntax
|
||||
|
||||
The description string passed to `CxFilter()` or `HotKey()` describes the input event to match.
|
||||
|
||||
| Component | Syntax | Matches |
|
||||
|---|---|---|
|
||||
| **Key + modifiers** | `"ctrl alt d"` | Ctrl+Alt+D |
|
||||
| **Raw key** | `"rawkey f1"` | F1 function key |
|
||||
| **Raw key + modifiers** | `"rawkey lshift rshift escape"` | Both Shifts + Escape |
|
||||
| **Upstroke** | `"-upstroke rawkey capslock"` | Key release (not press) |
|
||||
| **Suppress repeat** | `"alt -repeat a"` | Alt+A, ignoring auto-repeat |
|
||||
| **Mouse buttons** | `"ctrl selectdown"` | Ctrl + left mouse button down |
|
||||
| **Qualifier-only** | `"ctrl"` | Any event while Ctrl is held |
|
||||
| **Wildcard** | `"rawkey"` | Any raw key event |
|
||||
|
||||
### Modifier Keywords
|
||||
|
||||
| Keyword | Key |
|
||||
|---|---|
|
||||
| `lshift`, `rshift`, `shift` | Shift keys |
|
||||
| `lalt`, `ralt`, `alt` | Alt keys |
|
||||
| `lcommand`, `rcommand`, `command` | Amiga keys |
|
||||
| `ctrl` | Control key |
|
||||
| `capslock` | Caps Lock |
|
||||
| `numericpad` | Numeric pad |
|
||||
|
||||
---
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Example 1: Minimal Hotkey Commodity
|
||||
|
||||
```c
|
||||
#include <exec/types.h>
|
||||
#include <libraries/commodities.h>
|
||||
#include <proto/commodities.h>
|
||||
#include <proto/exec.h>
|
||||
|
||||
struct Library *CxBase;
|
||||
struct MsgPort *cxPort;
|
||||
CxObj *broker;
|
||||
|
||||
BOOL InitCommodity(void)
|
||||
{
|
||||
CxBase = OpenLibrary("commodities.library", 37);
|
||||
if (!CxBase) return FALSE;
|
||||
|
||||
cxPort = CreateMsgPort();
|
||||
if (!cxPort) return FALSE;
|
||||
|
||||
struct NewBroker nb = {
|
||||
NB_VERSION,
|
||||
"MyHotKey", /* Name */
|
||||
"My HotKey Tool", /* Title */
|
||||
"Global hotkey utility", /* Description */
|
||||
NBU_UNIQUE | NBU_NOTIFY, /* One instance, notify on dup */
|
||||
0,
|
||||
0, /* Priority */
|
||||
cxPort,
|
||||
0
|
||||
};
|
||||
|
||||
broker = CxBroker(&nb, NULL);
|
||||
if (!broker) return FALSE;
|
||||
|
||||
/* Create hotkey: Ctrl+Alt+H */
|
||||
CxObj *hotkey = HotKey("ctrl alt h", cxPort, 1);
|
||||
if (hotkey)
|
||||
{
|
||||
AttachCxObj(broker, hotkey);
|
||||
}
|
||||
|
||||
ActivateCxObj(broker, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void RunCommodity(void)
|
||||
{
|
||||
ULONG cxSig = 1L << cxPort->mp_SigBit;
|
||||
BOOL running = TRUE;
|
||||
|
||||
while (running)
|
||||
{
|
||||
ULONG received = Wait(cxSig | SIGBREAKF_CTRL_C);
|
||||
|
||||
if (received & SIGBREAKF_CTRL_C) running = FALSE;
|
||||
|
||||
if (received & cxSig)
|
||||
{
|
||||
CxMsg *msg;
|
||||
while ((msg = (CxMsg *)GetMsg(cxPort)))
|
||||
{
|
||||
ULONG type = CxMsgType(msg);
|
||||
LONG id = CxMsgID(msg);
|
||||
ReplyMsg((struct Message *)msg);
|
||||
|
||||
if (type == CXM_IEVENT && id == 1)
|
||||
{
|
||||
/* Ctrl+Alt+H was pressed */
|
||||
HandleHotkeyAction();
|
||||
}
|
||||
else if (type == CXM_COMMAND)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case CXCMD_DISABLE:
|
||||
ActivateCxObj(broker, FALSE);
|
||||
break;
|
||||
case CXCMD_ENABLE:
|
||||
ActivateCxObj(broker, TRUE);
|
||||
break;
|
||||
case CXCMD_KILL:
|
||||
running = FALSE;
|
||||
break;
|
||||
case CXCMD_UNIQUE:
|
||||
/* Another instance tried to start */
|
||||
ShowPopup("Already running!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CleanupCommodity(void)
|
||||
{
|
||||
DeleteCxObjAll(broker); /* Frees entire tree */
|
||||
DeleteMsgPort(cxPort);
|
||||
CloseLibrary(CxBase);
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Screen Blanker Pattern
|
||||
|
||||
```c
|
||||
/* A screen blanker monitors all input and resets a timer.
|
||||
After N seconds of inactivity, it blanks the screen. */
|
||||
|
||||
CxObj *blankerBroker;
|
||||
struct MsgPort *blankerPort;
|
||||
ULONG blankerSig;
|
||||
|
||||
BOOL InitBlanker(void)
|
||||
{
|
||||
blankerPort = CreateMsgPort();
|
||||
blankerSig = 1L << blankerPort->mp_SigBit;
|
||||
|
||||
struct NewBroker nb = {
|
||||
NB_VERSION,
|
||||
"ScreenBlanker",
|
||||
"Screen Blanker",
|
||||
"Blanks screen after inactivity",
|
||||
NBU_UNIQUE, 0, 0, blankerPort, 0
|
||||
};
|
||||
|
||||
blankerBroker = CxBroker(&nb, NULL);
|
||||
if (!blankerBroker) return FALSE;
|
||||
|
||||
/* Match ALL input events to detect activity */
|
||||
CxObj *filter = CxFilter(""); /* Empty string = match everything */
|
||||
CxObj *sender = CxSender(blankerPort, EVT_BLANKER_ACTIVITY);
|
||||
AttachCxObj(filter, sender);
|
||||
AttachCxObj(blankerBroker, filter);
|
||||
|
||||
ActivateCxObj(blankerBroker, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Key Remapping with CxTranslate
|
||||
|
||||
```c
|
||||
/* Remap Caps Lock to Escape — useful for some text editors */
|
||||
CxObj *remapBroker;
|
||||
|
||||
BOOL InitRemap(void)
|
||||
{
|
||||
struct NewBroker nb = { NB_VERSION, "KeyRemap", "Key Remap",
|
||||
"Remaps Caps Lock to Escape",
|
||||
NBU_UNIQUE, 0, 0, NULL, 0 };
|
||||
remapBroker = CxBroker(&nb, NULL);
|
||||
if (!remapBroker) return FALSE;
|
||||
|
||||
/* Filter: Caps Lock press */
|
||||
CxObj *filter = CxFilter("rawkey capslock");
|
||||
|
||||
/* Translate: replace with Escape press */
|
||||
struct InputEvent ie;
|
||||
ie.ie_NextEvent = NULL;
|
||||
ie.ie_Class = IECLASS_RAWKEY;
|
||||
ie.ie_SubClass = 0;
|
||||
ie.ie_Code = 0x45; /* Escape scancode */
|
||||
ie.ie_Qualifier = 0;
|
||||
ie.ie_X = 0;
|
||||
ie.ie_Y = 0;
|
||||
|
||||
CxObj *translate = CxTranslate(&ie);
|
||||
AttachCxObj(filter, translate);
|
||||
AttachCxObj(remapBroker, filter);
|
||||
|
||||
ActivateCxObj(remapBroker, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Custom CxObject Callback
|
||||
|
||||
```c
|
||||
/* Advanced: intercept events with a custom function */
|
||||
void MyCustomHandler(CxMsg *msg, CxObj *co)
|
||||
{
|
||||
struct InputEvent *ie = CxMsgData(msg);
|
||||
|
||||
/* Log the event to console */
|
||||
Printf("Event: class=%ld code=$%04lx qual=$%04lx\n",
|
||||
ie->ie_Class, ie->ie_Code, ie->ie_Qualifier);
|
||||
|
||||
/* Do NOT delete the message — let it continue to Intuition */
|
||||
}
|
||||
|
||||
/* Usage: */
|
||||
CxObj *custom = CxCustom(MyCustomHandler, 0);
|
||||
AttachCxObj(broker, custom);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decision Guide
|
||||
|
||||
| Approach | Input Handler Direct | Commodities Exchange | IDCMP |
|
||||
|---|---|---|---|
|
||||
| **When to use** | Games, system tools needing priority | Global hotkeys, screen blankers, input filters | Normal application input |
|
||||
| **Priority** | Any (can preempt Intuition) | Fixed at 51 (just before Intuition) | 50 (Intuition) |
|
||||
| **Safety** | Dangerous — conflicts with other handlers | Safe — managed by commodities.library | Safe — standard window events |
|
||||
| **Exchange control** | No | Yes — user can disable/kill | No |
|
||||
| **Event consumption** | Yes — can remove events from stream | Yes — via Translate or Custom | No — app sees only its window's events |
|
||||
| **Complexity** | High — manual IE chain management | Medium — CxObject tree setup | Low — standard IDCMP loop |
|
||||
| **Coexistence** | Poor — load-order dependent | Excellent — multiple brokers coexist | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Historical Context & Modern Analogies
|
||||
|
||||
### The 1990 Problem: Input Handler Anarchy
|
||||
|
||||
Before Commodities Exchange (pre-1990), developers who wanted global hotkeys installed raw `input.device` handlers directly. This created a "wild west" environment:
|
||||
|
||||
| Problem | Cause |
|
||||
|---|---|
|
||||
| **Load-order sensitivity** | Handlers installed later had higher priority; tools fought to be last |
|
||||
| **Event starvation** | A greedy handler could consume all input, freezing Intuition |
|
||||
| **No user control** | Once installed, only the owning program could remove a handler |
|
||||
| **Incompatibility** | Two hotkey tools often crashed when both were active |
|
||||
|
||||
Commodities Exchange solved this by providing a **single, system-managed handler** at priority 51 that all tools share. The framework handles coexistence, activation/deactivation, and graceful shutdown.
|
||||
|
||||
### Modern Analogies
|
||||
|
||||
| Commodities Concept | Modern Equivalent | Shared Concept |
|
||||
|---|---|---|
|
||||
| `CxBroker` | **Windows Service / macOS LaunchAgent** | Background process registered with the OS |
|
||||
| `CxFilter` + `CxSender` | **Global hotkey API** (RegisterHotKey, `MASShortcut`) | System-wide keybinding that sends message to app |
|
||||
| `CxTranslate` | **Karabiner-Elements / AutoHotkey remap** | Intercept and replace input events at system level |
|
||||
| `Exchange` utility | **Activity Monitor / Task Manager** | Central UI to manage background utilities |
|
||||
| `CXCMD_DISABLE/ENABLE` | **Launchctl load/unload** | Administrator control over background services |
|
||||
| `NBU_UNIQUE` | **Singleton pattern enforced by OS** | Only one instance of a service allowed |
|
||||
|
||||
### Where Analogies Break Down
|
||||
|
||||
- **No sandboxing**: Commodities run in the same address space as everything else. A buggy commodity can crash the system.
|
||||
- **No modern security model**: Any program can open `commodities.library` and install a broker. There is no privilege escalation or user confirmation.
|
||||
- **Polling-based event loop**: Unlike modern event-driven frameworks, the commodity must `Wait()` on a signal bit and drain its port — there is no callback thread pool.
|
||||
- **Cooperative only**: A commodity cannot forcefully prevent another commodity from seeing events. Priority ordering within the broker list determines precedence, but all brokers at the same priority see the same events.
|
||||
|
||||
---
|
||||
|
||||
## When to Use / When NOT to Use
|
||||
|
||||
### When to Use Commodities Exchange
|
||||
|
||||
| Scenario | Why Commodities Works |
|
||||
|---|---|
|
||||
| **Global hotkeys** | `HotKey()` provides a 5-line solution; Exchange lets users disable conflicts |
|
||||
| **Screen/mouse blankers** | Monitor all input via empty-string `CxFilter()`; reset idle timer |
|
||||
| **Input event logging** | `CxCustom()` callback sees every keystroke and mouse move |
|
||||
| **Key remapping** | `CxTranslate()` replaces events transparently to applications |
|
||||
| **Macro tools** | Capture a trigger, then inject synthetic events back into the stream |
|
||||
| **Window management tools** | ClickToFront-style utilities — detect click+modifier, call Intuition |
|
||||
|
||||
### When NOT to Use Commodities Exchange
|
||||
|
||||
| Scenario | Problem | Better Alternative |
|
||||
|---|---|---|
|
||||
| **Normal application shortcuts** | Misuse of the framework; steals input from other apps | IDCMP `IDCMP_VANILLAKEY` in your window |
|
||||
| **Per-window hotkeys** | Commodities are global; you don't need global scope | Register gadgets with `GA_RelVerify` |
|
||||
| **High-frequency input sampling** | The CxMessage queue adds latency | Raw `input.device` handler at priority 52+ |
|
||||
| **Game input** | Games need low latency and often take over the screen | Direct `input.device` or hardware polling |
|
||||
| **Secure input (passwords)** | Any commodity can log keystrokes | There is no secure input path on AmigaOS |
|
||||
|
||||
---
|
||||
|
||||
## Best Practices & Antipatterns
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always use `NBU_UNIQUE | NBU_NOTIFY`** for hotkey tools — prevents multiple instances and alerts the user.
|
||||
2. **Call `DeleteCxObjAll(broker)` on shutdown** — this walks the entire tree and frees all attached CxObjects.
|
||||
3. **Reply to every `CxMsg`** — even command messages must be `ReplyMsg()`'d back to the system.
|
||||
4. **Handle `CXCMD_KILL` immediately** — when Exchange tells you to die, clean up and exit.
|
||||
5. **Use `HotKey()` for simple cases** — it saves ~10 lines of boilerplate.
|
||||
6. **Set a meaningful `nb_Descr`** — users see this in Exchange and need to know what your tool does.
|
||||
7. **Consume events sparingly** — removing input from the stream prevents other apps from receiving it. Only consume what you genuinely need.
|
||||
8. **Test with multiple commodities active** — verify your tool plays nicely with FKey, ClickToFront, etc.
|
||||
9. **Use `CxFilterIX()` for complex matching** — when description strings are insufficient, build an `IX` struct.
|
||||
10. **Never call `OpenLibrary("commodities.library", 0)`** — always specify `37` or higher to ensure Exchange compatibility.
|
||||
|
||||
### Antipatterns
|
||||
|
||||
#### 1. The Greedy Filter
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — matching ALL events and not letting them through */
|
||||
CxObj *filter = CxFilter(""); /* Match everything */
|
||||
CxObj *sender = CxSender(myPort, 0);
|
||||
AttachCxObj(filter, sender);
|
||||
/* No Translate to regenerate the event → ALL input is consumed! */
|
||||
|
||||
/* RESULT: System appears frozen because no input reaches Intuition. */
|
||||
|
||||
/* CORRECT — if you only need to monitor, use CxCustom and do NOT
|
||||
delete the message. Or use Translate to pass the event through. */
|
||||
CxObj *filter = CxFilter("");
|
||||
CxObj *custom = CxCustom(MyMonitor, 0);
|
||||
AttachCxObj(filter, custom);
|
||||
/* MyMonitor does NOT delete the CxMsg → event continues to Intuition */
|
||||
```
|
||||
|
||||
#### 2. The Application-in-Disguise
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — using commodities just to show/hide an app window */
|
||||
struct NewBroker nb = { ... };
|
||||
CxObj *broker = CxBroker(&nb, NULL);
|
||||
/* App runs as a commodity purely to get a hotkey that brings up UI */
|
||||
|
||||
/* CORRECT — use IDCMP for app shortcuts; commodities are for
|
||||
background utilities that operate across all applications. */
|
||||
```
|
||||
|
||||
#### 3. The Memory Leak Tree
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — deleting the broker but not its children */
|
||||
DeleteCxObj(broker); /* Only frees the broker! */
|
||||
/* Children (Filter, Sender, etc.) are leaked. */
|
||||
|
||||
/* CORRECT — use DeleteCxObjAll to free the entire tree */
|
||||
DeleteCxObjAll(broker);
|
||||
```
|
||||
|
||||
#### 4. The Unreplied Message
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — GetMsg without ReplyMsg in error path */
|
||||
CxMsg *msg = (CxMsg *)GetMsg(cxPort);
|
||||
if (CxMsgType(msg) == CXM_IEVENT)
|
||||
HandleIt(msg);
|
||||
/* Missing ReplyMsg! System message pool exhausts. */
|
||||
|
||||
/* CORRECT — always reply, even if you don't handle the type */
|
||||
CxMsg *msg;
|
||||
while ((msg = (CxMsg *)GetMsg(cxPort)))
|
||||
{
|
||||
/* Process... */
|
||||
ReplyMsg((struct Message *)msg); /* Always! */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls & Common Mistakes
|
||||
|
||||
### 1. Priority Inversion with Intuition
|
||||
|
||||
```c
|
||||
/* PITFALL — setting nb_Pri too high */
|
||||
nb.nb_Pri = 100; /* Above Intuition! */
|
||||
|
||||
/* Commodities handler is at priority 51. Setting broker priority
|
||||
above this does not change the handler priority — it only affects
|
||||
ordering among brokers. However, documenting an incorrect priority
|
||||
confuses maintainers. Valid range: -128 to 127. */
|
||||
```
|
||||
|
||||
### 2. CxTranslate Event Ownership
|
||||
|
||||
```c
|
||||
/* PITFALL — passing a stack-allocated InputEvent to CxTranslate */
|
||||
struct InputEvent ie; /* on stack */
|
||||
ie.ie_Class = IECLASS_RAWKEY;
|
||||
ie.ie_Code = 0x45;
|
||||
CxObj *xlate = CxTranslate(&ie);
|
||||
/* The translated event may outlive this stack frame → CRASH. */
|
||||
|
||||
/* CORRECT — allocate the InputEvent in static or heap memory */
|
||||
static struct InputEvent ie_escape = {
|
||||
NULL, IECLASS_RAWKEY, 0, 0x45, 0, 0, 0
|
||||
};
|
||||
CxObj *xlate = CxTranslate(&ie_escape);
|
||||
```
|
||||
|
||||
### 3. Signal Collision
|
||||
|
||||
```c
|
||||
/* PITFALL — CxSignal uses a signal bit that conflicts with your app */
|
||||
CxObj *sig = CxSignal(FindTask(NULL), SIGBREAKF_CTRL_F);
|
||||
/* If your app also Wait()s on SIGBREAKF_CTRL_F, both wake up and
|
||||
you cannot tell whether it was the commodity or something else. */
|
||||
|
||||
/* CORRECT — allocate a private signal bit with AllocSignal() */
|
||||
BYTE sigBit = AllocSignal(-1);
|
||||
CxObj *sig = CxSignal(FindTask(NULL), 1L << sigBit);
|
||||
/* Now you can distinguish commodity signals from other sources. */
|
||||
```
|
||||
|
||||
### 4. Empty Filter String Misunderstanding
|
||||
|
||||
```c
|
||||
/* PITFALL — "" matches EVERYTHING, including mouse movement */
|
||||
CxObj *filter = CxFilter("");
|
||||
|
||||
/* A screen blanker wants this, but a hotkey tool does NOT.
|
||||
Mouse movement at 60 Hz will flood your message port. */
|
||||
|
||||
/* CORRECT — be specific about what you want to match */
|
||||
CxObj *filter = CxFilter("ctrl alt d"); /* Only this combo */
|
||||
```
|
||||
|
||||
### 5. Exchange Compatibility
|
||||
|
||||
```c
|
||||
/* PITFALL — not creating a MsgPort for the broker */
|
||||
nb.nb_Port = NULL;
|
||||
|
||||
/* Exchange cannot send CXCMD_DISABLE/KILL to your commodity.
|
||||
The user must use Break or kill the process externally. */
|
||||
|
||||
/* CORRECT — always provide a port if you want Exchange control */
|
||||
nb.nb_Port = CreateMsgPort();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Real-World Commodities
|
||||
|
||||
| Commodity | Function | CxObjects Used |
|
||||
|---|---|---|
|
||||
| **FKey** | Assign function keys to launch programs, scripts, or ARexx macros | `HotKey()` (Filter + Sender) |
|
||||
| **ClickToFront** | Click a window while holding Amiga key to bring it to front | `CxFilter`, `CxCustom` |
|
||||
| **ScreenBlanker** | Blank screen after idle timeout; wake on any input | `CxFilter(""), CxSender` |
|
||||
| **MagicMenu** | Pop-up menus triggered by right-mouse-hold | `CxFilter`, `CxCustom` |
|
||||
| **MouseBlanker** | Hide mouse pointer after idle timeout | `CxFilter(""), CxCustom` |
|
||||
| **MultiCX** | Framework for chaining multiple small commodities | Multiple brokers |
|
||||
| **RawKey** | Low-level key event monitoring and logging | `CxFilter`, `CxCustom` |
|
||||
|
||||
### Integration Patterns
|
||||
|
||||
**Pattern A: Hotkey → ARexx → Application**
|
||||
```
|
||||
Commodity: HotKey("ctrl alt r", port, CMD_RUN)
|
||||
On event: Send ARexx command to application's port
|
||||
Application: Receives command and performs action
|
||||
Result: Global hotkey controls a running application
|
||||
```
|
||||
|
||||
**Pattern B: Idle Detector → Signal → Timer**
|
||||
```
|
||||
Commodity: CxFilter("") + CxSignal(task, idleBit)
|
||||
Application: Wait(idleBit | timerBit)
|
||||
On idleBit: Reset timer
|
||||
On timer timeout: Blank screen / start screensaver
|
||||
```
|
||||
|
||||
**Pattern C: Input Logger → Custom → Disk**
|
||||
```
|
||||
Commodity: CxFilter("") + CxCustom(LogInput, 0)
|
||||
Custom function: Writes InputEvent details to log file
|
||||
Use case: Debugging input routing, accessibility tools
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Why does my commodity work alone but break when FKey is also running?**
|
||||
> Check if both commodities consume the same events. If your `CxTranslate` or `CxCustom` deletes the `CxMsg`, the event never reaches other brokers at the same priority. Use non-destructive monitoring unless you genuinely need to block the event.
|
||||
|
||||
**Q: Can a commodity generate synthetic input events?**
|
||||
> Yes, but not directly through the Commodities API. Use `input.device` with `IND_WRITEEVENT` to inject events into the stream. Some commodities use this for macro playback.
|
||||
|
||||
**Q: How do I debug a commodity that isn't triggering?**
|
||||
> 1. Verify `ActivateCxObj(broker, TRUE)` was called. 2. Check that your filter string syntax is valid. 3. Temporarily replace your filter with `""` to match everything. 4. Add a `CxDebug()` object to trace events to the serial port.
|
||||
|
||||
**Q: Can I change a commodity's hotkey at runtime?**
|
||||
> Not dynamically. You must delete the filter object and create a new one with `CxFilter()` or `HotKey()`, then reattach it to the broker. Some commodities destroy and rebuild their entire CxObject tree on configuration changes.
|
||||
|
||||
**Q: What's the difference between `CxSender` and `CxSignal`?**
|
||||
> `CxSender` sends a `CxMsg` to a `MsgPort` (queued, can carry data). `CxSignal` sends a signal to a task (fast, no queue, just a bit flip). Use `CxSender` when you need event details; use `CxSignal` for simple wakeups.
|
||||
|
||||
**Q: Is Commodities Exchange available on OS 1.3?**
|
||||
> No. It requires OS 2.0 (V37) or later. On 1.3, raw `input.device` handlers are the only option for global input monitoring.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK 3.9: `libraries/commodities.h`, `devices/inputevent.h`
|
||||
- ADCD 2.1: `commodities.library` autodocs — `CxBroker`, `CxFilter`, `HotKey`, `CxSender`, `CxTranslate`, `CxCustom`, `CxSignal`
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* — Chapter 23: Commodities Exchange
|
||||
- See also: [input_events.md](input_events.md) — Input handler chain, latency analysis, and raw input handling
|
||||
- See also: [boopsi.md](boopsi.md) — BOOPSI object system (Exchange UI uses GadTools/BOOPSI)
|
||||
- See also: [rexxsyslib.md](../11_libraries/rexxsyslib.md) — ARexx scripting for commodity-to-application communication
|
||||
- See also: [tasks_processes.md](../06_exec_os/tasks_processes.md) — Signal handling and task structure used by `CxSignal`
|
||||
|
|
@ -456,7 +456,7 @@ MUIA_Window_ID, MAKE_ID('M','A','I','N'),
|
|||
|---|---|
|
||||
| **External dependency** | `muimaster.library` must be installed (not in ROM) — ~300 KB |
|
||||
| **Memory overhead** | MUI objects consume more RAM than raw GadTools gadgets (~2–5× per widget) |
|
||||
| **Startup time** | Loading and initialising the MUI class tree adds noticeable delay on 68000 |
|
||||
| **Startup time** | Loading and initializing the MUI class tree adds noticeable delay on 68000 |
|
||||
| **Learning curve** | OOP concepts in C (dispatchers, TagItems, method IDs) are unfamiliar to many |
|
||||
| **Complex debugging** | Object tree issues (wrong parent, missing End) cause cryptic crashes |
|
||||
| **Shareware stigma** | Early MUI was shareware with nag screens — some users avoided it |
|
||||
|
|
@ -693,7 +693,7 @@ End,
|
|||
|
||||
### Group Types
|
||||
|
||||
| Type | Macro | Behaviour |
|
||||
| Type | Macro | Behavior |
|
||||
|---|---|---|
|
||||
| Vertical | `VGroup` | Stack children top-to-bottom |
|
||||
| Horizontal | `HGroup` | Arrange children left-to-right |
|
||||
|
|
@ -859,7 +859,7 @@ IPTR MySlider_Dispatcher(struct IClass *cl, Object *obj, Msg msg)
|
|||
switch (msg->MethodID) {
|
||||
case OM_NEW:
|
||||
{
|
||||
/* Initialise object */
|
||||
/* Initialize object */
|
||||
obj = (Object *)DoSuperMethodA(cl, obj, msg);
|
||||
if (obj) {
|
||||
struct MySliderData *data = INST_DATA(cl, obj);
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ Amiga devices are shared libraries with an exec I/O request interface. They prov
|
|||
| [keyboard.md](keyboard.md) | CIA-A serial handshake, raw key codes, key matrix, reset sequence, FPGA protocol notes |
|
||||
| [gameport.md](gameport.md) | Joystick/mouse port: quadrature decoding, XOR state, fire buttons, controller types |
|
||||
| [input.md](input.md) | Input handler chain: priority dispatch, event classes, key remapping, Commodities Exchange |
|
||||
| [console.md](console.md) | Text terminal I/O: ANSI escape sequences, cursor/colour control, raw key events, CON:/RAW: handlers |
|
||||
| [console.md](console.md) | Text terminal I/O: ANSI escape sequences, cursor/color control, raw key events, CON:/RAW: handlers |
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
`console.device` provides an ANSI-compatible text terminal within Intuition windows. It handles:
|
||||
- **Input**: translating raw keycodes from `input.device` into ASCII/ANSI characters
|
||||
- **Output**: rendering text, parsing escape sequences for cursor control, colour, and formatting
|
||||
- **Output**: rendering text, parsing escape sequences for cursor control, color, and formatting
|
||||
- **Clipboard**: cut/copy/paste integration
|
||||
|
||||
Every CLI/Shell window is backed by a console.device unit. Applications can open their own console units in any Intuition window for text I/O without implementing their own keyboard translation or cursor rendering.
|
||||
|
|
@ -68,7 +68,7 @@ void ConPuts(struct IOStdReq *con, char *str)
|
|||
/* Usage: */
|
||||
ConPuts(con, "Hello, Amiga!\n");
|
||||
ConPuts(con, "\033[1mBold text\033[0m\n"); /* bold on/off */
|
||||
ConPuts(con, "\033[33mYellow text\033[0m\n"); /* colour */
|
||||
ConPuts(con, "\033[33mYellow text\033[0m\n"); /* color */
|
||||
ConPuts(con, "\033[10;20HText at row 10 col 20"); /* absolute position */
|
||||
```
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ Console.device supports a rich subset of ANSI/VT100 escape sequences (CSI = `\03
|
|||
| `\033[23m` | Cancel italic |
|
||||
| `\033[24m` | Cancel underline |
|
||||
|
||||
### Colours
|
||||
### Colors
|
||||
|
||||
| Sequence | Foreground | Background |
|
||||
|---|---|---|
|
||||
|
|
@ -163,7 +163,7 @@ Console.device supports a rich subset of ANSI/VT100 escape sequences (CSI = `\03
|
|||
| `\033[39m` / `\033[49m` | Default | Default |
|
||||
|
||||
> [!NOTE]
|
||||
> Colour indices map to the **Intuition pen palette** of the window's screen, not absolute colours. Pen 0 = background, pen 1 = foreground by default.
|
||||
> Color indices map to the **Intuition pen palette** of the window's screen, not absolute colors. Pen 0 = background, pen 1 = foreground by default.
|
||||
|
||||
### Amiga-Specific Extensions
|
||||
|
||||
|
|
|
|||
|
|
@ -42,14 +42,14 @@ flowchart LR
|
|||
| **16-bit bus** | Gayle connects via 16-bit data path even on 32-bit CPUs |
|
||||
| **PIO mode 0** | Stock Gayle supports only PIO mode 0 (~3.3 MB/s theoretical, ~1.5 MB/s actual) |
|
||||
| **CIA timing** | CIA chip access introduces wait states |
|
||||
| **CPU overhead** | 100% CPU utilisation during transfers — no multitasking during disk I/O |
|
||||
| **CPU overhead** | 100% CPU utilization during transfers — no multitasking during disk I/O |
|
||||
|
||||
### Community Solutions — Fast IDE
|
||||
|
||||
| Solution | Method | Improvement |
|
||||
|---|---|---|
|
||||
| **FastATA** (E-Matrix/Elbox) | Zorro SCSI/IDE card with DMA | Up to ~10 MB/s, frees CPU |
|
||||
| **Buddha/Catweasel** | Clock port / Zorro IDE with optimised driver | ~2–3 MB/s, multiple IDE channels |
|
||||
| **Buddha/Catweasel** | Clock port / Zorro IDE with optimized driver | ~2–3 MB/s, multiple IDE channels |
|
||||
| **Blizzard SCSI** | Accelerator-integrated SCSI | Up to ~5 MB/s with DMA |
|
||||
| **GVP Series II** | Zorro II SCSI with custom DMA ASIC | ~3 MB/s, DMA frees CPU |
|
||||
| **A4091** | Zorro III SCSI (NCR 53C710) | **~10 MB/s** — fastest stock Amiga SCSI |
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ The **E-clock** is derived from the system clock ÷ 10:
|
|||
|
||||
### VBlank Timing
|
||||
|
||||
UNIT_VBLANK is synchronised to the display's vertical blank interrupt:
|
||||
UNIT_VBLANK is synchronized to the display's vertical blank interrupt:
|
||||
|
||||
| Standard | VBlank Rate | Period | Use |
|
||||
|---|---|---|---|
|
||||
|
|
@ -155,7 +155,7 @@ struct EClockVal { /* OS 2.0+ */
|
|||
|
||||
---
|
||||
|
||||
## Proper Initialisation and Shutdown
|
||||
## Proper Initialization and Shutdown
|
||||
|
||||
timer.device uses the standard Exec I/O model: a **MsgPort** for signal delivery, an **IORequest** to describe the operation, and an explicit **open/close** lifecycle. Every step matters — skipping any one causes subtle or catastrophic failures.
|
||||
|
||||
|
|
@ -191,8 +191,8 @@ struct Library *TimerBase = (struct Library *)tr->tr_node.io_Device;
|
|||
|---|---|
|
||||
| Skip `CreateMsgPort` | No signal bit allocated → `Wait()` will never wake up. Or worse: signal bit 0 (CTRL-C) gets reused, causing random task termination. |
|
||||
| Skip error check on `CreateIORequest` | NULL IORequest passed to `OpenDevice` → immediate crash (NULL pointer dereference in Exec). |
|
||||
| Skip `OpenDevice` error check | If device can't open (e.g. wrong unit), the IORequest is uninitialised — any subsequent `DoIO`/`SendIO` writes to random memory → Guru Meditation. |
|
||||
| Use `AllocMem` instead of `CreateIORequest` | IORequest fields (`io_Message.mn_ReplyPort`, `io_Message.mn_Length`) are not initialised → device replies to garbage address → memory corruption. |
|
||||
| Skip `OpenDevice` error check | If device can't open (e.g. wrong unit), the IORequest is uninitialized — any subsequent `DoIO`/`SendIO` writes to random memory → Guru Meditation. |
|
||||
| Use `AllocMem` instead of `CreateIORequest` | IORequest fields (`io_Message.mn_ReplyPort`, `io_Message.mn_Length`) are not initialized → device replies to garbage address → memory corruption. |
|
||||
| Not saving `TimerBase` | Can't call `AddTime`/`SubTime`/`CmpTime`/`ReadEClock` — they require the device's library base in A6. |
|
||||
|
||||
### Shutdown (The Right Way)
|
||||
|
|
@ -311,7 +311,7 @@ if (timerPending)
|
|||
## Use Case 3: Game/Demo Frame Sync (Periodic Timer)
|
||||
|
||||
```c
|
||||
/* 50 Hz game loop synchronised to PAL frame rate: */
|
||||
/* 50 Hz game loop synchronized to PAL frame rate: */
|
||||
#define FRAME_USEC 20000 /* 1/50th second = 20ms */
|
||||
|
||||
void GameLoop(void)
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ ULONG changeCount = diskReq->iotd_Req.io_Actual;
|
|||
diskReq->iotd_Req.io_Command = TD_ADDCHANGEINT;
|
||||
diskReq->iotd_Req.io_Data = (APTR)&myInterrupt;
|
||||
SendIO((struct IORequest *)diskReq);
|
||||
/* myInterrupt is signalled on disk change */
|
||||
/* myInterrupt is signaled on disk change */
|
||||
```
|
||||
|
||||
### Track Caching
|
||||
|
|
@ -141,7 +141,7 @@ Read sector 11 → new track → DMA reads track 1
|
|||
Read sector 5 → cache hit (still in track 0 buffer)
|
||||
```
|
||||
|
||||
> **FPGA implication**: the MiSTer core must emulate this whole-track DMA behaviour for correct timing. Games that measure seek+read latency will behave incorrectly if only single sectors are transferred.
|
||||
> **FPGA implication**: the MiSTer core must emulate this whole-track DMA behavior for correct timing. Games that measure seek+read latency will behave incorrectly if only single sectors are transferred.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,15 @@ Shared libraries beyond the core exec/dos/graphics/intuition subsystems. These p
|
|||
|---|---|
|
||||
| [utility.md](utility.md) | TagItem lists with chaining (TAG_MORE), callback hooks (register convention), date/time utilities, tag iteration patterns |
|
||||
| [expansion.md](expansion.md) | Zorro II/III bus architecture, AutoConfig ROM layout, board enumeration, FPGA implementation notes |
|
||||
| [icon.md](icon.md) | Workbench icons (.info): DiskObject structure, ToolType parsing, icon types, OS 3.5+ true-colour icons |
|
||||
| [icon.md](icon.md) | Workbench icons (.info): DiskObject structure, ToolType parsing, icon types, OS 3.5+ true-color icons |
|
||||
| [workbench.md](workbench.md) | Workbench integration: WBStartup handling, AppWindow drag-and-drop, AppIcon, AppMenuItem |
|
||||
| [iffparse.md](iffparse.md) | IFF file parsing: ILBM/8SVX/ANIM, BitMapHeader, ByteRun1 compression, clipboard integration |
|
||||
| [locale.md](locale.md) | Internationalisation: catalogue system (.cd/.ct files), locale-aware date/number formatting, character classification |
|
||||
| [iffparse.md](iffparse.md) | IFF file parsing: ILBM/8SVX/ANIM, nested chunk hierarchy, ByteRun1 compression, PBM planar deinterleaving, clipboard integration, decision guide vs DataTypes |
|
||||
| [locale.md](locale.md) | Internationalization: catalog system (.cd/.ct files), locale-aware date/number formatting, character classification |
|
||||
| [keymap.md](keymap.md) | Keyboard mapping: raw-to-ASCII translation, KeyMap structure, dead keys, rawkey codes, national layouts |
|
||||
| [rexxsyslib.md](rexxsyslib.md) | ARexx scripting: hosting ARexx ports, command parsing, sending commands, return codes |
|
||||
| [mathffp.md](mathffp.md) | Motorola FFP and IEEE 754 floating point |
|
||||
| [layers.md](layers.md) | Window clipping: ClipRect engine, Simple/Smart/Super refresh, damage repair, backfill hooks, layer locking |
|
||||
| [diskfont.md](diskfont.md) | Disk-based fonts: FONTS: directory structure, AvailFonts enumeration, colour fonts (OS 3.0+) |
|
||||
| [diskfont.md](diskfont.md) | Disk-based fonts: FONTS: directory structure, AvailFonts enumeration, color fonts (OS 3.0+) |
|
||||
| [datatypes.md](datatypes.md) | DataTypes system: object-oriented file loading for images, sound, text, animation via BOOPSI classes |
|
||||
| [amigaguide.md](amigaguide.md) | AmigaGuide hypertext help system: database format, @commands, API, ARexx integration, cross-database linking |
|
||||
| [translator.md](translator.md) | translator.library: English-to-phonetic translation for speech synthesis, narrator.device integration, ARPABET phonemes |
|
||||
|
|
|
|||
760
11_libraries/amigaguide.md
Normal file
760
11_libraries/amigaguide.md
Normal file
|
|
@ -0,0 +1,760 @@
|
|||
[← Home](../README.md) · [Libraries](README.md)
|
||||
|
||||
# AmigaGuide — Hypertext Help System and Database Format
|
||||
|
||||
## Overview
|
||||
|
||||
AmigaGuide is AmigaOS's native hypertext document format, introduced in 1991 and elevated to a system component with OS 3.0. An AmigaGuide database is a plain ASCII file annotated with `@` commands that define nodes, links, text attributes, and executable actions.
|
||||
|
||||
The `amigaguide.library` provides both a standalone viewer and a programmatic API, allowing applications to open help databases synchronously or asynchronously, send navigation commands via an ARexx port, and even generate content dynamically at runtime through **dynamic nodes**. Because AmigaGuide is also implemented as a DataType class (`amigaguideclass`), any application using the DataTypes framework — including MultiView — can display `.guide` files without additional code. For developers, this means a single help file format serves both standalone documentation and in-application context-sensitive help, with cross-database linking, embedded images (via DataTypes), and ARexx-driven interactivity built in.
|
||||
|
||||
> **Key constraint**: AmigaGuide is text-centric. While it can launch images, animations, and audio through DataType links, the document itself is linear text — there is no concept of a 2D page layout, embedded widgets, or cascading stylesheets.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### System Components
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Application"
|
||||
APP["Application\nHelp menu / F1 key"]
|
||||
SYNC["OpenAmigaGuide()\nSynchronous viewer"]
|
||||
ASYNC["OpenAmigaGuideAsync()\nAsync + ARexx control"]
|
||||
end
|
||||
|
||||
subgraph "amigaguide.library"
|
||||
LIB["amigaguide.library\nParser + Window manager"]
|
||||
DNODE["Dynamic Node Host\n(optional callback)"]
|
||||
end
|
||||
|
||||
subgraph "DataTypes"
|
||||
DT["amigaguideclass\nRenders .guide in MultiView"]
|
||||
PIC["pictureclass\nEmbedded images"]
|
||||
ANI["animationclass\nEmbedded animations"]
|
||||
end
|
||||
|
||||
subgraph "External"
|
||||
AREXX["ARexx port\nRemote control / callbacks"]
|
||||
SHELL["Shell\nSYSTEM links"]
|
||||
end
|
||||
|
||||
APP --> SYNC --> LIB
|
||||
APP --> ASYNC --> LIB
|
||||
LIB --> DT
|
||||
LIB --> DNODE
|
||||
DT --> PIC
|
||||
DT --> ANI
|
||||
LIB --> AREXX
|
||||
LIB --> SHELL
|
||||
|
||||
style LIB fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style DT fill:#e8f5e9,stroke:#4caf50,color:#333
|
||||
```
|
||||
|
||||
### File → Nodes → Links
|
||||
|
||||
An AmigaGuide database is a flat file divided into **nodes**. Each node is a self-contained document fragment. Navigation between nodes happens through **links**, which are rendered as clickable buttons in the viewer.
|
||||
|
||||
```
|
||||
@DATABASE MyApp.guide
|
||||
@INDEX Main
|
||||
|
||||
@NODE Main "MyApp Help"
|
||||
Welcome to MyApp.
|
||||
|
||||
@{"Getting Started" LINK GettingStarted}
|
||||
@{"Reference" LINK Reference}
|
||||
@EndNode
|
||||
|
||||
@NODE GettingStarted "Getting Started"
|
||||
1. Install the software.
|
||||
2. Run from Workbench.
|
||||
|
||||
@{"Back to Main" LINK Main}
|
||||
@EndNode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Format Reference
|
||||
|
||||
### Database-Level Commands (Global)
|
||||
|
||||
These commands apply to the entire database. Most must appear at the start of the file, before the first `@NODE`.
|
||||
|
||||
| Command | Description | Required |
|
||||
|---|---|---|
|
||||
| `@DATABASE <name>` | Identifies this file as an AmigaGuide database. Must be the **first line**. | Yes |
|
||||
| `@INDEX <node>` | Specifies the node used for the **Index** button. Defaults to MAIN if omitted. | No |
|
||||
| `@HELP <node>` | Specifies the node used for the **Help** button. | No |
|
||||
| `@WIDTH <chars>` | Declares the maximum width of any node (viewer hint). | No |
|
||||
| `@HEIGHT <rows>` | Declares the maximum height of any node. | No |
|
||||
| `@FONT <name> <size>` | Default font for the database. | No |
|
||||
| `@WORDWRAP` | Enables automatic word wrapping (V39). | No |
|
||||
| `@SMARTWRAP` | Smarter wrapping: paragraphs separated by blank lines (V40). | No |
|
||||
| `@TAB <spaces>` | Tab stop width (default 8) (V40). | No |
|
||||
| `@AUTHOR <name>` | Author metadata. | No |
|
||||
| `@(C) <text>` | Copyright notice. | No |
|
||||
| `$VER: <version>` | Standard AmigaDOS version string. | No |
|
||||
| `@ONOPEN <rexx>` | ARexx command to execute when the database opens (V40). | No |
|
||||
| `@ONCLOSE <rexx>` | ARexx command to execute when the database closes (V40). | No |
|
||||
|
||||
### Node-Level Commands
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `@NODE <name> "<title>"` | Starts a new node. `<name>` must be unique, no spaces. `<title>` appears in the window title bar. |
|
||||
| `@ENDNODE` | Ends the current node. |
|
||||
| `@TITLE "<title>"` | Overrides the window title for this node (alternative to `@NODE`'s title argument). |
|
||||
| `@TOC <node>` | Sets the **Contents** button target for this node. Defaults to MAIN. |
|
||||
| `@PREV <node>` | Sets the **Browse <** target. Defaults to previous node in file. |
|
||||
| `@NEXT <node>` | Sets the **Browse >** target. Defaults to next node in file. |
|
||||
| `@REMARK <text>` | Comment — not displayed. |
|
||||
|
||||
### Link Syntax
|
||||
|
||||
Links are the core hypertext mechanism. They can appear anywhere on a line and are rendered as buttons.
|
||||
|
||||
```
|
||||
@{"<label>" <action> <arguments>}
|
||||
```
|
||||
|
||||
#### Action Commands
|
||||
|
||||
| Action | Syntax | Description |
|
||||
|---|---|---|
|
||||
| `LINK` | `@{"Label" LINK TargetNode}` | Jump to another node in this database. |
|
||||
| `LINK` (cross-db) | `@{"Label" LINK Other.guide/NodeName}` | Jump to a node in another database. |
|
||||
| `RX` | `@{"Label" RX 'ADDRESS MYPORT MyCommand'}` | Execute an ARexx command. |
|
||||
| `RXS` | `@{"Label" RXS MyScript.rexx}` | Run an ARexx script file. |
|
||||
| `SYSTEM` | `@{"Label" SYSTEM 'dir >ram:output'}` | Execute a Shell command. |
|
||||
| `BEEP` | `@{"Label" BEEP}` | Play the system beep. |
|
||||
| `QUIT` | `@{"Label" QUIT}` | Close the AmigaGuide viewer. |
|
||||
|
||||
> **Cross-database paths**: The path can be any AmigaDOS path, including assigns. If no path is given, AmigaGuide searches `ENV:AmigaGuide/Path`. As of OS 3.0, you can also link to any DataTypes-supported file: `@{"Picture" LINK image.iff/Main}`.
|
||||
|
||||
### Text Attributes
|
||||
|
||||
Attributes wrap text in `@{name ...}` and must be terminated with `@{uname}` (undo attribute).
|
||||
|
||||
| Attribute | Description | Example |
|
||||
|---|---|---|
|
||||
| `@{b}` / `@{ub}` | Bold on / off | `@{b}bold text@{ub}` |
|
||||
| `@{i}` / `@{ui}` | Italic on / off | `@{i}italic@{ui}` |
|
||||
| `@{u}` / `@{uu}` | Underline on / off | `@{u}underlined@{uu}` |
|
||||
| `@{fg <n>}` / `@{ufg}` | Foreground pen color | `@{fg 2}red text@{ufg}` |
|
||||
| `@{bg <n>}` / `@{ubg}` | Background pen color | `@{bg 1}highlighted@{ubg}` |
|
||||
| `@{jleft}` | Left justify (default) | |
|
||||
| `@{jright}` | Right justify | |
|
||||
| `@{jcenter}` | Center justify | |
|
||||
| `@{amigaguide}` | Embed an AmigaGuide glyph | |
|
||||
| `@{clear}` | Clear to end of line | |
|
||||
|
||||
### Macros (V40)
|
||||
|
||||
Macros let you define reusable attribute sequences:
|
||||
|
||||
```
|
||||
@MACRO warning "@{b}WARNING:@{ub} $1"
|
||||
|
||||
Later in text:
|
||||
This is a @{"critical issue" warning "Do not power off during format."}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Structures
|
||||
|
||||
### NewAmigaGuide
|
||||
|
||||
```c
|
||||
/* libraries/amigaguide.h — NDK 3.9 */
|
||||
struct NewAmigaGuide {
|
||||
BPTR nag_Lock; /* Lock on database directory */
|
||||
STRPTR nag_Name; /* Database file name */
|
||||
struct Screen * nag_Screen; /* Screen to open on, or NULL */
|
||||
STRPTR nag_PubScreen; /* Name of public screen */
|
||||
STRPTR nag_HostPort; /* App's ARexx port (unused) */
|
||||
STRPTR nag_ClientPort; /* Base name for DB's ARexx port */
|
||||
ULONG nag_Flags; /* NAGF_* flags */
|
||||
STRPTR * nag_Context; /* NULL-terminated context array */
|
||||
STRPTR nag_Node; /* Starting node name */
|
||||
LONG nag_Line; /* Starting line number */
|
||||
struct TagItem * nag_Extens; /* Additional tags (V37+) */
|
||||
APTR nag_Client; /* Private — must be NULL */
|
||||
};
|
||||
```
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Value | Meaning |
|
||||
|---|---|---|
|
||||
| `NAGF_LOCK` | `0x0001` | `nag_Lock` is valid |
|
||||
| `NAGF_CLOSE` | `0x0002` | Close the database file when done |
|
||||
| `NAGF_NOTIFY` | `0x0004` | Enable ARexx notification |
|
||||
| `NAGF_HOSTPORT` | `0x0008` | `nag_HostPort` is valid |
|
||||
| `NAGF_CONTEXT` | `0x0010` | `nag_Context` is valid |
|
||||
| `NAGF_UNIQUE` | `0x0020` | Create unique ARexx port name |
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Synchronous Viewer
|
||||
|
||||
```c
|
||||
#include <libraries/amigaguide.h>
|
||||
#include <proto/amigaguide.h>
|
||||
|
||||
/* Open a modal AmigaGuide viewer. Blocks until user closes all windows. */
|
||||
AMIGAGUIDECONTEXT OpenAmigaGuideA(struct NewAmigaGuide *nag,
|
||||
struct TagItem *attrs);
|
||||
AMIGAGUIDECONTEXT OpenAmigaGuide(struct NewAmigaGuide *nag,
|
||||
Tag tag1, ...);
|
||||
|
||||
/* Close the viewer and free resources */
|
||||
void CloseAmigaGuide(AMIGAGUIDECONTEXT handle);
|
||||
```
|
||||
|
||||
**Common tags for `OpenAmigaGuide()`:**
|
||||
|
||||
| Tag | Description |
|
||||
|---|---|
|
||||
| `AGA_HelpGroup` | Unique ID for help window grouping (V39) |
|
||||
|
||||
### Asynchronous Viewer
|
||||
|
||||
```c
|
||||
/* Open a non-modal viewer. Returns immediately. */
|
||||
AMIGAGUIDECONTEXT OpenAmigaGuideAsyncA(struct NewAmigaGuide *nag,
|
||||
struct TagItem *attrs);
|
||||
|
||||
/* Send a command to an async AmigaGuide instance */
|
||||
LONG SendAmigaGuideCmdA(AMIGAGUIDECONTEXT handle,
|
||||
STRPTR cmd,
|
||||
struct TagItem *attrs);
|
||||
|
||||
/* Send a context-sensitive help request */
|
||||
LONG SendAmigaGuideContextA(AMIGAGUIDECONTEXT handle,
|
||||
struct TagItem *attrs);
|
||||
```
|
||||
|
||||
### Navigation Commands (SendAmigaGuideCmd)
|
||||
|
||||
Commands are sent as strings to the async viewer:
|
||||
|
||||
| Command | Effect |
|
||||
|---|---|
|
||||
| `"BUTTON Contents"` | Click the Contents button |
|
||||
| `"BUTTON Index"` | Click the Index button |
|
||||
| `"BUTTON Retrace"` | Click Retrace |
|
||||
| `"BUTTON Browse <"` | Browse previous |
|
||||
| `"BUTTON Browse >"` | Browse next |
|
||||
| `"BUTTON Help"` | Click Help |
|
||||
| `"NODE <name>"` | Jump to a specific node |
|
||||
| `"QUIT"` | Close the viewer |
|
||||
|
||||
---
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Example 1: Minimal Help Database
|
||||
|
||||
```
|
||||
@database MyApp.guide
|
||||
|
||||
@NODE Main "MyApp Help"
|
||||
@{b}MyApp v1.0 Help@{ub}
|
||||
|
||||
Welcome to MyApp. Choose a topic:
|
||||
|
||||
@{"Quick Start" LINK QuickStart}
|
||||
@{"Menu Reference" LINK MenuRef}
|
||||
@{"Troubleshooting" LINK Troubleshoot}
|
||||
@EndNode
|
||||
|
||||
@NODE QuickStart "Quick Start"
|
||||
1. Double-click the MyApp icon.
|
||||
2. Select @{"New Project" LINK NewProject} from the Project menu.
|
||||
3. Save often with @{"Save" SYSTEM "echo Save reminder >CON:0/0/400/50/MyApp"}.
|
||||
|
||||
@{"Back to Main" LINK Main}
|
||||
@EndNode
|
||||
|
||||
@NODE MenuRef "Menu Reference"
|
||||
| Menu | Item | Action |
|
||||
| Project | New | Creates a new document |
|
||||
| Project | Open | Opens an existing document |
|
||||
| Project | Save | Saves the current document |
|
||||
|
||||
@{"Back to Main" LINK Main}
|
||||
@EndNode
|
||||
|
||||
@NODE Troubleshoot "Troubleshooting"
|
||||
@{b}Common Problems@{ub}
|
||||
|
||||
@{b}Problem:@{ub} App crashes on startup.
|
||||
@{b}Solution:@{ub} Ensure @{"MYAPP: assign" SYSTEM "assign >NIL:"} exists.
|
||||
|
||||
@{"Back to Main" LINK Main}
|
||||
@EndNode
|
||||
```
|
||||
|
||||
### Example 2: Open Help from an Application
|
||||
|
||||
```c
|
||||
#include <exec/types.h>
|
||||
#include <libraries/amigaguide.h>
|
||||
#include <proto/amigaguide.h>
|
||||
#include <proto/dos.h>
|
||||
|
||||
void ShowHelp(CONST_STRPTR dbPath, CONST_STRPTR nodeName)
|
||||
{
|
||||
struct NewAmigaGuide nag = {0};
|
||||
|
||||
nag.nag_Name = (STRPTR)dbPath;
|
||||
nag.nag_Node = (STRPTR)nodeName;
|
||||
nag.nag_Flags = NAGF_CLOSE;
|
||||
|
||||
AMIGAGUIDECONTEXT ctx = OpenAmigaGuide(&nag, TAG_DONE);
|
||||
if (ctx)
|
||||
{
|
||||
/* Synchronous: blocks here until user closes viewer */
|
||||
CloseAmigaGuide(ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
LONG err = IoErr();
|
||||
Printf("Failed to open help: %ld\n", err);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Async Help with ARexx Control
|
||||
|
||||
```c
|
||||
#include <libraries/amigaguide.h>
|
||||
#include <proto/amigaguide.h>
|
||||
|
||||
struct NewAmigaGuide nag = {0};
|
||||
nag.nag_Name = "MyApp.guide";
|
||||
nag.nag_Node = "Main";
|
||||
nag.nag_Flags = NAGF_CLOSE | NAGF_NOTIFY;
|
||||
|
||||
AMIGAGUIDECONTEXT ctx = OpenAmigaGuideAsyncA(&nag, NULL);
|
||||
if (ctx)
|
||||
{
|
||||
/* Application continues running... */
|
||||
|
||||
/* Later: programmatically navigate to a node */
|
||||
SendAmigaGuideCmdA(ctx, "NODE Troubleshoot", NULL);
|
||||
|
||||
/* Later: close help from code */
|
||||
SendAmigaGuideCmdA(ctx, "QUIT", NULL);
|
||||
|
||||
CloseAmigaGuide(ctx);
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Context-Sensitive Help (F1 Key)
|
||||
|
||||
```c
|
||||
/* In your IDCMP event loop: */
|
||||
case IDCMP_RAWKEY:
|
||||
if (code == 0x5B) /* Help key scancode */
|
||||
{
|
||||
/* Determine which gadget is under the mouse */
|
||||
UWORD gadID = GetGadgetIDUnderMouse(win);
|
||||
|
||||
CONST_STRPTR node = "Main";
|
||||
switch (gadID)
|
||||
{
|
||||
case GAD_OPEN: node = "FileOpen"; break;
|
||||
case GAD_SAVE: node = "FileSave"; break;
|
||||
case GAD_PREFS: node = "Preferences"; break;
|
||||
}
|
||||
|
||||
ShowHelp("PROGDIR:MyApp.guide", node);
|
||||
}
|
||||
break;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decision Guide
|
||||
|
||||
| Criterion | AmigaGuide | Raw Text + More | IFF-FTXT |
|
||||
|---|---|---|---|
|
||||
| **When to use** | Application help; cross-linked docs; need buttons and ARexx | Simple one-page docs; minimal dependencies | Structured text with formatting but no interactivity |
|
||||
| **Interactivity** | Links, ARexx, Shell commands | None | None |
|
||||
| **Images/audio** | Via DataType links (V39+) | None | None |
|
||||
| **Cross-database links** | Yes — `@{"Label" LINK other.guide/node}` | No | No |
|
||||
| **Viewer required** | amigaguide.library or MultiView | Any text viewer | Any IFF text viewer |
|
||||
| **OS version** | OS 2.0+ (full features: 3.0+) | Works everywhere | OS 1.3+ |
|
||||
| **Embedding in app UI** | Can open standalone only; not embeddable as gadget | Display in custom read-only string gadget | Display in custom gadget |
|
||||
|
||||
---
|
||||
|
||||
## Historical Context & Modern Analogies
|
||||
|
||||
### The Elegance of Executable Documentation
|
||||
|
||||
AmigaGuide was not merely a hypertext format — it was a **programmable application extension mechanism** disguised as a help file. A developer could ship a `.guide` file that, when the user clicked a link, sent an ARexx command back to the running application, executed a shell script to repair a configuration, or opened an IFF image through the DataTypes system. The help file was not static documentation; it was an interactive partner to the application.
|
||||
|
||||
This design philosophy — **documentation as code** — would not resurface in mainstream computing until the rise of literate programming tools, Jupyter notebooks, and interactive web documentation in the 2010s. In 1991, it was genuinely unique.
|
||||
|
||||
Consider what a single AmigaGuide database could do without the host application knowing anything about its internal structure:
|
||||
|
||||
1. **Navigate internally** — `@NODE` and `@LINK` provide structured hypertext
|
||||
2. **Control the host application** — `RX` links send ARexx commands to the app's port
|
||||
3. **Modify the system** — `SYSTEM` links execute shell commands
|
||||
4. **Display rich media** — cross-links to any DataType-supported image, sound, or animation
|
||||
5. **Form a documentation graph** — cross-database `@{"Other" LINK lib.guide/Main}` creates a web of help files
|
||||
|
||||
The host application only needed to call `OpenAmigaGuideAsyncA()` with a file path. Everything else — rendering, navigation, interactivity, media embedding — was handled by `amigaguide.library` and the DataTypes framework.
|
||||
|
||||
### The 1991 Competitive Landscape
|
||||
|
||||
| Platform (1991) | System | Hypertext | Scripting | External Linking | App Control | System Integration |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **AmigaOS** | **AmigaGuide** | Nodes | ARexx (RX/RXS) | Cross-db `.guide` | Yes — ARexx ports | DataTypes, Shell |
|
||||
| **Mac OS** | **HyperCard** (1987) | Visual stacks | HyperTalk | Stack-to-stack | Limited — AppleEvents | Clipboard, File |
|
||||
| **Windows 3.0** | **WinHelp** (`.hlp`) | Popups, macros | Limited macro lang | No | No | ShellExecute |
|
||||
| **NeXTSTEP** | **Help** + **Digital Librarian** | Rich text | No | No | No | Search index |
|
||||
| **UNIX** | **Texinfo** / **man -k`** | Cross-references | No | Info nodes only | No | man path |
|
||||
| **VMS** | **Bookreader** | Structured docs | No | No | No | DEC-specific |
|
||||
|
||||
The critical differentiator is the **ARexx bidirectional control**. HyperCard stacks could script the Macintosh (via AppleEvents, later), but the help file could not easily send a command to a *specific running application* and expect a response. WinHelp macros were confined to the help viewer itself. AmigaGuide's `RX` links could address any ARexx port on the system — including the application that opened the help file — creating a true conversational relationship between documentation and software.
|
||||
|
||||
### How It Worked in Practice: The MUI Developer Reference
|
||||
|
||||
The **MUI (Magic User Interface)** developer reference — `MUIdev.guide` — is perhaps the finest example of AmigaGuide's power. It was not a manual; it was an ecosystem:
|
||||
|
||||
| Feature | Traditional Manual | MUIdev.guide |
|
||||
|---|---|---|
|
||||
| Class reference | Static tables | `@NODE` per class with `@TOC` navigation |
|
||||
| Example code | Embedded in text | `SYSTEM` links to run example scripts |
|
||||
| Cross-references | Page numbers | `@LINK` to other `.guide` databases |
|
||||
| Method parameters | Text description | ARexx-driven parameter exploration (in advanced setups) |
|
||||
| Updates | Re-print or new edition | Drop-in replacement `.guide` file |
|
||||
|
||||
A developer reading about `Listview.mui` could click a link to jump to the `List` class reference in the same file, click another to open the `Popobject.mui` reference in a separate database, and click a third to execute an ARexx script that created a live example window — all without leaving the help viewer.
|
||||
|
||||
### Modern Analogies
|
||||
|
||||
No single modern system replicates AmigaGuide's exact combination of properties, but several come close in different dimensions:
|
||||
|
||||
| AmigaGuide Concept | Modern Equivalent | Why It Maps (and Where It Diverges) |
|
||||
|---|---|---|
|
||||
| `@NODE` / modular docs | **DITA topics / DocBook `<section>`** | Modular, reusable document fragments. DITA goes further with conditional profiling (`@audience`, `@platform`); AmigaGuide has no conditional text. |
|
||||
| `@{"Label" LINK Node}` | **HTML `<a href>` + anchor** | Direct hypertext navigation. HTML is richer (CSS styling, embedded media); AmigaGuide links are constrained to button-like widgets. |
|
||||
| `RX` / `RXS` — app control from docs | **Jupyter Notebook cells** | Documentation that executes code and returns results. Jupyter is far more powerful (Python, visualization, stateful kernels), but requires a heavy runtime. AmigaGuide's ARexx was lightweight and system-wide. |
|
||||
| `SYSTEM` — shell from docs | **Markdown code fences with `bash` execution** (e.g., R Markdown, Quarto) | Document-embedded command execution. Modern tools sandbox or prompt; AmigaGuide executed with the user's full privileges. |
|
||||
| Cross-database `@LINK other.guide` | **HTML `<a href>` across files** | The web itself is the analogy — a distributed documentation graph. AmigaGuide's `ENV:AmigaGuide/Path` is a primitive `PATH` for docs. |
|
||||
| `amigaguide.library` viewer | **Qt Assistant / Apple Help Viewer / DevDocs** | Dedicated offline help viewers. Modern viewers support full-text search, indexing, and CSS; AmigaGuide had none of these but loaded instantly on 68000-class hardware. |
|
||||
| `.guide` as DataType | **HTML rendered in any WebKit view** | Universal rendering via shared framework. The key difference: a DataType object is an OS-native BOOPSI object; a WebKit view is an application-embedded widget. |
|
||||
| `@ONOPEN` / `@ONCLOSE` | **HTML `<body onload>` / JavaScript `DOMContentLoaded`** | Event-driven document lifecycle. AmigaGuide's events are limited to ARexx commands; modern web docs have full Turing-complete scripting. |
|
||||
|
||||
### What Made AmigaGuide Unique (and Unreplicated)
|
||||
|
||||
Despite the partial modern analogies above, no contemporary system in 1991 — and few today — offered this specific combination:
|
||||
|
||||
1. **Zero-compilation hypertext with system-wide reach**: HyperCard required the HyperCard runtime (a separate application). WinHelp required a compiler (`hc.exe`) to build `.hlp` from `.rtf` source. AmigaGuide files are plain ASCII — editable in any text editor, viewable in any AmigaGuide-aware application, with no build step.
|
||||
|
||||
2. **Documentation as remote control**: The `RX` link is not merely "run a script" — it is "send a message to a named port." This means the help file can query application state (`ADDRESS MyApp GETVERSION`), trigger actions (`ADDRESS MyApp DOACTION`), or chain multiple applications together. It is RPC embedded in documentation.
|
||||
|
||||
3. **Late-bound media embedding**: Because AmigaGuide links to DataTypes, a `.guide` file written in 1991 could embed a PNG image in 1996, an MP3 in 2000, or a video in 2026 — without the help file or the viewer knowing what PNG, MP3, or video formats are. The DataTypes system (see [datatypes.md](datatypes.md)) handles the indirection.
|
||||
|
||||
4. **No host application dependency**: A `.guide` file is self-sufficient. It carries its own navigation structure (`@INDEX`, `@TOC`), styling (`@MACRO`, attributes), and actions. The host application does not need to parse, render, or understand the file format — it merely hands the path to `amigaguide.library`.
|
||||
|
||||
### Where the Analogies Break Down
|
||||
|
||||
- **No compilation step**: Unlike WinHelp (`.hlp`) or modern CHM, `.guide` files are raw text — no indexing, compression, or encryption. This is simpler but slower for large documents.
|
||||
- **No full-text search**: The viewer does not index content. Navigation is entirely through pre-defined links or external tools.
|
||||
- **No conditional text**: There is no `#if`, audience profiling, or platform-specific filtering. Every user sees every node.
|
||||
- **Linear rendering**: Nodes are displayed as scrolling text — no pagination, no columns, no responsive layout, no CSS.
|
||||
- **No security model**: `SYSTEM` and `RX` links execute with the user's full privileges. There is no sandbox, no confirmation dialog, and no capability system. A malicious `.guide` file can delete files or send harmful ARexx commands.
|
||||
- **ARexx dependency**: The full power of AmigaGuide requires a functional `rexxsyslib.library` and ARexx port infrastructure. On systems without ARexx, `RX` links fail silently and async mode loses much of its utility.
|
||||
|
||||
---
|
||||
|
||||
## When to Use / When NOT to Use
|
||||
|
||||
### When to Use AmigaGuide
|
||||
|
||||
| Scenario | Why AmigaGuide Works |
|
||||
|---|---|
|
||||
| **Application help files** | Native OS support; opens from Help key; context-sensitive via `nag_Node` |
|
||||
| **Cross-referenced documentation** | `LINK other.guide/node` creates a documentation ecosystem |
|
||||
| **Interactive tutorials** | `SYSTEM` and `RX` links let users execute commands from within help |
|
||||
| **Small-to-medium docs (< 200 KB)** | Raw text is efficient; no compilation overhead |
|
||||
| **Integration with ARexx-enabled apps** | Help can send commands back to the application |
|
||||
|
||||
### When NOT to Use AmigaGuide
|
||||
|
||||
| Scenario | Problem | Better Alternative |
|
||||
|---|---|---|
|
||||
| **Large manuals (> 500 KB)** | No search; linear loading; slow navigation | Split into multiple `.guide` files or use external viewer |
|
||||
| **Print-quality documentation** | No page layout, margins, or typography control | Texinfo → PostScript, or DTP tools |
|
||||
| **Secure/restricted content** | No access control; plain text is trivially editable | Compiled help (WinHelp-style) or PDF |
|
||||
| **Embedded in-app help pane** | Cannot embed AmigaGuide viewer as a sub-window gadget | Custom read-only BOOPSI gadget with formatted text |
|
||||
| **Modern cross-platform docs** | `.guide` is Amiga-only | HTML, Markdown, or plain text |
|
||||
|
||||
---
|
||||
|
||||
## Best Practices & Antipatterns
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always name the first node `MAIN`** — viewers default to it if no starting node is specified.
|
||||
2. **Set `@INDEX` and `@TOC`** — gives users predictable navigation buttons.
|
||||
3. **Use `@SMARTWRAP` (V40) instead of `@WORDWRAP`** — produces cleaner output on all viewer versions.
|
||||
4. **Provide a `@{"Back" LINK ...}` link on every non-MAIN node** — users expect a way back.
|
||||
5. **Use `PROGDIR:` or assigned paths for cross-database links** — avoids breakage when the user moves the application.
|
||||
6. **Set `$VER:`** — allows the `Version` command to report the help file version.
|
||||
7. **Keep nodes focused** — one topic per node; deep nesting via links is better than long scrolling.
|
||||
8. **Test in both MultiView and standalone AmigaGuide** — rendering differs slightly between viewers.
|
||||
9. **Use `@MACRO` for consistent styling** (V40) — reduces markup repetition.
|
||||
10. **Set `@ONCLOSE` to clean up** if your `@{RX}` links create temporary files or ports.
|
||||
|
||||
### Antipatterns
|
||||
|
||||
#### 1. The Missing EndNode
|
||||
|
||||
```
|
||||
/* ANTIPATTERN — @ENDNODE omitted */
|
||||
@NODE Main "Help"
|
||||
Welcome to the app.
|
||||
@NODE Setup "Setup"
|
||||
Install instructions.
|
||||
@EndNode
|
||||
|
||||
/* RESULT: "Setup" node includes all text from MAIN onwards
|
||||
because MAIN was never properly closed. */
|
||||
|
||||
/* CORRECT — always close every node */
|
||||
@NODE Main "Help"
|
||||
Welcome to the app.
|
||||
@EndNode
|
||||
|
||||
@NODE Setup "Setup"
|
||||
Install instructions.
|
||||
@EndNode
|
||||
```
|
||||
|
||||
#### 2. The Broken Cross-Database Link
|
||||
|
||||
```
|
||||
/* ANTIPATTERN — relative path assumes current directory */
|
||||
@{"See Also" LINK OtherApp.guide/Main}
|
||||
|
||||
/* If the user opens help from a different directory, this fails. */
|
||||
|
||||
/* CORRECT — use an assign or absolute path */
|
||||
@{"See Also" LINK MYAPP:Docs/OtherApp.guide/Main}
|
||||
```
|
||||
|
||||
#### 3. The Unclean RX Link
|
||||
|
||||
```
|
||||
/* ANTIPATTERN — RX command with unquoted special characters */
|
||||
@{"Run" RX 'ADDRESS MYPORT Run script with spaces'}
|
||||
|
||||
/* Parsing ambiguity — may truncate at first space in argument. */
|
||||
|
||||
/* CORRECT — quote the argument or use RXS for complex scripts */
|
||||
@{"Run" RX 'ADDRESS MYPORT "Run script with spaces"'}
|
||||
```
|
||||
|
||||
#### 4. The Invisible Link
|
||||
|
||||
```
|
||||
/* ANTIPATTERN — link text that looks like body text */
|
||||
For more information see the advanced topics section.
|
||||
|
||||
/* No clickable link — users won't know it's interactive. */
|
||||
|
||||
/* CORRECT — use explicit button-like labels */
|
||||
For more information, @{"click here for advanced topics" LINK Advanced}.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls & Common Mistakes
|
||||
|
||||
### 1. Node Name Collisions
|
||||
|
||||
```
|
||||
/* PITFALL — node names are case-insensitive but must be unique */
|
||||
@NODE Setup "Setup"
|
||||
@NODE setup "Setup Details" /* COLLISION: "setup" == "Setup" */
|
||||
```
|
||||
|
||||
### 2. AmigaGuide Path Not Set
|
||||
|
||||
```
|
||||
/* PITFALL — cross-database links fail if ENV:AmigaGuide/Path is missing */
|
||||
@{"External" LINK SomeLib.guide/Main}
|
||||
|
||||
/* If SomeLib.guide is not in the current directory or the path,
|
||||
the link silently fails. Set the path at install time:
|
||||
SetEnv AmigaGuide/Path "MYAPP:Docs" */
|
||||
```
|
||||
|
||||
### 3. Forgetting Synchronous Blocks
|
||||
|
||||
```c
|
||||
/* PITFALL — calling OpenAmigaGuide() from the main task freezes UI */
|
||||
void OnHelpClick(void)
|
||||
{
|
||||
/* This blocks until the user closes the help window! */
|
||||
OpenAmigaGuide(&nag, TAG_DONE);
|
||||
/* App is frozen — no IDCMP processing, no timer events */
|
||||
}
|
||||
|
||||
/* CORRECT — use async mode for multi-window apps */
|
||||
void OnHelpClick(void)
|
||||
{
|
||||
ctx = OpenAmigaGuideAsyncA(&nag, NULL);
|
||||
/* App continues; handle help closure via ARexx or ignore */
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Dynamic Node Confusion
|
||||
|
||||
Dynamic nodes are generated on-the-fly by an application hosting `amigaguide.library`. If you are not implementing a dynamic node host, do not use `@DNODE` — it is obsolete and ignored by modern viewers.
|
||||
|
||||
### 5. Attribute Nesting Errors
|
||||
|
||||
```
|
||||
/* PITFALL — incorrect nesting of attributes */
|
||||
@{b}bold @{i}bold-italic@{ub} still italic@{ui}
|
||||
|
||||
/* @{ub} undoes bold, but italic is still active.
|
||||
Viewer rendering is undefined. */
|
||||
|
||||
/* CORRECT — close in reverse order of opening (LIFO) */
|
||||
@{b}bold @{i}bold-italic@{ui} bold again@{ub} normal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Real-World Software Using AmigaGuide
|
||||
|
||||
| Software | AmigaGuide Usage |
|
||||
|---|---|
|
||||
| **MultiView** (OS 3.0+) | Displays any `.guide` file via `amigaguideclass` — the default viewer |
|
||||
| **SAS/C** | Compiler error explanations linked via AmigaGuide |
|
||||
| **Directory Opus** | Configuration help and button reference |
|
||||
| **MUI** | `MUIdev.guide` — the entire MUI developer reference is an AmigaGuide database |
|
||||
| **AmigaOS installer scripts** | Post-install help often launched as AmigaGuide |
|
||||
| **Aminet** | Package documentation frequently shipped as `.guide` files |
|
||||
|
||||
### Common Integration Patterns
|
||||
|
||||
**Pattern A: Context-Sensitive Help**
|
||||
```
|
||||
Application maintains a mapping:
|
||||
Window A + Gadget X → "NodeRef/GadgetX"
|
||||
Window B + Menu Y → "NodeRef/MenuY"
|
||||
|
||||
On Help key:
|
||||
nag.nag_Node = mappedNode;
|
||||
OpenAmigaGuideAsyncA(&nag, NULL);
|
||||
```
|
||||
|
||||
**Pattern B: ARexx-Driven Navigation**
|
||||
```
|
||||
Application exposes ARexx port "MYAPP.1"
|
||||
AmigaGuide links use:
|
||||
@{"Do Action" RX 'ADDRESS MYAPP.1 DOACTION'}
|
||||
|
||||
Result: User clicks help → ARexx command sent → app performs action
|
||||
```
|
||||
|
||||
**Pattern C: Documentation Suite**
|
||||
```
|
||||
MyApp.guide (main help)
|
||||
MyApp_API.guide (function reference)
|
||||
MyApp_Tools.guide (utility reference)
|
||||
|
||||
Cross-links:
|
||||
MyApp.guide: @{"API Reference" LINK MyApp_API.guide/Main}
|
||||
MyApp_API.guide: @{"Back to User Guide" LINK MyApp.guide/Main}
|
||||
```
|
||||
|
||||
### AmigaOS Developer Documentation as AmigaGuide
|
||||
|
||||
Commodore itself structured the entire AmigaOS developer documentation corpus as AmigaGuide databases. The **Amiga Developer CD 2.1 (ADCD 2.1)** — the definitive official SDK — shipped the following manuals as `.guide` files:
|
||||
|
||||
| ADCD 2.1 Path | Content | Approximate Scope |
|
||||
|---|---|---|
|
||||
| `Libraries_Manual_guide/` | *ROM Kernel Reference Manual: Libraries* | Every system library: Exec, DOS, Intuition, Graphics, etc. |
|
||||
| `Devices_Manual_guide/` | *ROM Kernel Reference Manual: Devices* | TrackDisk, Audio, Serial, Parallel, Timer, etc. |
|
||||
| `Hardware_Manual_guide/` | *ROM Kernel Reference Manual: Hardware* | Custom chips, DMA, CIA, chipset registers |
|
||||
| `Includes_and_Autodocs_3._guide/` | NDK 3.1 headers + autodocs | All struct definitions and function-by-function API docs |
|
||||
|
||||
This was not merely a packaging choice — it was an architectural statement. The same AmigaGuide viewer that displayed a game's help file could display the complete technical reference for `graphics.library` or the Blitter's minterm logic. A developer could:
|
||||
|
||||
1. **Browse autodocs interactively** — click `@LINK` cross-references to jump from `AllocMem` to `MemHeader` to `FreeMem`
|
||||
2. **Keep docs open while coding** — the async viewer sat alongside the editor, navigable without leaving the Workbench
|
||||
3. **Search with external tools** — because `.guide` files are plain ASCII, `grep` and `Search` could find text inside them; no proprietary indexing format required
|
||||
4. **Ship custom subsets** — a developer could copy just the relevant autodoc nodes into a project's `Docs/` drawer
|
||||
|
||||
The autodoc format — a structured comment convention in NDK header files — was also converted to AmigaGuide by community tools. **New Style Autodocs** (Aminet `dev/misc/NSA_amigaguide.lha`, 2002) repackaged library autodocs as hyperlinked `.guide` files with `@NODE` per function and `@TOC` navigation, making the raw API reference far more browsable than scrolling through flat text files.
|
||||
|
||||
### Authoring Tools and Converters
|
||||
|
||||
Because AmigaGuide is plain ASCII, any text editor suffices for authoring. However, several specialized tools streamlined creation and conversion:
|
||||
|
||||
| Tool | Aminet Path | Purpose |
|
||||
|---|---|---|
|
||||
| **AGWriter** | `text/hyper/AGWriter103.lha` | GUI editor for creating, editing, and validating AmigaGuide files. Supports WYSIWYG-style node management, link insertion (`LINK`, `RX`, `RXS`, `SYSTEM`), and round-trip conversion to plain text. |
|
||||
| **GuideML** | `text/hyper/guideml.lha` | AmigaGuide-to-HTML converter (C + GUI). Converts `@NODE` to HTML pages, `@LINK` to `<a href>`, and attributes to inline styles. Includes source code. |
|
||||
| **ag2html** | `text/hyper/ag2html.lha` | Perl script that converts a `.guide` file into a directory of interlinked HTML files suitable for web serving. |
|
||||
| **HTML2Guide** | `util/conv/HTML2Guide-1.2.lha` | Reverse converter: batch-converts `.html`/`.htm` files (including subdirectories) into a single AmigaGuide database with preserved relative links. |
|
||||
| **New Style Autodocs** | `dev/misc/NSA_amigaguide.lha` | Reformats NDK autodoc text files into hyperlinked AmigaGuide databases with per-function `@NODE` entries. |
|
||||
|
||||
> [!NOTE]
|
||||
> The ADCD 2.1 online mirror (`http://amigadev.elowar.com/read/ADCD_2.1/`) renders the original AmigaGuide `.guide` files as HTML using modern server-side conversion. The URL structure directly maps to the original ADCD directory layout, preserving the node hierarchy that Commodore established in 1994.
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Can I embed an image directly in an AmigaGuide node?**
|
||||
> Not inline. You link to it via `@{"Image" LINK picture.iff/Main}` (V39+). The DataTypes viewer opens the image in a separate window or replaces the current view depending on the viewer.
|
||||
|
||||
**Q: Why does my link to another database fail?**
|
||||
> Either the path is wrong, or `ENV:AmigaGuide/Path` does not include the target directory. Always use assigns (e.g., `MYAPP:Docs/Other.guide/Main`) for reliability.
|
||||
|
||||
**Q: What is the difference between `RX` and `RXS`?**
|
||||
> `RX` executes an inline ARexx command string. `RXS` executes an ARexx script file from disk. Use `RXS` for complex multi-line scripts.
|
||||
|
||||
**Q: Can I use AmigaGuide on OS 1.3?**
|
||||
> No. AmigaGuide requires OS 2.0+ for the viewer, and OS 3.0+ for DataType integration (cross-linking to images/audio). On 1.3, use plain text or IFF-FTXT.
|
||||
|
||||
**Q: How do I make my `.guide` file open from Workbench?**
|
||||
> Set the icon's default tool to `SYS:Utilities/MultiView` (OS 3.0+) or `SYS:Utilities/AmigaGuide`. Ensure `amigaguide.library` is in `LIBS:`.
|
||||
|
||||
**Q: Is there a size limit for AmigaGuide files?**
|
||||
> No hard limit, but files over ~500 KB become unwieldy due to the lack of full-text search. Split large documentation into multiple linked databases.
|
||||
|
||||
**Q: Can I convert AmigaGuide to HTML?**
|
||||
> Not natively. Third-party tools exist (e.g., `guide2html` on Aminet) that parse the `@` commands and emit HTML. The mapping is straightforward since the concepts are similar.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK 3.9: `libraries/amigaguide.h`, `datatypes/amigaguideclass.h`
|
||||
- ADCD 2.1: `amigaguide.library` autodocs (`OpenAmigaGuide`, `OpenAmigaGuideAsync`, `SendAmigaGuideCmd`)
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* — Appendix C: AmigaGuide
|
||||
- See also: [datatypes.md](datatypes.md) — DataTypes framework that powers `amigaguideclass` and media embedding
|
||||
- See also: [rexxsyslib.md](rexxsyslib.md) — ARexx scripting and port communication
|
||||
- See also: [input_events.md](../09_intuition/input_events.md) — Handling Help key and IDCMP for context-sensitive help
|
||||
- See also: [boopsi.md](../09_intuition/boopsi.md) — BOOPSI foundation underlying the AmigaGuide DataType
|
||||
839
11_libraries/datatypes.md
Normal file
839
11_libraries/datatypes.md
Normal file
|
|
@ -0,0 +1,839 @@
|
|||
[← Home](../README.md) · [Libraries](README.md)
|
||||
|
||||
# Datatypes System — Object-Oriented File Loading for Images, Sound, Text, and Animation
|
||||
|
||||
## Overview
|
||||
|
||||
The DataTypes system is AmigaOS's extensible, object-oriented framework for loading, displaying, and manipulating structured data without hard-coding format-specific parsers. Built on Intuition's BOOPSI object model and introduced in OS 2.0, it allows a single API — `NewDTObject()` — to instantiate an object from an ILBM image, an 8SVX sample, an ASCII text file, or any third-party format installed on the system. The framework delegates parsing to loadable **datatype classes** (subclasses of pictureclass, soundclass, textclass, animationclass, or amigaguideclass) that live in `SYS:Classes/DataTypes/`. Each class registers itself via a descriptor in `DEVS:DataTypes/` so that `datatypes.library` can identify files by magic bytes or extension, route them to the correct parser, and present a uniform interface to the application. For the developer, this means no per-format loader code, no manual IFF chunk walking for basic tasks, and automatic clipboard integration. The trade-off is memory overhead (BOOPSI objects are larger than raw buffers) and reduced control over the parsing pipeline.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### The BOOPSI Class Hierarchy
|
||||
|
||||
The DataTypes framework is a layered extension of BOOPSI. Every datatype object is a BOOPSI object, and every datatype class is a BOOPSI `IClass` registered with Intuition.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Application"
|
||||
APP["NewDTObject(path)"]
|
||||
end
|
||||
|
||||
subgraph "datatypes.library"
|
||||
NDTO["NewDTObject()\nIdentify file → Find class"]
|
||||
DTA["DataType superclass\n(common attributes)"]
|
||||
end
|
||||
|
||||
subgraph "Intuition — BOOPSI"
|
||||
ROOT["rootclass"]
|
||||
GAD["gadgetclass"]
|
||||
end
|
||||
|
||||
subgraph "Concrete Datatype Classes"
|
||||
PIC["pictureclass\n(ILBM, PNG, GIF, JPEG)"]
|
||||
SND["soundclass\n(8SVX, WAV, AIFF)"]
|
||||
TXT["textclass\n(ASCII, IFF-FTXT)"]
|
||||
ANI["animationclass\n(ANIM, GIF anim)"]
|
||||
AGD["amigaguideclass\n(.guide hypertext)"]
|
||||
end
|
||||
|
||||
APP --> NDTO
|
||||
NDTO --> DTA
|
||||
DTA --> ROOT
|
||||
DTA --> GAD
|
||||
PIC --> DTA
|
||||
SND --> DTA
|
||||
TXT --> DTA
|
||||
ANI --> DTA
|
||||
AGD --> DTA
|
||||
|
||||
style NDTO fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style PIC fill:#e8f5e9,stroke:#4caf50,color:#333
|
||||
style SND fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
```
|
||||
|
||||
### Data Flow: From File to Renderable Object
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Application
|
||||
participant DT as datatypes.library
|
||||
participant Desc as DEVS:DataTypes/ descriptor
|
||||
participant Class as Concrete class
|
||||
participant Super as pictureclass/soundclass
|
||||
|
||||
App->>DT: NewDTObject(path, DTA_SourceType, DTST_FILE, ...)
|
||||
DT->>Desc: Scan descriptors for matching signature
|
||||
Desc-->>DT: Match: "ilbm.datatype"
|
||||
DT->>Class: Create object (OM_NEW)
|
||||
Class->>Class: Parse file, build BitMap / VoiceHeader
|
||||
Class->>Super: Set superclass attributes (PDTA_BitMap, etc.)
|
||||
Super-->>Class: Object ready
|
||||
Class-->>DT: Return DataType object
|
||||
DT-->>App: Return Object *
|
||||
|
||||
App->>DT: GetDTAttrs(obj, PDTA_BitMap, &bm, ...)
|
||||
DT-->>App: BitMap * populated
|
||||
|
||||
App->>DT: DisposeDTObject(obj)
|
||||
DT->>Class: OM_DISPOSE
|
||||
Class->>Class: Free file-specific resources
|
||||
```
|
||||
|
||||
### Key Design Insight
|
||||
|
||||
The framework splits responsibility into three layers:
|
||||
|
||||
1. **datatypes.library** — file identification, class routing, common attribute management
|
||||
2. **Superclass** (pictureclass, soundclass, etc.) — domain-specific rendering, clipboard serialization, standard data format
|
||||
3. **Subclass** (ilbm.datatype, gif.datatype, etc.) — file parsing, conversion to superclass format
|
||||
|
||||
This means a picture subclass only needs to parse its format and fill a `BitMapHeader`, allocate a `BitMap`, and supply a `ColorMap`. The picture superclass handles everything else: rendering into a window, copy-to-clipboard, save-back-to-file, and color remapping.
|
||||
|
||||
---
|
||||
|
||||
## Data Structures
|
||||
|
||||
### DataType Header
|
||||
|
||||
```c
|
||||
/* datatypes/datatypes.h — NDK 3.9 */
|
||||
struct DataType {
|
||||
struct Node dtn_Node; /* ln_Name = human-readable name */
|
||||
struct DataType * dtn_Next; /* next in chain */
|
||||
struct DataType * dtn_Previous; /* previous in chain */
|
||||
ULONG dtn_Flags; /* DTF_* flags */
|
||||
ULONG dtn_Labels; /* label bitfield (for ASL requester) */
|
||||
UWORD dtn_GroupID; /* GID_* (picture, sound, text, etc.) */
|
||||
UWORD dtn_ID; /* class-specific ID */
|
||||
ULONG dtn_CodePage; /* text codepage, if applicable */
|
||||
STRPTR dtn_TextAttr; /* text attribute string */
|
||||
ULONG dtn_Reserved[4]; /* must be zero */
|
||||
};
|
||||
```
|
||||
|
||||
### BitMapHeader (Picture Class)
|
||||
|
||||
```c
|
||||
/* datatypes/pictureclass.h — NDK 3.9 */
|
||||
struct BitMapHeader {
|
||||
UWORD bmh_Width; /* image width in pixels */
|
||||
UWORD bmh_Height; /* image height in pixels */
|
||||
WORD bmh_Left; /* x offset (usually 0) */
|
||||
WORD bmh_Top; /* y offset (usually 0) */
|
||||
UBYTE bmh_Depth; /* number of bitplanes (1–8 typical) */
|
||||
UBYTE bmh_Masking; /* 0=none, 1=has mask, 2=transparent color */
|
||||
UBYTE bmh_Compression; /* 0=none, 1=ByteRun1 */
|
||||
UBYTE bmh_Pad;
|
||||
UWORD bmh_Transparent; /* transparent color index */
|
||||
UBYTE bmh_XAspect; /* pixel aspect ratio X */
|
||||
UBYTE bmh_YAspect; /* pixel aspect ratio Y */
|
||||
WORD bmh_PageWidth; /* source page width */
|
||||
WORD bmh_PageHeight; /* source page height */
|
||||
};
|
||||
```
|
||||
|
||||
### VoiceHeader (Sound Class)
|
||||
|
||||
```c
|
||||
/* datatypes/soundclass.h — NDK 3.9 */
|
||||
struct VoiceHeader {
|
||||
ULONG vh_OneShotHiSamples; /* one-shot part length */
|
||||
ULONG vh_RepeatHiSamples; /* repeat part length */
|
||||
ULONG vh_SamplesPerHiCycle; /* samples per cycle */
|
||||
UWORD vh_SamplesPerSec; /* sample rate (Hz) */
|
||||
UBYTE vh_Octaves; /* number of octaves */
|
||||
UBYTE vh_Compression; /* 0=none */
|
||||
ULONG vh_Volume; /* 0–64 (or 0–65535 for 16-bit) */
|
||||
};
|
||||
```
|
||||
|
||||
### SourceType Constants
|
||||
|
||||
| Constant | Value | Meaning |
|
||||
|---|---|---|
|
||||
| `DTST_FILE` | 1 | Source is an AmigaDOS file path |
|
||||
| `DTST_CLIPBOARD` | 2 | Source is the clipboard device |
|
||||
| `DTST_RAM` | 3 | Source is a memory buffer |
|
||||
| `DTST_HOTLINK` | 4 | Source is a hypertext link (AmigaGuide) |
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Object Lifecycle
|
||||
|
||||
```c
|
||||
#include <datatypes/datatypes.h>
|
||||
#include <proto/datatypes.h>
|
||||
|
||||
/* Create a DataType object from a source */
|
||||
Object *NewDTObject(APTR name, ULONG tag1, ...);
|
||||
|
||||
/* Destroy a DataType object and all associated resources */
|
||||
void DisposeDTObject(Object *o);
|
||||
```
|
||||
|
||||
**Key tags for `NewDTObject()`:**
|
||||
|
||||
| Tag | Type | I/S/G | Description |
|
||||
|---|---|---|---|
|
||||
| `DTA_SourceType` | `ULONG` | I | `DTST_FILE`, `DTST_CLIPBOARD`, `DTST_RAM` |
|
||||
| `DTA_Handle` | `BPTR` | I | File handle (if `DTST_FILE` and already open) |
|
||||
| `DTA_DataType` | `struct DataType *` | G | Returns the matched DataType descriptor |
|
||||
| `DTA_NominalHoriz` | `ULONG` | G | Native width in pixels (picture/animation) |
|
||||
| `DTA_NominalVert` | `ULONG` | G | Native height in pixels |
|
||||
| `DTA_ObjName` | `STRPTR` | G | Title/name metadata from file |
|
||||
| `DTA_ObjAuthor` | `STRPTR` | G | Author metadata |
|
||||
| `DTA_ObjAnnotation` | `STRPTR` | G | Annotation/comment metadata |
|
||||
| `DTA_ObjCopyright` | `STRPTR` | G | Copyright string |
|
||||
| `DTA_ObjVersion` | `STRPTR` | G | Version string |
|
||||
| `DTA_Domain` | `STRPTR` | I | Request a specific datatype by name |
|
||||
| `DTA_GroupID` | `ULONG` | I | Restrict to group (e.g., `GID_PICTURE`) |
|
||||
|
||||
### Attribute Access
|
||||
|
||||
```c
|
||||
/* Get one or more attributes */
|
||||
ULONG GetDTAttrs(Object *o, ULONG tag1, ...);
|
||||
|
||||
/* Set one or more attributes */
|
||||
ULONG SetDTAttrs(Object *o, struct Window *win,
|
||||
struct Requester *req, ULONG tag1, ...);
|
||||
```
|
||||
|
||||
> **Note**: `SetDTAttrs()` requires a window pointer when the object is embedded in a gadget context — it triggers visual refresh through Intuition.
|
||||
|
||||
### Window Embedding
|
||||
|
||||
Because DataType objects are BOOPSI gadgets, they can be added directly to Intuition windows:
|
||||
|
||||
```c
|
||||
/* Add a DataType object to a window as a gadget */
|
||||
LONG AddDTObject(struct Window *win, struct Requester *req,
|
||||
Object *o, LONG pos);
|
||||
|
||||
/* Remove from window */
|
||||
void RemoveDTObject(struct Window *win, Object *o);
|
||||
|
||||
/* Trigger redraw (e.g., after SetDTAttrs changes visual state) */
|
||||
void RefreshDTObjectA(Object *o, struct Window *win,
|
||||
struct Requester *req, struct TagItem *attrs);
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
```c
|
||||
/* Perform a method on the object */
|
||||
ULONG DoDTMethodA(Object *o, struct Window *win,
|
||||
struct Requester *req, Msg msg);
|
||||
|
||||
/* Get supported method mask */
|
||||
ULONG GetDTMethods(Object *obj);
|
||||
|
||||
/* Get supported trigger method mask */
|
||||
ULONG GetDTTriggerMethods(Object *obj);
|
||||
```
|
||||
|
||||
Common methods:
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `DTM_WRITE` | Save object back to file or clipboard |
|
||||
| `DTM_COPY` | Copy to clipboard |
|
||||
| `DTM_PRINT` | Print the object |
|
||||
| `DTM_TRIGGER` | Execute a trigger (e.g., `STM_PLAY` for sound) |
|
||||
|
||||
### Trigger Methods
|
||||
|
||||
| Trigger | Description |
|
||||
|---|---|
|
||||
| `STM_PLAY` | Play sound / start animation |
|
||||
| `STM_STOP` | Stop playback |
|
||||
| `STM_PAUSE` | Pause playback |
|
||||
| `STM_RESUME` | Resume playback |
|
||||
| `STM_REWIND` | Rewind to start |
|
||||
| `STM_FASTFORWARD` | Fast forward |
|
||||
|
||||
### Picture Class Attributes
|
||||
|
||||
| Tag | Type | Description |
|
||||
|---|---|---|
|
||||
| `PDTA_BitMapHeader` | `struct BitMapHeader **` | Pointer to header struct |
|
||||
| `PDTA_BitMap` | `struct BitMap **` | The actual bitmap (Chip RAM!) |
|
||||
| `PDTA_ColorRegisters` | `struct ColorRegister **` | Palette entries (RGB) |
|
||||
| `PDTA_CRegs` | `LONG **` | Color registers for remapping |
|
||||
| `PDTA_NumColors` | `ULONG` | Number of colors in palette |
|
||||
| `PDTA_ModeID` | `ULONG` | Display ModeID for this image |
|
||||
|
||||
### Sound Class Attributes
|
||||
|
||||
| Tag | Type | Description |
|
||||
|---|---|---|
|
||||
| `SDTA_Sample` | `UBYTE **` | 8-bit sample data pointer |
|
||||
| `SDTA_SampleLength` | `ULONG` | Length in bytes |
|
||||
| `SDTA_Period` | `UWORD` | Paula period value |
|
||||
| `SDTA_Volume` | `UWORD` | Volume 0–64 |
|
||||
| `SDTA_Cycles` | `UWORD` | Loop count (0 = infinite) |
|
||||
| `SDTA_VoiceHeader` | `struct VoiceHeader **` | Voice header struct |
|
||||
|
||||
---
|
||||
|
||||
## Decision Guide: DataTypes vs. Alternatives
|
||||
|
||||
| Criterion | DataTypes | iffparse.library | Direct File I/O |
|
||||
|---|---|---|---|
|
||||
| **When to use** | Quick loading of standard formats; GUI display; clipboard integration | Full control over IFF structure; need custom chunk handling | Non-standard formats; maximum performance; minimal memory |
|
||||
| **Code size** | Small — one API for all formats | Medium — must handle chunks per format | Large — custom parser per format |
|
||||
| **Format coverage** | Extensible via installed classes | IFF only (ILBM, 8SVX, ANIM, etc.) | Whatever you implement |
|
||||
| **Memory overhead** | Higher (BOOPSI objects, BitMapHeader, ColorMap) | Low — you control allocations | Lowest — raw buffers |
|
||||
| **Display integration** | Automatic — embed as gadget | Manual — parse then render yourself | Manual |
|
||||
| **Save/write support** | Yes — `DTM_WRITE` delegates to class | Yes — manual chunk writing | Yes — custom writer |
|
||||
| **OS version** | Requires OS 2.0+ (V37+) | Requires OS 1.3+ (iffparse) | Always works |
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Q1{"Need to display data<br/>in a window?"} -->|"Yes"| Q2{"Format is standard<br/>ILBM/8SVX/ANIM/text?"}
|
||||
Q1 -->|"No / custom engine"| DIRECT["Direct I/O or<br/>custom parser"]
|
||||
Q2 -->|"Yes"| Q3{"Need fine-grained<br/>chunk control?"}
|
||||
Q2 -->|"No / third-party format"| DT["DataTypes<br/>(if class exists)"]
|
||||
Q3 -->|"No"| DT
|
||||
Q3 -->|"Yes"| IFF["iffparse.library<br/>(manual chunk walk)"]
|
||||
|
||||
style DT fill:#e8f5e9,stroke:#4caf50,color:#333
|
||||
style IFF fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
style DIRECT fill:#ffebee,stroke:#f44336,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Historical Context & Modern Analogies
|
||||
|
||||
### The Elegance of Zero-Recompile Extensibility
|
||||
|
||||
The DataTypes system embodies a design philosophy that remains radical even today: **applications should not know what file formats exist**. A developer writes a paint program that calls `NewDTObject()` on any file. Ten years later, a user installs a JPEG datatype class that did not exist when the paint program was written. The paint program — never recompiled, never patched, never reconfigured — can now load JPEG files as if the format had been built in from the start.
|
||||
|
||||
This is not plugin architecture in the limited sense of "my application loads plugins for itself." This is **system-wide late binding**: the OS itself brokers between applications and formats. Two files copied to disk constitute a complete installation:
|
||||
|
||||
1. `SYS:Classes/DataTypes/picture/jpeg.datatype` — the class binary
|
||||
2. `DEVS:DataTypes/JPEG` — the descriptor (magic bytes, extension, precedence)
|
||||
|
||||
No registry editing. No config file parsing. No daemon restart. The next `NewDTObject()` call automatically discovers the new class through `datatypes.library`'s internal scan. Every DataTypes-aware application on the system gains the new capability simultaneously.
|
||||
|
||||
In 1990, this was unprecedented. Applications on every other platform carried format support inside their own binaries:
|
||||
|
||||
| Platform (1990) | Extensible? | Recompile Required? | Mechanism |
|
||||
|---|---|---|---|
|
||||
| **AmigaOS 2.0+** | **Yes** | **No** | System-wide BOOPSI classes with descriptors |
|
||||
| **Atari ST / TT** | No | Yes | Each app bundles its own loaders |
|
||||
| **Mac OS (System 7)** | Partial | Yes | Apps link against specific graphics libraries |
|
||||
| **Windows 3.0** | No | Yes | OLE 1.0 is COM-based and app-specific, not file-centric |
|
||||
| **UNIX (X11)** | No | Yes | Statically linked image libraries per application |
|
||||
| **NeXTSTEP** | Partial | Partial | NXImage uses typed streams but apps must bundle readers |
|
||||
|
||||
The "no recompile" property is the critical differentiator. NeXTSTEP had typed streams, but an application shipped with the readers it knew about. Windows would not get system-wide file-type extensibility until the Windows 95 shell and COM IDataObject (1995). macOS would not get equivalent functionality until QuickTime importer components (1991) and later UTType (2007). Linux distributions would not standardize on MIME-type associations with shared handler plugins until the mid-2000s.
|
||||
|
||||
### How It Worked in Practice
|
||||
|
||||
The release cycle of PNG in the mid-1990s illustrates the benefit perfectly:
|
||||
|
||||
| Step | Traditional Platform | AmigaOS with DataTypes |
|
||||
|---|---|---|
|
||||
| New format emerges (PNG) | User must wait for every app vendor to add support | User waits for one developer to write a PNG datatype |
|
||||
| Support arrives | Download new versions of paint program, image viewer, thumbnailer | Copy `png.datatype` and `DEVS:DataTypes/PNG` to disk |
|
||||
| Integration | Each app has its own PNG decoder, color management, metadata handling | All apps share one canonical PNG decoder |
|
||||
| Consistency | Colors render differently in each app | One color remap through pictureclass |
|
||||
| Clipboard | Apps must add PNG import/export to their private clipboard logic | `DTST_CLIPBOARD` works automatically via the superclass |
|
||||
|
||||
### Modern Analogies
|
||||
|
||||
The closest modern equivalents to DataTypes' system-wide, zero-recompile extensibility are **media framework plugin systems** and **OS-level content-type registries**:
|
||||
|
||||
| DataTypes Concept | Modern Equivalent | Why It Maps |
|
||||
|---|---|---|
|
||||
| `NewDTObject()` → auto-routed to class | **GStreamer pipeline auto-plug** | Install an AV1 decoder plugin; ALL GStreamer applications (Totem, Cheese, Rhythmbox) can play AV1 without recompilation. They do not know AV1 exists — they ask GStreamer to "play this" and the framework negotiates the correct element. |
|
||||
| Descriptor in `DEVS:DataTypes/` | **Linux MIME `.desktop` associations + shared-mime-info** | Install one MIME type definition and one application handler; all file managers, web browsers, and email clients can open that format. But: apps must still understand the data, unlike DataTypes where the OS returns a usable object. |
|
||||
| pictureclass / soundclass | **ImageMagick codec delegates / GEGL operations** | Add a HEIC delegate to ImageMagick; all ImageMagick-based tools (and GIMP via plugin) gain HEIC support. The superclass (`Image` / `GeglBuffer`) abstracts format specifics. |
|
||||
| `DTA_SourceType` + auto-detection | **macOS `UTType` + `QLPreviewController`** | The system identifies a file by its content (not just extension) and routes to the correct preview provider. QuickLook plugins work system-wide without host application recompilation. |
|
||||
| `DTM_WRITE` serialization | **Microsoft COM `IPersistFile`** | The OS asks the format handler to serialize itself; the host application delegates save logic. But COM requires explicit interface negotiation; DataTypes uses attribute tags. |
|
||||
| `DTST_CLIPBOARD` uniform transfer | **HTML5 Clipboard API `ClipboardItem`** | Applications paste "an image" without caring whether it arrived as PNG, JPEG, or BMP. The platform handles format conversion. |
|
||||
| BOOPSI gadget embedding | **WPF `Image` / Qt `QLabel` with `QPixmap`** | The view widget renders from an abstract source. But modern widgets are framework-specific; a DataTypes object is an Intuition-native BOOPSI gadget addable to any window. |
|
||||
|
||||
### Where the Analogies Break Down
|
||||
|
||||
Despite the parallels, no modern system fully replicates DataTypes' combination of properties:
|
||||
|
||||
1. **True OS-native object model**: DataTypes objects are BOOPSI objects — they can be added directly to Intuition windows, receive input events, and participate in the layout system. Modern equivalents require wrapper widgets (Qt `QImageReader` → `QLabel`), adapter layers (GStreamer → GTK video sink), or explicit host application support (QuickLook plugins need a host that calls the API).
|
||||
|
||||
2. **Single-file install, no registration step**: GStreamer plugins need `gst-plugin-scanner` cache updates; ImageMagick delegates need `policy.xml` edits; macOS UTTypes need `Info.plist` declarations and app bundle restarts. Amiga DataTypes required only a file copy — `datatypes.library` rescans on demand.
|
||||
|
||||
3. **Write-time ignorance**: A developer in 1992 could write `NewDTObject(path, DTA_GroupID, GID_PICTURE, TAG_DONE)` and be guaranteed that every image format invented through 2026 would work, provided a datatype class existed. Modern frameworks generally require the application to at least specify a MIME type or content category.
|
||||
|
||||
4. **No async loading**: `NewDTObject()` is synchronous and blocking. Modern frameworks (GStreamer's async state changes, Android's `ContentResolver` async queries, web `createImageBitmap()`) use callbacks, promises, or coroutines to avoid blocking the UI thread.
|
||||
|
||||
5. **No streaming or progressive decode**: The entire file is parsed into a complete BOOPSI object before control returns. There is no equivalent to modern progressive JPEG decode, video frame streaming, or memory-mapped access.
|
||||
|
||||
6. **Planar graphics assumptions**: Color remapping, BitMap allocation, and palette management are designed for Amiga's planar display system. A modern RGBA framebuffer datatype would need a fundamentally different `pictureclass` implementation.
|
||||
|
||||
---
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Example 1: Load and Display an Image
|
||||
|
||||
```c
|
||||
#include <exec/types.h>
|
||||
#include <intuition/intuition.h>
|
||||
#include <datatypes/datatypes.h>
|
||||
#include <datatypes/pictureclass.h>
|
||||
#include <proto/datatypes.h>
|
||||
#include <proto/exec.h>
|
||||
#include <proto/intuition.h>
|
||||
#include <proto/graphics.h>
|
||||
|
||||
/* Load an image file and extract its BitMap */
|
||||
struct BitMap *LoadPicture(CONST_STRPTR path, struct ColorMap **cmOut)
|
||||
{
|
||||
struct BitMap *bm = NULL;
|
||||
struct ColorMap *cm = NULL;
|
||||
|
||||
Object *dto = NewDTObject((APTR)path,
|
||||
DTA_SourceType, DTST_FILE,
|
||||
DTA_GroupID, GID_PICTURE,
|
||||
PDTA_Remap, FALSE, /* keep original colors */
|
||||
TAG_DONE);
|
||||
|
||||
if (!dto)
|
||||
{
|
||||
/* File not found, or no matching picture class installed */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Retrieve the BitMap and ColorMap from the picture superclass */
|
||||
GetDTAttrs(dto,
|
||||
PDTA_BitMap, (ULONG)&bm,
|
||||
PDTA_ColorMap, (ULONG)&cm,
|
||||
TAG_DONE);
|
||||
|
||||
/* Note: the BitMap belongs to the object; if you need it after
|
||||
DisposeDTObject(), you must copy or detach it. */
|
||||
|
||||
if (cmOut) *cmOut = cm;
|
||||
|
||||
/* DisposeDTObject() will free bm and cm unless you detached them.
|
||||
For a quick blit-and-forget, keep the object alive until done. */
|
||||
return bm; /* still valid while dto lives */
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Play a Sound Sample
|
||||
|
||||
```c
|
||||
#include <datatypes/datatypes.h>
|
||||
#include <datatypes/soundclass.h>
|
||||
#include <proto/datatypes.h>
|
||||
|
||||
BOOL PlaySoundFile(CONST_STRPTR path)
|
||||
{
|
||||
Object *dto = NewDTObject((APTR)path,
|
||||
DTA_SourceType, DTST_FILE,
|
||||
DTA_GroupID, GID_SOUND,
|
||||
TAG_DONE);
|
||||
|
||||
if (!dto) return FALSE;
|
||||
|
||||
/* Trigger playback */
|
||||
struct dtTrigger dtt;
|
||||
dtt.dtt_Method = DTM_TRIGGER;
|
||||
dtt.dtt_GInfo = NULL;
|
||||
dtt.dtt_Function = STM_PLAY;
|
||||
dtt.dtt_Data = NULL;
|
||||
|
||||
DoDTMethodA(dto, NULL, NULL, (Msg)&dtt);
|
||||
|
||||
/* In a real app, wait for completion or provide UI to stop */
|
||||
/* For now, just let it play and clean up after a delay */
|
||||
Delay(300); /* ~6 seconds at 50 Hz */
|
||||
|
||||
DisposeDTObject(dto);
|
||||
return TRUE;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Embed a DataType Object in a Window
|
||||
|
||||
```c
|
||||
#include <intuition/intuition.h>
|
||||
#include <datatypes/datatypes.h>
|
||||
#include <proto/datatypes.h>
|
||||
#include <proto/intuition.h>
|
||||
|
||||
struct Window *win;
|
||||
Object *dtObj;
|
||||
|
||||
BOOL OpenImageWindow(CONST_STRPTR path)
|
||||
{
|
||||
win = OpenWindowTags(NULL,
|
||||
WA_Title, "DataTypes Viewer",
|
||||
WA_Width, 640,
|
||||
WA_Height, 480,
|
||||
WA_Flags, WFLG_CLOSEGADGET | WFLG_DRAGBAR |
|
||||
WFLG_DEPTHGADGET | WFLG_ACTIVATE,
|
||||
TAG_DONE);
|
||||
|
||||
if (!win) return FALSE;
|
||||
|
||||
/* Create the DataType object; it acts as a BOOPSI gadget */
|
||||
dtObj = NewDTObject((APTR)path,
|
||||
DTA_SourceType, DTST_FILE,
|
||||
GA_Left, 0,
|
||||
GA_Top, 0,
|
||||
GA_RelWidth, TRUE,
|
||||
GA_RelHeight, TRUE,
|
||||
TAG_DONE);
|
||||
|
||||
if (!dtObj)
|
||||
{
|
||||
CloseWindow(win);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Add to window — the object renders itself */
|
||||
AddDTObject(win, NULL, dtObj, -1);
|
||||
RefreshDTObjectA(dtObj, win, NULL, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CloseImageWindow(void)
|
||||
{
|
||||
if (dtObj)
|
||||
{
|
||||
RemoveDTObject(win, dtObj);
|
||||
DisposeDTObject(dtObj);
|
||||
dtObj = NULL;
|
||||
}
|
||||
if (win)
|
||||
{
|
||||
CloseWindow(win);
|
||||
win = NULL;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Load from Clipboard
|
||||
|
||||
```c
|
||||
#include <datatypes/datatypes.h>
|
||||
#include <proto/datatypes.h>
|
||||
|
||||
Object *LoadFromClipboard(void)
|
||||
{
|
||||
Object *dto = NewDTObject(NULL,
|
||||
DTA_SourceType, DTST_CLIPBOARD,
|
||||
DTA_GroupID, GID_PICTURE, /* or GID_TEXT, GID_SOUND */
|
||||
TAG_DONE);
|
||||
|
||||
/* The library reads the clipboard unit 0 and identifies the
|
||||
format automatically via the descriptor signatures. */
|
||||
|
||||
return dto;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Use / When NOT to Use
|
||||
|
||||
### When to Use DataTypes
|
||||
|
||||
| Scenario | Why DataTypes Excels |
|
||||
|---|---|
|
||||
| **System-friendly applications** | Cooperative with Intuition; objects are valid BOOPSI gadgets |
|
||||
| **Multi-format viewers** | One codebase handles ILBM, PNG, GIF, JPEG, etc. automatically |
|
||||
| **Clipboard integration** | `DTST_CLIPBOARD` gives uniform read/write for free |
|
||||
| **Rapid prototyping** | `NewDTObject()` + `AddDTObject()` displays an image in ~10 lines |
|
||||
| **Workbench tools** | Metadata tags (`DTA_ObjName`, `DTA_ObjAuthor`) expose file info |
|
||||
| **Save/export support** | `DTM_WRITE` delegates to the class; no custom serializer needed |
|
||||
|
||||
### When NOT to Use DataTypes
|
||||
|
||||
| Scenario | Problem | Better Alternative |
|
||||
|---|---|---|
|
||||
| **Game engine texture loading** | BOOPSI overhead, planar bitmap conversion, no async | Direct `AllocBitMap()` + custom loader; use [iffparse.library](iffparse.md) for IFF |
|
||||
| **Real-time audio streaming** | Entire sample loaded synchronously; no streaming API | Direct `audio.device` with double-buffered [IORequest](../06_exec_os/io_requests.md) |
|
||||
| **Memory-constrained tools** | Object overhead + full bitmap + colormap can be large | iffparse.library with on-demand decode |
|
||||
| **Custom or proprietary formats** | No class exists; writing a class is more work than a parser | Direct file I/O or custom parser |
|
||||
| **Batch conversion pipelines** | `NewDTObject()` → `DTM_WRITE` is convenient but slower than dedicated tools | Command-line tools (ImageMagick port, etc.) |
|
||||
| **Need pixel-level access during decode** | Framework abstracts away the parsing pipeline | iffparse.library or direct loader |
|
||||
|
||||
### Applicability Ranges
|
||||
|
||||
- **Image sizes up to ~1 MB** (Chip RAM permitting): DataTypes handles comfortably
|
||||
- **Audio samples > 500 KB**: Consider direct audio.device I/O to avoid duplication
|
||||
- **Animation**: Use DataTypes for short clips; long sequences need custom frame management
|
||||
- **Object counts**: Embedding >20 DataType gadgets in one window stresses Intuition's gadget list
|
||||
|
||||
---
|
||||
|
||||
## Best Practices & Antipatterns
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always check `NewDTObject()` return value** — a missing class or corrupted file returns `NULL`
|
||||
2. **Set `DTA_GroupID` when the type is known** — reduces descriptor scan time and prevents misidentification
|
||||
3. **Use `PDTA_Remap, FALSE` for off-screen processing** — avoids unnecessary color remapping
|
||||
4. **Keep the object alive while using its BitMap/Sample** — disposing frees the underlying buffers
|
||||
5. **Call `RefreshDTObjectA()` after `SetDTAttrs()` on an embedded object** — Intuition does not auto-redraw
|
||||
6. **Use `RemoveDTObject()` before `DisposeDTObject()`** on window-embedded objects — prevents dangling gadget pointers
|
||||
7. **Query `GetDTMethods()` before calling `DTM_WRITE`** — not all subclasses implement save
|
||||
8. **Free the file handle yourself if you passed `DTA_Handle`** — the library does not close it
|
||||
9. **Call `SetIoErr()` before returning `NULL` from a custom class** — proper DOS error propagation
|
||||
10. **Test with `DTA_Domain` to force a specific class** — useful for debugging format detection
|
||||
|
||||
### Antipatterns
|
||||
|
||||
#### 1. The Orphaned BitMap
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — extracting pointer then disposing the owner */
|
||||
struct BitMap *bm;
|
||||
GetDTAttrs(dto, PDTA_BitMap, (ULONG)&bm, TAG_DONE);
|
||||
DisposeDTObject(dto);
|
||||
/* bm is now a dangling pointer — the BitMap was freed */
|
||||
BltBitMap(bm, ...); /* CRASH */
|
||||
|
||||
/* CORRECT — either keep dto alive, or copy the BitMap */
|
||||
struct BitMap *myCopy = AllocBitMap(width, height, depth,
|
||||
BMF_CLEAR, screen->RastPort.BitMap);
|
||||
BltBitMap(bm, 0, 0, myCopy, 0, 0, width, height, 0xC0, 0x01, NULL);
|
||||
DisposeDTObject(dto);
|
||||
/* myCopy is now safe to use independently */
|
||||
```
|
||||
|
||||
#### 2. The Naked Dispose
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — disposing while still attached to window */
|
||||
DisposeDTObject(dtObj); /* Gadget list still references this object */
|
||||
CloseWindow(win); /* Intuition walks gadget list → CRASH */
|
||||
|
||||
/* CORRECT — remove first, then dispose */
|
||||
RemoveDTObject(win, dtObj);
|
||||
DisposeDTObject(dtObj);
|
||||
CloseWindow(win);
|
||||
```
|
||||
|
||||
#### 3. The Silent Failure
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — no error checking, no diagnostics */
|
||||
Object *dto = NewDTObject((APTR)path, TAG_DONE);
|
||||
AddDTObject(win, NULL, dto, -1); /* dto could be NULL */
|
||||
|
||||
/* CORRECT — check and report */
|
||||
Object *dto = NewDTObject((APTR)path,
|
||||
DTA_SourceType, DTST_FILE,
|
||||
TAG_DONE);
|
||||
if (!dto)
|
||||
{
|
||||
LONG err = IoErr();
|
||||
Printf("Failed to load '%s': %ld\n", path, err);
|
||||
return FALSE;
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. The Assuming Saver
|
||||
|
||||
```c
|
||||
/* ANTIPATTERN — calling DTM_WRITE on a read-only subclass */
|
||||
struct dtWrite dtw;
|
||||
dtw.dtw_Method = DTM_WRITE;
|
||||
dtw.dtw_GInfo = NULL;
|
||||
dtw.dtw_FileHandle = fh;
|
||||
dtw.dtw_Mode = DTWM_RAW;
|
||||
DoDTMethodA(dto, NULL, NULL, (Msg)&dtw); /* May fail silently */
|
||||
|
||||
/* CORRECT — check capabilities first */
|
||||
if (GetDTMethods(dto) & (1L << DTM_WRITE))
|
||||
{
|
||||
/* Safe to attempt write */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls & Common Mistakes
|
||||
|
||||
### 1. BitMap in Chip RAM for AGA/Display
|
||||
|
||||
```c
|
||||
/* PITFALL — assuming BitMap is displayable on custom screens */
|
||||
struct BitMap *bm;
|
||||
GetDTAttrs(dto, PDTA_BitMap, (ULONG)&bm, TAG_DONE);
|
||||
|
||||
/* On systems without graphics card, BitMap MUST be in Chip RAM
|
||||
for blitter or display DMA. DataTypes usually allocates correctly,
|
||||
but if you replace the BitMap, verify: */
|
||||
if (TypeOfMem(bm) & MEMF_CHIP)
|
||||
{
|
||||
/* Safe for display DMA */
|
||||
}
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> **Requires Chip RAM**: The `PDTA_BitMap` returned by pictureclass must reside in Chip RAM if you intend to use it with the Blitter or display it via custom chip DMA. Fast RAM bitmaps are fine for CPU-only processing but will crash if passed to `BltBitMap()` or attached to a `ViewPort` on OCS/ECS/AGA.
|
||||
|
||||
### 2. ColorMap Lifecycle
|
||||
|
||||
```c
|
||||
/* PITFALL — ColorMap freed with object, but ViewPort still references it */
|
||||
struct ColorMap *cm;
|
||||
GetDTAttrs(dto, PDTA_ColorMap, (ULONG)&cm, TAG_DONE);
|
||||
viewport->ColorMap = cm; /* ViewPort now references object's memory */
|
||||
DisposeDTObject(dto); /* cm is freed — ViewPort now has dangling pointer */
|
||||
|
||||
/* CORRECT — copy the ColorMap if the ViewPort outlives the object */
|
||||
struct ColorMap *cmCopy = CopyColorMap(cm);
|
||||
viewport->ColorMap = cmCopy;
|
||||
DisposeDTObject(dto);
|
||||
/* Free cmCopy later when ViewPort is torn down */
|
||||
```
|
||||
|
||||
### 3. Forgetting to Refresh After Resize
|
||||
|
||||
```c
|
||||
/* PITFALL — changing size attributes without refresh */
|
||||
SetDTAttrs(dtObj, win, NULL,
|
||||
DTA_NominalHoriz, 320,
|
||||
DTA_NominalVert, 200,
|
||||
TAG_DONE);
|
||||
/* Window still shows old size; visual garbage ensues */
|
||||
|
||||
/* CORRECT — trigger redraw */
|
||||
SetDTAttrs(dtObj, win, NULL,
|
||||
DTA_NominalHoriz, 320,
|
||||
DTA_NominalVert, 200,
|
||||
TAG_DONE);
|
||||
RefreshDTObjectA(dtObj, win, NULL, NULL);
|
||||
```
|
||||
|
||||
### 4. Mixing DataTypes with Custom Blitter Code
|
||||
|
||||
```c
|
||||
/* PITFALL — DataTypes BitMap may not match your RastPort's format */
|
||||
struct BitMap *bm;
|
||||
GetDTAttrs(dto, PDTA_BitMap, (ULONG)&bm, TAG_DONE);
|
||||
|
||||
/* If your RastPort is 3-plane and the image is 5-plane (HAM),
|
||||
direct BltBitMap() produces garbage. Always check depth: */
|
||||
struct BitMapHeader *bmh;
|
||||
GetDTAttrs(dto, PDTA_BitMapHeader, (ULONG)&bmh, TAG_DONE);
|
||||
if (bmh && bmh->bmh_Depth <= myRastPort->BitMap->Depth)
|
||||
{
|
||||
BltBitMap(bm, ...); /* Safe */
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Assuming DTA_Handle Ownership
|
||||
|
||||
```c
|
||||
/* PITFALL — library closes handle if it opened it, but not if you passed it */
|
||||
BPTR fh = Open(path, MODE_OLDFILE);
|
||||
Object *dto = NewDTObject(NULL,
|
||||
DTA_SourceType, DTST_FILE,
|
||||
DTA_Handle, fh,
|
||||
TAG_DONE);
|
||||
DisposeDTObject(dto);
|
||||
/* fh may or may not still be valid — behavior is inconsistent
|
||||
across subclasses. Always open/close yourself. */
|
||||
|
||||
/* CORRECT — let DataTypes open the file, or manage the handle yourself */
|
||||
Object *dto = NewDTObject((APTR)path,
|
||||
DTA_SourceType, DTST_FILE,
|
||||
TAG_DONE);
|
||||
DisposeDTObject(dto);
|
||||
/* File was opened and closed by the framework */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Real-World Software That Relied on DataTypes
|
||||
|
||||
| Software | How It Used DataTypes |
|
||||
|---|---|
|
||||
| **MultiView** (OS 3.1+) | The canonical DataTypes viewer — opens any file DataTypes can identify; essentially a thin wrapper around `NewDTObject()` + `AddDTObject()` |
|
||||
| **Directory Opus** | File viewer pane uses DataTypes for image/sound preview |
|
||||
| **Personal Paint** | Uses pictureclass for import of non-native formats (GIF, JPEG via third-party classes) |
|
||||
| **AmigaGuide** | The hypertext system is implemented as a DataType class; `amigaguideclass` renders pages and handles links |
|
||||
| **Web browsers (AWeb, IBrowse)** | Image decoding delegated to DataTypes; new image formats installable without browser updates |
|
||||
| **Audio players** | `STM_PLAY` trigger used for preview in file managers |
|
||||
|
||||
### Integration Patterns
|
||||
|
||||
**Pattern A: Thumbnail Generator**
|
||||
```
|
||||
For each file in directory:
|
||||
dto = NewDTObject(path, DTA_GroupID, GID_PICTURE)
|
||||
if dto:
|
||||
GetDTAttrs(dto, PDTA_BitMap, &bm, ...)
|
||||
ScaleBitMap(bm, thumbSize) /* custom downscale */
|
||||
DisposeDTObject(dto)
|
||||
```
|
||||
|
||||
**Pattern B: Format Validator**
|
||||
```
|
||||
dto = NewDTObject(path, TAG_DONE)
|
||||
if dto:
|
||||
GetDTAttrs(dto, DTA_DataType, &dtn, ...)
|
||||
printf("Type: %s Group: %s\n", dtn->dtn_Node.ln_Name, groupName[dtn->dtn_GroupID])
|
||||
DisposeDTObject(dto)
|
||||
else:
|
||||
printf("Unknown or corrupted file\n")
|
||||
```
|
||||
|
||||
**Pattern C: Clipboard Image Paste**
|
||||
```
|
||||
dto = NewDTObject(NULL, DTA_SourceType, DTST_CLIPBOARD, DTA_GroupID, GID_PICTURE)
|
||||
if dto:
|
||||
AddDTObject(win, NULL, dto, -1)
|
||||
RefreshDTObjectA(dto, win, NULL, NULL)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Can I write my own DataType class?**
|
||||
> Yes. You create a BOOPSI subclass of the appropriate superclass (e.g., pictureclass), implement `OM_NEW` to parse your format and fill the superclass structures, and install a descriptor in `DEVS:DataTypes/`. The OS will automatically route matching files to your class.
|
||||
|
||||
**Q: Why does `NewDTObject()` return NULL for a valid JPEG?**
|
||||
> Either `jpeg.datatype` is not installed, or the file's magic bytes do not match the descriptor's signature. Verify the class exists in `SYS:Classes/DataTypes/` and its descriptor is in `DEVS:DataTypes/`. Also check `IoErr()` immediately after failure.
|
||||
|
||||
**Q: How do I know which methods an object supports?**
|
||||
> Call `GetDTMethods(obj)` — it returns a bitmask of supported methods. Similarly, `GetDTTriggerMethods(obj)` returns supported triggers like `STM_PLAY`.
|
||||
|
||||
**Q: Is DataTypes available on OS 1.3?**
|
||||
> No. DataTypes requires OS 2.0 (V37) or later, as it depends on BOOPSI (introduced in 2.0). On 1.3, use iffparse.library or direct file I/O.
|
||||
|
||||
**Q: Can I use DataTypes without opening an Intuition window?**
|
||||
> Yes. `NewDTObject()` with `DTST_FILE` creates an off-screen object. Use `GetDTAttrs()` to extract `PDTA_BitMap` or `SDTA_Sample` and process the data directly. You only need a window if you call `AddDTObject()`.
|
||||
|
||||
**Q: Why is my embedded DataType object not redrawn after `SetDTAttrs()`?**
|
||||
> `SetDTAttrs()` updates internal state but does not send a gadget refresh message. You must explicitly call `RefreshDTObjectA()` or use `SetGadgetAttrs()` if the object is in a window context.
|
||||
|
||||
**Q: Do DataType objects work with MUI?**
|
||||
> Indirectly. MUI provides the `Boopsi` class to embed native BOOPSI gadgets. Create the DataType object, then embed it via `MUIA_Boopsi_Gadget` or `MUIA_Boopsi_Object` in a `BoopsiObject`.
|
||||
|
||||
## References
|
||||
|
||||
- NDK 3.9: `datatypes/datatypes.h`, `datatypes/datatypesclass.h`, `datatypes/pictureclass.h`, `datatypes/soundclass.h`, `datatypes/textclass.h`, `datatypes/animationclass.h`
|
||||
- ADCD 2.1: `datatypes.library` autodocs (`datatypes.doc`, `picture_dtc.doc`, `sound_dtc.doc`)
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* — Chapter 23: DataTypes
|
||||
- See also: [iffparse.md](iffparse.md) — Low-level IFF parsing when DataTypes abstraction is insufficient
|
||||
- See also: [boopsi.md](../09_intuition/boopsi.md) — BOOPSI object system foundation
|
||||
- See also: [bitmap.md](../08_graphics/bitmap.md) — Planar BitMap structure and Chip RAM requirements
|
||||
- See also: [memory_management.md](../06_exec_os/memory_management.md) — `AllocMem()` flags and Chip vs Fast RAM
|
||||
|
|
@ -123,23 +123,23 @@ FreeMem(buf, bufSize);
|
|||
| `AFF_DISK` | Available on `FONTS:` | Requires disk access to load |
|
||||
| `AFF_SCALED` | Algorithmically scaled from another size | Lower quality; avoid when native size exists |
|
||||
| `AFF_BITMAP` | Bitmap (pixel) font | Standard Amiga font format |
|
||||
| `AFF_TAGGED` | Tagged (OS 3.0+ extended) font | Supports colour fonts, outlined fonts |
|
||||
| `AFF_TAGGED` | Tagged (OS 3.0+ extended) font | Supports color fonts, outlined fonts |
|
||||
|
||||
---
|
||||
|
||||
## Colour Fonts (OS 3.0+)
|
||||
## Color Fonts (OS 3.0+)
|
||||
|
||||
OS 3.0 introduced **colour bitmap fonts** — each glyph can have multiple bitplanes:
|
||||
OS 3.0 introduced **color bitmap fonts** — each glyph can have multiple bitplanes:
|
||||
|
||||
```c
|
||||
/* Colour fonts use ColorTextFont — an extension of TextFont: */
|
||||
/* Color fonts use ColorTextFont — an extension of TextFont: */
|
||||
struct ColorTextFont {
|
||||
struct TextFont ctf_TF; /* standard TextFont */
|
||||
UWORD ctf_Flags; /* CT_COLORFONT etc. */
|
||||
UBYTE ctf_Depth; /* number of bitplanes */
|
||||
UBYTE ctf_FgColor; /* default foreground pen */
|
||||
UBYTE ctf_Low; /* lowest colour used */
|
||||
UBYTE ctf_High; /* highest colour used */
|
||||
UBYTE ctf_Low; /* lowest color used */
|
||||
UBYTE ctf_High; /* highest color used */
|
||||
APTR ctf_PlanePick; /* plane selection */
|
||||
APTR ctf_PlaneOnOff; /* plane on/off defaults */
|
||||
struct ColorFontColors *ctf_ColorTable;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
Every Workbench-visible file has a companion `.info` file containing its icon imagery, tool types (key=value metadata), default tool, stack size, and position. `icon.library` provides reading, writing, and manipulating these structures.
|
||||
|
||||
The `.info` file format is binary, not text. icon.library handles all serialisation.
|
||||
The `.info` file format is binary, not text. icon.library handles all serialization.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
|
|
@ -52,7 +52,7 @@ struct DiskObject {
|
|||
|
||||
## Icon Types
|
||||
|
||||
| Constant | Value | Description | Workbench Behaviour |
|
||||
| Constant | Value | Description | Workbench Behavior |
|
||||
|---|---|---|---|
|
||||
| `WBDISK` | 1 | Disk/volume icon | Opens drawer showing disk contents |
|
||||
| `WBDRAWER` | 2 | Drawer (directory) | Opens drawer window |
|
||||
|
|
@ -169,12 +169,12 @@ FreeDiskObject(newIcon);
|
|||
|
||||
## OS 3.5+ New-Style Icons
|
||||
|
||||
AmigaOS 3.5 introduced **true-colour icons** (PNG-based) alongside the legacy planar format. The `icon.library` v46+ handles both transparently — `GetDiskObject` returns the best available format.
|
||||
AmigaOS 3.5 introduced **true-color icons** (PNG-based) alongside the legacy planar format. The `icon.library` v46+ handles both transparently — `GetDiskObject` returns the best available format.
|
||||
|
||||
| Feature | Legacy (OS 1.x–3.1) | New-Style (OS 3.5+) |
|
||||
|---|---|---|
|
||||
| Format | Planar bitplane imagery | PNG/true-colour embedded |
|
||||
| Colours | 4–16 (Workbench palette) | 24-bit true colour |
|
||||
| Format | Planar bitplane imagery | PNG/true-color embedded |
|
||||
| Colors | 4–16 (Workbench palette) | 24-bit true color |
|
||||
| Size | Fixed (standard sizes) | Scalable |
|
||||
| Transparency | 1-bit mask | 8-bit alpha channel |
|
||||
| Storage | `do_Gadget.GadgetRender` | Extended chunks in `.info` |
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Common IFF types:
|
|||
flowchart TD
|
||||
subgraph "IFF ILBM File"
|
||||
FORM["FORM ILBM"] --> BMHD["BMHD<br/>(bitmap header)"]
|
||||
FORM --> CMAP["CMAP<br/>(colour palette)"]
|
||||
FORM --> CMAP["CMAP<br/>(color palette)"]
|
||||
FORM --> CAMG["CAMG<br/>(Amiga view mode)"]
|
||||
FORM --> BODY["BODY<br/>(pixel data)"]
|
||||
end
|
||||
|
|
@ -60,6 +60,344 @@ Nested FORMs:
|
|||
| `CAT ` | Unordered concatenation of FORMs |
|
||||
| `PROP` | Default property block (within LIST) |
|
||||
|
||||
### Chunk Wire Format
|
||||
|
||||
Every IFF chunk begins with an 8-byte header — a 4-character ASCII ID followed by a 4-byte big-endian size:
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────┐
|
||||
│ IFF Chunk Header (8 bytes) │
|
||||
├──────────┬──────────┬──────────┬──────────┬───────────┤
|
||||
│ Offset │ +$00 │ +$01 │ +$02 │ +$03 │
|
||||
├──────────┼──────────┴──────────┴──────────┴───────────┤
|
||||
│ ID │ 4-char ASCII FOURCC (e.g. 'B' 'M' 'H' 'D') │
|
||||
│ │ → ID_BMHD = 0x424D4844 │
|
||||
├──────────┼──────────┬──────────┬──────────┬───────────┤
|
||||
│ Offset │ +$04 │ +$05 │ +$06 │ +$07 │
|
||||
├──────────┼──────────┴──────────┴──────────┴───────────┤
|
||||
│ Size │ 32-bit BIG-ENDIAN unsigned LONG │
|
||||
│ │ (data bytes ONLY, excludes header & pad) │
|
||||
└──────────┴────────────────────────────────────────────┘
|
||||
|
||||
┌── Data bytes (0..Size-1) ──┐
|
||||
│ │
|
||||
│ [pad byte if Size is odd] │
|
||||
└────────────────────────────┘
|
||||
```
|
||||
|
||||
```c
|
||||
/* The raw chunk header — 8 bytes on disk */
|
||||
struct IFFChunkHeader {
|
||||
ULONG ck_ID; /* 4-char FOURCC, e.g. 'BMHD' = 0x424D4844 */
|
||||
LONG ck_Size; /* data length in bytes, BIG-ENDIAN */
|
||||
};
|
||||
```
|
||||
|
||||
| Field | Bytes | Type | Description |
|
||||
|---|---|---|---|
|
||||
| `ck_ID` | 0–3 | `ULONG` (big-endian) | FOURCC identifier. `0x424D4844` = `'B'<<24 \| 'M'<<16 \| 'H'<<8 \| 'D'` = "BMHD" |
|
||||
| `ck_Size` | 4–7 | `LONG` (big-endian) | Number of data bytes following the header. **Does not include** the 8-byte header or the optional pad byte. May be 0 for empty chunks |
|
||||
|
||||
**Pad byte rule**: If `ck_Size` is odd, a single zero-pad byte follows the data to restore even alignment. The pad byte is **not counted** in `ck_Size`. This ensures the next chunk header always starts at an even offset from the file beginning.
|
||||
|
||||
### Container Headers — FORM, LIST, CAT
|
||||
|
||||
Container chunks (FORM, LIST, CAT) have a **12-byte header** — the standard 8-byte chunk header plus a 4-byte form type:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ Container Chunk Header (12 bytes minimum) │
|
||||
├──────────┬──────────┬──────────┬──────────┬──────────────────────┤
|
||||
│ Offset │ +$00–$03 │ +$04–$07 │ +$08–$0B │ +$0C... │
|
||||
├──────────┼──────────┼──────────┼──────────┼──────────────────────┤
|
||||
│ Field │ ck_ID │ ck_Size │ FormType │ Sub-chunks... │
|
||||
│ │ "FORM" │ (includes│ "ILBM" │ │
|
||||
│ │ "LIST" │ FormType│ "FTXT" │ │
|
||||
│ │ "CAT " │ + subs) │ "PBM " │ │
|
||||
└──────────┴──────────┴──────────┴──────────┴──────────────────────┘
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> **Critical detail**: `ck_Size` in a container header **includes the 4-byte FormType plus all nested sub-chunks**. For example, a FORM ILBM with BMHD (20 bytes) + CMAP (48 bytes) + BODY (32,000 bytes) has `ck_Size = 4 + 20 + 48 + 32000 = 32072`. The 4-byte FormType is part of the counted size.
|
||||
|
||||
### Computing Offsets — Walking a File Manually
|
||||
|
||||
When reading an IFF file without iffparse.library, you walk chunks by reading the header and advancing:
|
||||
|
||||
```
|
||||
next_chunk_offset = current_offset + 8 + ck_Size + (ck_Size & 1)
|
||||
│ │ │ │
|
||||
│ │ │ └─ pad byte if odd
|
||||
│ │ └─────────── data bytes
|
||||
│ └────────────────── chunk header (8 bytes)
|
||||
└───────────────────────────────── where we are now
|
||||
```
|
||||
|
||||
```c
|
||||
/* Manual IFF chunk walker — for reading without iffparse.library */
|
||||
LONG WalkIFFChunks(BPTR fh)
|
||||
{
|
||||
LONG offset = 0;
|
||||
struct IFFChunkHeader hdr;
|
||||
|
||||
while (Read(fh, &hdr, sizeof(hdr)) == sizeof(hdr))
|
||||
{
|
||||
Printf("Offset $%08lx: chunk '%c%c%c%c', size=%ld\n",
|
||||
offset,
|
||||
(char)(hdr.ck_ID >> 24), (char)(hdr.ck_ID >> 16),
|
||||
(char)(hdr.ck_ID >> 8), (char)(hdr.ck_ID),
|
||||
hdr.ck_Size);
|
||||
|
||||
if (hdr.ck_ID == ID_FORM || hdr.ck_ID == ID_LIST ||
|
||||
hdr.ck_ID == ID_CAT)
|
||||
{
|
||||
/* Read the 4-char FormType after the header */
|
||||
ULONG formType;
|
||||
Read(fh, &formType, 4);
|
||||
Printf(" → Container type: '%c%c%c%c'\n",
|
||||
(char)(formType >> 24), (char)(formType >> 16),
|
||||
(char)(formType >> 8), (char)(formType));
|
||||
/* formType is included in ck_Size — adjust remaining data */
|
||||
LONG dataAfterType = hdr.ck_Size - 4;
|
||||
/* Skip the nested contents */
|
||||
Seek(fh, dataAfterType, OFFSET_CURRENT);
|
||||
offset += 8 + 4 + dataAfterType;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Regular chunk — skip its data + optional pad */
|
||||
Seek(fh, hdr.ck_Size, OFFSET_CURRENT);
|
||||
offset += 8 + hdr.ck_Size;
|
||||
}
|
||||
|
||||
/* Skip pad byte if data was odd-sized */
|
||||
if (hdr.ck_Size & 1)
|
||||
{
|
||||
Seek(fh, 1, OFFSET_CURRENT);
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
offset += 8; /* header already read, accounted in next loop */
|
||||
}
|
||||
return offset; /* total file size walked */
|
||||
}
|
||||
```
|
||||
|
||||
### Identifying Content Types
|
||||
|
||||
To determine what kind of data an IFF file contains, read the **top-level FormType** (the 4 bytes following the `FORM` header):
|
||||
|
||||
| Top-Level Signature | Content Type | Sub-chunks to Expect |
|
||||
|---|---|---|
|
||||
| `FORM .... ILBM` | Interleaved Bitmap image | `BMHD`, `CMAP`, `CAMG`(opt), `BODY`, `CRNG`(opt) |
|
||||
| `FORM .... 8SVX` | 8-bit Sampled Voice (audio) | `VHDR`, `BODY`, `CHAN`(opt), `NAME`(opt) |
|
||||
| `FORM .... ANIM` | Animation (ILBM frames with timing) | `ANHD`, sub-`FORM ILBM` frames, `DLTA`(opt) |
|
||||
| `FORM .... FTXT` | Formatted Text | `CHRS` — character data with style markers |
|
||||
| `FORM .... SMUS` | Simple Musical Score | `SHDR`, `TRAK` — tracker-style music data |
|
||||
| `FORM .... PBM ` | Planar BitMap (non-interleaved ILBM) | Same chunks as ILBM, but BODY is plane-contiguous |
|
||||
| `FORM .... AIFF` | Audio Interchange File Format | `COMM`, `SSND`, `MARK`(opt), `INST`(opt) |
|
||||
| `LIST .... FTXT` | AmigaGuide hypertext document | `PROP ILBM`, nested `FORM FTXT` + `FORM ILBM` |
|
||||
| `CAT .... ILBM` | Concatenated bitmap collection | Multiple `FORM ILBM` sub-chunks (sprite sheet) |
|
||||
|
||||
> [!NOTE]
|
||||
> The spaces after `CAT ` and `PBM ` are intentional — FOURCCs are always exactly 4 characters. `CAT ` = `0x43415420`, `PBM ` = `0x50424D20`.
|
||||
|
||||
**Quick identification in a hex editor**:
|
||||
```
|
||||
Offset 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ASCII
|
||||
------ ------------------------------------------------- -----
|
||||
$0000 46 4F 52 4D 00 00 7D 18 49 4C 42 4D 42 4D 48 44 FORM..}.ILBMBMHD
|
||||
└─"FORM"──┘ └─size──┘ └─"ILBM"─┘ └─"BMHD"──────┘
|
||||
ck_ID ck_Size FormType first sub-chunk
|
||||
= 32024 bytes total starts here
|
||||
```
|
||||
|
||||
### FTXT — Formatted Text
|
||||
|
||||
Unlike raw ASCII where every character is a single byte of glyph data, FTXT encodes **styled text** with inline formatting commands. The `CHRS` chunk contains a stream of characters interspersed with control codes that change font, color, alignment, and spacing:
|
||||
|
||||
| FTXT Command | Code | Effect |
|
||||
|---|---|---|
|
||||
| `\n` | `$0A` | Line break (same as ASCII LF) |
|
||||
| `\t` | `$09` | Tab (same as ASCII HT) |
|
||||
| **SetFont** | `$80 nn` | Switch to font number `nn` (pre-loaded via `FONS` chunk) |
|
||||
| **SetColor** | `$81 rr gg bb` | Set text color to 24-bit RGB |
|
||||
| **SetJustification** | `$82 mm` | `0`=left, `1`=center, `2`=right, `3`=full justify |
|
||||
| **SetLeftMargin** | `$83 wwww` | Set left margin in pixels (big-endian WORD) |
|
||||
| **SetRightMargin** | `$84 wwww` | Set right margin in pixels |
|
||||
| **Indent** | `$85 wwww` | Indent first line of paragraph |
|
||||
| **SetLeading** | `$86 wwww` | Line spacing in pixels |
|
||||
| **SetKerning** | `$87 nn` | `0`=off, `1`=on |
|
||||
| **PageBreak** | `$8C` | Force new page |
|
||||
| **CenterText** | `$8D` | Center current line |
|
||||
| **SetTabs** | `$8E nn wwww...` | Define `nn` tab stops (list of big-endian WORDs) |
|
||||
|
||||
```c
|
||||
/* A CHRS stream might look like this in memory: */
|
||||
UBYTE chrs[] = {
|
||||
0x82, 0x01, /* SetJustification → center */
|
||||
0x81, 0xFF, 0x00, 0x00, /* SetColor → red */
|
||||
'H', 'e', 'a', 'd', 'i', 'n', 'g', 0x0A,
|
||||
0x81, 0x00, 0x00, 0x00, /* SetColor → black */
|
||||
0x82, 0x00, /* SetJustification → left */
|
||||
'B', 'o', 'd', 'y', ' ', 't', 'e', 'x', 't', '.', 0x0A
|
||||
};
|
||||
```
|
||||
|
||||
**How FTXT differs from raw ASCII:**
|
||||
- Raw text is just bytes → glyphs. FTXT is a **serialized stream of layout instructions** that a renderer executes sequentially.
|
||||
- Font changes are explicit (`SetFont nn`) — you can mix proportional and monospace fonts in the same document.
|
||||
- Color is per-span, not per-character — `SetColor` applies to all subsequent text until the next `SetColor`.
|
||||
- The `FONS` chunk (optional) pre-declares which fonts the document needs, so the renderer can load them before parsing `CHRS`.
|
||||
|
||||
FTXT is the text layer underlying **AmigaGuide** hypertext. An AmigaGuide file (`LIST FTXT+ILBM`) is a collection of FTXT pages (each a `FORM FTXT`) with inline image references (`FORM ILBM`), linked by `@{"linkname"}` cross-reference markers embedded in the character stream.
|
||||
|
||||
### ANIM — Animation Format
|
||||
|
||||
An IFF ANIM file is physically a **sequence of ILBM frames** packaged in a single FORM, with an animation header (`ANHD`) that describes timing and playback parameters:
|
||||
|
||||
```
|
||||
FORM ANIM
|
||||
├── ANHD ← animation header (frame count, timing, mode)
|
||||
├── FORM ILBM ← frame 0 (full image — mandatory keyframe)
|
||||
├── DLTA ← frame 1 delta data (only changed pixels from frame 0)
|
||||
├── DLTA ← frame 2 delta data (only changed pixels from frame 1)
|
||||
├── ...
|
||||
└── FORM ILBM ← optional refresh keyframe every N frames
|
||||
```
|
||||
|
||||
| ANHD Field | Size | Description |
|
||||
|---|---|---|
|
||||
| `ah_Operation` | UBYTE | `0`=standard (DLTA are XOR deltas), `2`=long-delta mode (individual longword patches), `7`=set-delta (replace, not XOR) |
|
||||
| `ah_Mask` | UBYTE | Interleaving mask bits (which planes the delta covers) |
|
||||
| `ah_Width/ah_Height` | UWORD | Frame dimensions |
|
||||
| `ah_Left/ah_Top` | WORD | Frame offset within display |
|
||||
| `ah_AbsTime` | ULONG | Frame display time in **jiffies** (1/60s NTSC, 1/50s PAL) |
|
||||
| `ah_RelTime` | ULONG | Inter-frame delay in jiffies |
|
||||
| `ah_Interleave` | UBYTE | `0`=no interleaving, `1`=interleaved storage |
|
||||
| `ah_Pad0/ah_Pad1` | UBYTE | Padding for alignment |
|
||||
| `ah_Flags` | ULONG | `1`=interleaved, `2`=half-brite mode |
|
||||
|
||||
**How ANIM works physically:**
|
||||
|
||||
1. **Frame 0 is always a full ILBM** — it serves as the reference image and defines the palette (CMAP) for the entire animation.
|
||||
2. **Subsequent frames are DLTA chunks** — they contain only the pixels that *changed* from the previous frame, encoded as (offset, data) pairs. This is essentially a binary diff format at the pixel level.
|
||||
3. **DLTA data is XOR-applied** — the decoder reads the previous frame's pixel data, XORs the delta bytes on top, and produces the new frame. XOR means you can animate both directions (a pixel that was set can be cleared).
|
||||
4. **Refresh frames** (full ILBMs) appear periodically to prevent accumulated delta errors and to allow seeking.
|
||||
|
||||
> [!WARNING]
|
||||
> ANIM frames share a single palette (CMAP from frame 0). If your frames have different palettes, you cannot use IFF ANIM — use a custom multi-ILBM format or ANIM5/ANIM7 extensions (rare).
|
||||
|
||||
**Performance characteristics:** On a stock A500 (68000), ANIM playback can achieve ~6–10 FPS at 320×200×5 bitplanes using standard DLTA mode. Long-delta mode (`ah_Operation=2`) is faster on 68020+ because it copies aligned longwords rather than byte-by-byte XOR. The ANIM format was used by Deluxe Paint's "Anim" feature, SCALA multimedia presentations, and countless Amiga game cutscenes.
|
||||
|
||||
> See also: [animation.md](../08_graphics/animation.md) — the GEL system (BOBs, VSprites, AnimObs) for real-time sprite animation, which is a completely different mechanism from IFF ANIM file playback.
|
||||
|
||||
### 8SVX — 8-bit Sampled Voice
|
||||
|
||||
8SVX is the Amiga's native digital audio format — equivalent to what WAV became on Windows, but simpler and with chunk-based extensibility built in from the start.
|
||||
|
||||
```
|
||||
FORM 8SVX
|
||||
├── VHDR ← voice header (sample rate, volume, compression)
|
||||
├── NAME ← optional sample name string
|
||||
├── CHAN ← optional panning/volume per channel
|
||||
└── BODY ← raw sample data (signed 8-bit PCM, or compressed)
|
||||
```
|
||||
|
||||
| VHDR Field | Size | Description |
|
||||
|---|---|---|
|
||||
| `vh_OneShotHiSamples` | ULONG | Number of samples (big-endian) — hi word of 32-bit count |
|
||||
| `vh_RepeatHiSamples` | ULONG | Repeat offset for looping instruments |
|
||||
| `vh_SamplesPerCycle` | ULONG | Playback rate in **Hz** (e.g., 22050, 11025, 8363) |
|
||||
| `vh_Octaves` | UWORD | Number of frequency octaves (usually 1) |
|
||||
| `vh_Compression` | ULONG | `0`=signed 8-bit PCM, `1`=Fibonacci delta, `2`=Exponential delta |
|
||||
| `vh_Volume` | ULONG | Playback volume (0–65535, linear scale) |
|
||||
|
||||
**8SVX vs WAV analogies:**
|
||||
|
||||
| Aspect | IFF 8SVX (1985) | RIFF WAV (1991) |
|
||||
|---|---|---|
|
||||
| **Container** | IFF FORM (big-endian) | RIFF chunk (little-endian) |
|
||||
| **Sample data chunk** | `BODY` | `data` |
|
||||
| **Header chunk** | `VHDR` (20 bytes, fixed layout) | `fmt ` (variable size, extensible) |
|
||||
| **Sample format** | Signed 8-bit PCM only (in practice) | 8/16/24/32-bit, PCM or float |
|
||||
| **Compression** | Fibonacci delta (lossy), Exponential delta (lossy) | μ-law, ADPCM, MP3 (in theory) |
|
||||
| **Loop points** | `vh_RepeatHiSamples` + `vh_OneShotHiSamples` define a sustain loop | `smpl` chunk with loop points |
|
||||
| **Multi-channel** | One sample per 8SVX; stereo = two 8SVX files | Multi-channel interleaved in single file |
|
||||
| **Legacy** | Died with the Amiga; converted to WAV via SoX/ffmpeg | Still the universal PCM container |
|
||||
|
||||
> [!WARNING]
|
||||
> 8SVX samples are **signed 8-bit** (range -128 to +127). If you read them as unsigned (0–255), every sample shifts by 128 — silence becomes a loud DC offset. This is the #1 cause of "8SVX sounds like static" bug reports.
|
||||
|
||||
**Fibonacci delta compression** (`vh_Compression=1`) encodes the difference between consecutive samples using Fibonacci-encoded values. It achieves ~4:1 compression on typical speech, but is lossy — repeated encode/decode cycles degrade quality. Most modern tools convert 8SVX to WAV on import rather than handling Fibonacci delta natively.
|
||||
|
||||
> See also: [audio.md](../10_devices/audio.md) — audio.device DMA channel programming for real-time sample playback on Paula.
|
||||
|
||||
### Nested Containers in Practice
|
||||
|
||||
An ILBM file is a single FORM, but IFF supports deep nesting for multi-object documents:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph TOP["LIST FTXT+ILBM (hypertext document)"]
|
||||
direction LR
|
||||
P["PROP ILBM<br/>(shared palette, mode)"]
|
||||
F1["FORM FTXT<br/>Heading"]
|
||||
F2["FORM ILBM<br/>Inline image 1"]
|
||||
F3["FORM FTXT<br/>Body text"]
|
||||
F4["FORM ILBM<br/>Inline image 2"]
|
||||
end
|
||||
|
||||
subgraph INSIDE["Inside each FORM ILBM"]
|
||||
direction TB
|
||||
BM["BMHD"] --> CM["CMAP"]
|
||||
CM --> BO["BODY"]
|
||||
end
|
||||
|
||||
TOP -.-> INSIDE
|
||||
|
||||
style P fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style TOP fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
| Container | Use Case | iffparse Behavior |
|
||||
|---|---|---|
|
||||
| `FORM ILBM` | Single image | `StopChunk(iff, ID_ILBM, ID_BMHD)` stops at BMHD within this FORM only |
|
||||
| `LIST FTXT+ILBM` | AmigaGuide article with inline images | `ParseIFF` descends into LIST, then into each FORM — context node stack tracks nesting |
|
||||
| `CAT ILBM` | Sprite sheet (unrelated images concatenated) | Same API as LIST; `CurrentChunk()` returns the innermost context |
|
||||
|
||||
iffparse.library maintains an internal **context stack**. Each `PushChunk`/entry into a FORM pushes a new `ContextNode`, and each `PopChunk`/exit restores the parent. The `cn_Type` field tells you whether you're inside a FORM (`IFF_FORMTYPE`), LIST (`IFF_LISTTYPE`), or CAT (`IFF_CATTYPE`).
|
||||
|
||||
### IFF Design Philosophy
|
||||
|
||||
IFF was not designed as an image format — it was designed as a **universal data interchange container**. Jerry Morrison of Electronic Arts authored the "EA IFF 85" specification (January 14, 1985) with a single goal: files that could move between applications, operating systems, and even different computers without losing structure. Every IFF file self-describes its contents via FOURCC chunk IDs (4-character ASCII codes like `BMHD`, `CMAP`, `BODY`). An application that encounters an unknown chunk simply **skips it** by reading its size and advancing the file pointer — no error, no corruption, just graceful ignorance.
|
||||
|
||||
This "skip what you don't understand" contract is the single most important design decision in IFF. It means a file created by Deluxe Paint in 1986 with a custom `DPAN` chunk (DPaint animation settings) can be opened by a modern image viewer — the viewer skips `DPAN` and renders the image from `BMHD` + `CMAP` + `BODY`. This is exactly how DataTypes descriptors work a decade later, and it's the same philosophy behind PNG's ancillary chunks and XML namespaces.
|
||||
|
||||
### IFF vs Contemporary Formats (1984–1990)
|
||||
|
||||
| Format | Year | Platform | Type | Extensible? | Compression | Palette | Multi-Image | Byte Order |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| **IFF ILBM** | 1985 | Amiga | Nested container | ✅ Yes — unknown chunks skipped safely | ByteRun1 RLE (optional) | 1–256 colors (CMAP) | ✅ Yes (LIST, CAT) | Big-Endian |
|
||||
| **MacPaint** | 1984 | Macintosh | Fixed bitmap | ❌ No — 576×720 only, no header version | None | 1-bit only | ❌ No | Big-Endian |
|
||||
| **PCX** | 1984 | PC DOS (ZSoft) | Single image | ❌ No — fixed header layout | RLE (mandatory) | 1–256 colors (EGA/VGA) | ❌ No | Little-Endian |
|
||||
| **BMP (DIB)** | 1985 | Windows 1.0 | Single image | ⚠️ Partial — later versions added header fields but no chunk skip | RLE (rarely used) | 1–256 colors (palette) | ❌ No | Little-Endian |
|
||||
| **GIF 87a** | 1987 | CompuServe | Multi-image (non-animated) | ⚠️ Partial — extension blocks in GIF89a | LZW (mandatory) | 1–256 colors (global + local palette) | ✅ Yes (multiple images) | Little-Endian |
|
||||
| **TIFF** | 1986 | Aldus (Mac/PC) | Tag-based container | ✅ Yes — IFD tags with skip rule | Multiple codecs (RLE, LZW, CCITT, JPEG later) | 1–24-bit (tag-defined) | ✅ Yes (multiple IFDs) | Both (magic: `II`/`MM`) |
|
||||
| **IMG (GEM)** | 1985 | Atari ST (Digital Research) | Single image | ❌ No — rigid header | None (later versions: PackBits) | 1–16 colors | ❌ No | Big-Endian |
|
||||
|
||||
**Key takeaways from the era:**
|
||||
|
||||
1. **IFF was the only format that was a true container from day one.** BMP, PCX, and MacPaint were rigid: one file = one image with a fixed header. IFF could hold multiple FORMs in a LIST, nest metadata inside PROP chunks, and intermix text (FTXT) with images (ILBM) in the same file — essential for AmigaGuide hypertext.
|
||||
|
||||
2. **IFF and TIFF were the only extensible formats.** TIFF's tag-based approach (IFD entries with numeric tags) was more flexible than IFF's FOURCC chunks, but also more complex — a TIFF reader must parse the IFD chain before it can skip unknown tags. An IFF reader skips unknown chunks with zero metadata lookup. Both, however, shared the same core insight: **a format that can't grow is already dead.**
|
||||
|
||||
3. **PCX and GIF owned the PC world for different reasons.** PCX was dead-simple — a 128-byte header followed by RLE data — and every DOS paint program supported it. GIF owned online services (CompuServe) and the early web because LZW compression produced smaller files than RLE on synthetic graphics, and the 87a spec included multi-image support before any other format except IFF.
|
||||
|
||||
4. **BMP won by platform monopoly, not technical merit.** BMP (Device Independent Bitmap) was Windows' native image format. It had no compression in practice (RLE was specified but almost never used), no extensibility, and no multi-image support. It survived because every Windows application was forced to support it. IFF was technically superior in every dimension — but Microsoft's platform dominance made BMP the default, just as it made WAV (RIFF) the default over IFF 8SVX.
|
||||
|
||||
5. **Byte order was a religious war.** IFF and MacPaint used big-endian (Motorola 68000 native). PCX, BMP, and GIF used little-endian (Intel x86 native). TIFF supported both with a magic-number flag. IFF's big-endian choice was natural for the Amiga but created an eternal annoyance for cross-platform tools — reading an IFF file on a PC required byte-swapping every LONG and WORD.
|
||||
|
||||
---
|
||||
|
||||
## Reading an IFF File
|
||||
|
|
@ -102,8 +440,8 @@ while ((error = ParseIFF(iff, IFFPARSE_SCAN)) == 0)
|
|||
{
|
||||
UBYTE palette[256 * 3];
|
||||
LONG palSize = ReadChunkBytes(iff, palette, cn->cn_Size);
|
||||
LONG numColours = palSize / 3;
|
||||
Printf("Palette: %ld colours\n", numColours);
|
||||
LONG numColors = palSize / 3;
|
||||
Printf("Palette: %ld colors\n", numColors);
|
||||
break;
|
||||
}
|
||||
case ID_CAMG:
|
||||
|
|
@ -149,8 +487,8 @@ WriteChunkBytes(iff, &bmhd, sizeof(bmhd));
|
|||
PopChunk(iff);
|
||||
|
||||
/* Write CMAP chunk: */
|
||||
PushChunk(iff, 0, ID_CMAP, numColours * 3);
|
||||
WriteChunkBytes(iff, palette, numColours * 3);
|
||||
PushChunk(iff, 0, ID_CMAP, numColors * 3);
|
||||
WriteChunkBytes(iff, palette, numColors * 3);
|
||||
PopChunk(iff);
|
||||
|
||||
/* Write BODY chunk: */
|
||||
|
|
@ -180,7 +518,7 @@ struct BitMapHeader {
|
|||
UBYTE bmh_Masking; /* 0=none, 1=hasMask, 2=hasTransparentColor */
|
||||
UBYTE bmh_Compression; /* 0=none, 1=ByteRun1 */
|
||||
UBYTE bmh_Pad;
|
||||
UWORD bmh_Transparent; /* transparent colour index */
|
||||
UWORD bmh_Transparent; /* transparent color index */
|
||||
UBYTE bmh_XAspect; /* pixel aspect ratio */
|
||||
UBYTE bmh_YAspect;
|
||||
WORD bmh_PageWidth; /* source page width */
|
||||
|
|
@ -228,15 +566,67 @@ void DecompressByteRun1(UBYTE *src, UBYTE *dst, LONG dstSize)
|
|||
|
||||
---
|
||||
|
||||
## Common Chunk IDs
|
||||
## Reading Planar Bitmap Data from BODY
|
||||
|
||||
After decompressing the BODY chunk, you have **interleaved bitplanes** — row 0 of plane 0, row 0 of plane 1, ..., row 0 of plane n, then row 1 of plane 0, ... This is the raw ILBM layout. To build a usable `BitMap` structure, you must deinterleave and convert to Amiga bitplane order.
|
||||
|
||||
### Deinterleaving the BODY
|
||||
|
||||
```c
|
||||
/* Given a decompressed BODY buffer and a BMHD header,
|
||||
* fill a struct BitMap with per-plane pointers: */
|
||||
struct BitMap *BuildBitMap(UBYTE *bodyData, struct BitMapHeader *bmhd)
|
||||
{
|
||||
struct BitMap *bm = AllocMem(sizeof(struct BitMap), MEMF_CLEAR);
|
||||
ULONG widthBytes = (bmhd->bmh_Width + 15) / 16 * 2; /* word-aligned row width */
|
||||
ULONG planeSize = widthBytes * bmhd->bmh_Height;
|
||||
UBYTE depth = bmhd->bmh_Depth;
|
||||
|
||||
InitBitMap(bm, depth, widthBytes * 8, bmhd->bmh_Height);
|
||||
|
||||
/* Allocate each plane separately (Chip RAM if DMA needed): */
|
||||
for (UBYTE p = 0; p < depth; p++)
|
||||
{
|
||||
bm->Planes[p] = AllocMem(planeSize, MEMF_CHIP | MEMF_CLEAR);
|
||||
if (!bm->Planes[p]) { /* OOM — free prior planes */ }
|
||||
}
|
||||
|
||||
/* Deinterleave: ILBM stores row-interleaved, Amiga wants per-plane contiguous */
|
||||
for (ULONG y = 0; y < bmhd->bmh_Height; y++)
|
||||
{
|
||||
for (UBYTE p = 0; p < depth; p++)
|
||||
{
|
||||
/* Source: p-th plane of this row in interleaved BODY */
|
||||
UBYTE *src = bodyData + (y * depth + p) * widthBytes;
|
||||
/* Dest: row y of plane p */
|
||||
UBYTE *dst = bm->Planes[p] + y * widthBytes;
|
||||
memcpy(dst, src, widthBytes);
|
||||
}
|
||||
}
|
||||
|
||||
return bm;
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The ILBM BODY interleaves by **(row × depth)**, not by depth-first. If you get the deinterleaving wrong, you'll see "striped" images where each group of depth rows came from the wrong plane.
|
||||
|
||||
### PBM (Planar BitMap) vs ILBM
|
||||
|
||||
| Aspect | ILBM (Interleaved) | PBM (Planar) |
|
||||
|---|---|---|
|
||||
| **Channel order** | Row 0 → all planes → Row 1 → all planes... | Plane 0 → all rows → Plane 1 → all rows... |
|
||||
| **Faster for...** | Rendering one row at a time (line-by-line drawing) | Blitter copy (`BltBitMap`) — each plane is contiguous |
|
||||
| **BODY layout** | Default ILBMalen format | `BODY` chunk identical, but "PBM " FORM type signals planar intent |
|
||||
| **iffparse support** | Standard `ID_ILBM` + `ID_BMHD` + `ID_BODY` | Same parsing; `ID_PBM` chunk ID (`0x50424D20`) if present |
|
||||
|
||||
| FORM Type | Chunk | Size | Description |
|
||||
|---|---|---|---|
|
||||
| `ILBM` | `BMHD` | 20 | Bitmap header (width, height, depth, compression) |
|
||||
| `ILBM` | `CMAP` | n×3 | Colour map (R,G,B triples, 8-bit each) |
|
||||
| `ILBM` | `CMAP` | n×3 | Color map (R,G,B triples, 8-bit each) |
|
||||
| `ILBM` | `CAMG` | 4 | Amiga display mode (ModeID for ViewPort) |
|
||||
| `ILBM` | `BODY` | varies | Pixel data (interleaved bitplanes) |
|
||||
| `ILBM` | `CRNG` | 8 | Colour cycling range (DPaint) |
|
||||
| `ILBM` | `CRNG` | 8 | Color cycling range (DPaint) |
|
||||
| `ILBM` | `GRAB` | 4 | Hotspot (cursor/brush grab point) |
|
||||
| `8SVX` | `VHDR` | 20 | Voice header (rate, volume, octaves) |
|
||||
| `8SVX` | `BODY` | varies | Audio sample data (signed 8-bit) |
|
||||
|
|
@ -263,9 +653,379 @@ FreeIFF(iff);
|
|||
|
||||
---
|
||||
|
||||
## When to Use IFFParse vs DataTypes
|
||||
|
||||
Both `iffparse.library` and the [DataTypes](datatypes.md) system can load IFF ILBM images, but they serve different needs:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
START["Need to read an IFF file?"]
|
||||
|
||||
Q1{"Do you know the exact<br/>internal structure?"}
|
||||
Q2{"Need clipboard/<br/>drag-and-drop?"}
|
||||
Q3{"Loading non-IFF<br/>formats (PNG, GIF)?"}
|
||||
Q4{"Memory budget<br/>under 64 KB?"}
|
||||
|
||||
IFF["✅ iffparse.library<br/>Fine-grained control<br/>Lower overhead"]
|
||||
DT["✅ datatypes.library<br/>Format-agnostic<br/>Auto-detection"]
|
||||
|
||||
START --> Q1
|
||||
Q1 -->|Yes| Q4
|
||||
Q1 -->|No| DT
|
||||
Q4 -->|Yes| IFF
|
||||
Q4 -->|No| Q2
|
||||
Q2 -->|Yes| DT
|
||||
Q2 -->|No| Q3
|
||||
Q3 -->|Yes| DT
|
||||
Q3 -->|No| IFF
|
||||
|
||||
style IFF fill:#e8f5e9,stroke:#4caf50,color:#333
|
||||
style DT fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
| Criterion | iffparse.library | datatypes.library |
|
||||
|---|---|---|
|
||||
| **Parsing granularity** | Per-chunk — you decide what to read and when | Per-object — library reads everything |
|
||||
| **Format support** | Any IFF file (ILBM, 8SVX, ANIM, FTXT, custom) | IFF + PNG, GIF, JPEG, WAV, AIFF (via subclass) |
|
||||
| **Stream source** | DOS file, clipboard, memory (custom hook) | DOS file, clipboard |
|
||||
| **Writing** | Full control via `PushChunk`/`PopChunk` | Not supported — DataTypes is read-only |
|
||||
| **Memory overhead** | ~2 KB for IFFHandle + context stack | ~20–50 KB for BOOPSI object + decoded bitmap |
|
||||
| **API complexity** | 10 functions, manual state management | 3 functions, Booleans for auto-everything |
|
||||
| **Best for** | IFF-specific tools, crunchers, conversion utilities | Application file loading, thumbnails, format-agnostic viewers |
|
||||
|
||||
> [!NOTE]
|
||||
> If you need to **write** files in any format, use iffparse.library. DataTypes cannot serialize objects back to disk — it's strictly a reader.
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls & Common Mistakes
|
||||
|
||||
### 1. Forgetting the Odd-Byte Padding
|
||||
|
||||
Every chunk's data must be padded to an even byte boundary. When writing, iffparse handles this automatically. When reading raw bytes outside iffparse (manual parsing), you must account for the pad byte yourself:
|
||||
|
||||
```c
|
||||
/* BAD: Hard-coding chunk position without pad accounting */
|
||||
LONG chunkSize = ReadLong(fh); /* e.g., 21 bytes */
|
||||
Read(fh, buffer, chunkSize); /* reads 21 bytes */
|
||||
LONG nextChunkID = ReadLong(fh); /* WRONG: if 21 is odd, this reads the pad byte as ID! */
|
||||
|
||||
/* CORRECT: Skip pad byte if chunk size is odd */
|
||||
LONG chunkSize = ReadLong(fh);
|
||||
Read(fh, buffer, chunkSize);
|
||||
if (chunkSize & 1) Read(fh, &pad, 1); /* consume pad byte */
|
||||
LONG nextChunkID = ReadLong(fh); /* now at correct position */
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> iffparse.library's `ReadChunkBytes` handles padding internally, but only if you use the library's own `InitIFFasDOS` hook. If you provide custom stream hooks, you are responsible for pad-byte alignment.
|
||||
|
||||
### 2. Reading Chunk Size as Little-Endian
|
||||
|
||||
The 68000 is **big-endian**. IFF sizes are also big-endian (network byte order). If you cast chunk bytes to a LONG without byte-swapping on a little-endian system (cross-compiled test harness, PC-hosted IFF validator), you'll read garbage:
|
||||
|
||||
```c
|
||||
/* DANGEROUS on x86 test rigs: */
|
||||
LONG size = *(LONG *)chunkHeader; /* bytes are 00 00 00 14 → x86 reads 0x14000000! */
|
||||
|
||||
/* SAFE: always use explicit big-endian parse */
|
||||
LONG size = (chunk[0] << 24) | (chunk[1] << 16) | (chunk[2] << 8) | chunk[3];
|
||||
```
|
||||
|
||||
### 3. Missing StopChunk Registration
|
||||
|
||||
If you don't call `StopChunk()` for a chunk type, `ParseIFF(IFFPARSE_SCAN)` skips right over it. This is by design, but it's a common surprise:
|
||||
|
||||
```c
|
||||
/* Only BMHD and BODY are registered — CMAP is silently skipped! */
|
||||
StopChunk(iff, ID_ILBM, ID_BMHD);
|
||||
StopChunk(iff, ID_ILBM, ID_BODY);
|
||||
/* Missing: StopChunk(iff, ID_ILBM, ID_CMAP); */
|
||||
|
||||
while (ParseIFF(iff, IFFPARSE_SCAN) == 0) {
|
||||
/* Will see BMHD → BODY, never CMAP */
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Closing the DOS Filehandle Before CloseIFF
|
||||
|
||||
`CloseIFF` may need to seek or read trailer data. If you close the underlying file handle first, `CloseIFF` operates on a stale/garbage `iff_Stream`:
|
||||
|
||||
```c
|
||||
/* BAD: Stream closed before library */
|
||||
Close((BPTR)iff->iff_Stream); /* gone! */
|
||||
CloseIFF(iff); /* operates on freed handle → undefined behavior */
|
||||
|
||||
/* CORRECT: Library first, then stream */
|
||||
CloseIFF(iff);
|
||||
Close((BPTR)iff->iff_Stream);
|
||||
FreeIFF(iff);
|
||||
```
|
||||
|
||||
### 5. EHB / Extra-HalfBrite Pitfall
|
||||
|
||||
EHB mode uses 64 colors where the upper 32 are half-brightness versions of the lower 32. If `CAMG` indicates EHB (`$80`), the CMAP chunk still has only 64 entries — but `bmh_Depth` is 6, not 5. Relying on `bmh_Depth` to calculate palette size will give wrong results:
|
||||
|
||||
```c
|
||||
/* BAD: bmh_Depth==6 for EHB suggests 2^6=64 colors, but only 64 are stored */
|
||||
LONG expectedColors = 1 << bmh_Depth; /* 64 — correct by coincidence */
|
||||
|
||||
/* CORRECT: CMAP chunk size determines real color count */
|
||||
LONG numColors = cn->cn_Size / 3; /* always accurate */
|
||||
|
||||
/* Extra check: EHB flag in CAMG */
|
||||
if (viewMode & EXTRA_HALFBRITE)
|
||||
Printf("EHB mode: 32 base colors + 32 half-bright variants\n");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Named Antipatterns
|
||||
|
||||
### 6. "The Blind Write": Forgetting IFFSIZE_UNKNOWN
|
||||
|
||||
When writing an IFF file where the body size isn't known until after encoding, you must use `IFFSIZE_UNKNOWN`. Pushing with a hard-coded size that turns out wrong produces a corrupt file:
|
||||
|
||||
```c
|
||||
/* BAD: Guessing size before encoding */
|
||||
PushChunk(iff, 0, ID_BODY, 32000); /* hope this is enough */
|
||||
WriteChunkBytes(iff, data, actualSize); /* actualSize might be 48000! */
|
||||
/* iffparse writes only min(32000, actualSize) — data loss */
|
||||
|
||||
/* CORRECT: Let iffparse handle unknown sizes */
|
||||
PushChunk(iff, 0, ID_BODY, IFFSIZE_UNKNOWN);
|
||||
WriteChunkBytes(iff, data, actualSize);
|
||||
PopChunk(iff); /* iffparse seeks back and patches the correct size */
|
||||
```
|
||||
|
||||
### 7. "The Leaky Parser": Skipping FreeIFF
|
||||
|
||||
`AllocIFF` allocates an `IFFHandle` plus internal buffers. Forgetting to call `FreeIFF` leaks memory even if the file was closed. This is particularly insidious in image batch converters:
|
||||
|
||||
```c
|
||||
/* BAD: Loop that leaks an IFFHandle per iteration */
|
||||
for (int i = 0; i < numFiles; i++) {
|
||||
struct IFFHandle *iff = AllocIFF();
|
||||
/* ... parse one file ... */
|
||||
CloseIFF(iff);
|
||||
Close((BPTR)iff->iff_Stream);
|
||||
/* Missing FreeIFF(iff) — leaks ~2 KB per file! */
|
||||
}
|
||||
|
||||
/* CORRECT: Always free the handle */
|
||||
for (int i = 0; i < numFiles; i++) {
|
||||
struct IFFHandle *iff = AllocIFF();
|
||||
/* ... parse ... */
|
||||
CloseIFF(iff);
|
||||
Close((BPTR)iff->iff_Stream);
|
||||
FreeIFF(iff); /* return handle to system */
|
||||
}
|
||||
```
|
||||
|
||||
### 8. "The Chunk Swallower": Reading More Than cn_Size
|
||||
|
||||
`ReadChunkBytes` returns the actual number of bytes read, which may be less than requested if you asked for more than the chunk contains. The `ContextNode->cn_Size` is authoritative:
|
||||
|
||||
```c
|
||||
/* BAD: Blind ReadChunkBytes for fixed-size struct without checking */
|
||||
struct BitMapHeader bmhd;
|
||||
ReadChunkBytes(iff, &bmhd, sizeof(bmhd)); /* what if chunk is smaller? */
|
||||
|
||||
/* CORRECT: Respect cn_Size */
|
||||
struct BitMapHeader bmhd;
|
||||
LONG toRead = min(cn->cn_Size, sizeof(bmhd));
|
||||
LONG actuallyRead = ReadChunkBytes(iff, &bmhd, toRead);
|
||||
if (actuallyRead < toRead)
|
||||
Printf("Warning: truncated BMHD chunk\n");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Can I parse two IFF files concurrently?**
|
||||
|
||||
Yes. Each `AllocIFF()` returns an independent `IFFHandle`. You can interleave `ParseIFF` calls on two handles without interference — useful for comparing or merging IFF files.
|
||||
|
||||
**Q: What happens if I don't register StopChunk for BODY?**
|
||||
|
||||
`ParseIFF` skips the pixel data entirely and proceeds to the next chunk (or end of FORM). This is actually useful for extracting only metadata (BMHD, CMAP) from large images without allocating memory for the BODY.
|
||||
|
||||
**Q: Can I use iffparse to validate an IFF file structure?**
|
||||
|
||||
Yes. Run `ParseIFF(iff, IFFPARSE_SCAN)` until it returns `IFFERR_EOC` (end of context). If it returns any other error before that, the file is malformed. Use `IFFPARSE_STEP` instead of `IFFPARSE_SCAN` to walk one level at a time for detailed error reporting.
|
||||
|
||||
**Q: Does iffparse handle AmigaGuide files?**
|
||||
|
||||
Yes — AmigaGuide `.guide` files are IFF `FORM FTXT+ILBM` under the hood. `iffparse.library` can parse them at the chunk level, but [amigaguide.library](amigaguide.md) provides a higher-level API for displaying and navigating AmigaGuide documents.
|
||||
|
||||
**Q: Why does my 8SVX file sound like static after parsing with iffparse?**
|
||||
|
||||
8SVX samples are **signed 8-bit** PCM. If you treat them as unsigned (common on PC sound APIs), the zero-crossing shifts polarity. The `VHDR` chunk's `vh_Compression` field tells you the encoding: 0 = signed 8-bit PCM, 1 = Fibonacci delta, 2 = Exponential delta.
|
||||
|
||||
---
|
||||
|
||||
## Use-Case Cookbook
|
||||
|
||||
### 1. Metadata Extractor — Read Only BMHD and CMAP
|
||||
|
||||
For thumbnail generation or file cataloging, skip the BODY entirely:
|
||||
|
||||
```c
|
||||
struct IFFHandle *iff = AllocIFF();
|
||||
iff->iff_Stream = (ULONG)Open(file, MODE_OLDFILE);
|
||||
InitIFFasDOS(iff);
|
||||
OpenIFF(iff, IFFF_READ);
|
||||
|
||||
/* Register only metadata chunks — BODY is skipped automatically */
|
||||
StopChunk(iff, ID_ILBM, ID_BMHD);
|
||||
StopChunk(iff, ID_ILBM, ID_CMAP);
|
||||
|
||||
LONG error;
|
||||
while ((error = ParseIFF(iff, IFFPARSE_SCAN)) == 0)
|
||||
{
|
||||
struct ContextNode *cn = CurrentChunk(iff);
|
||||
if (cn->cn_ID == ID_BMHD) {
|
||||
ReadChunkBytes(iff, &meta.bmhd, sizeof(meta.bmhd));
|
||||
} else if (cn->cn_ID == ID_CMAP) {
|
||||
meta.palSize = ReadChunkBytes(iff, meta.palette, cn->cn_Size);
|
||||
}
|
||||
}
|
||||
|
||||
CloseIFF(iff); Close((BPTR)iff->iff_Stream); FreeIFF(iff);
|
||||
```
|
||||
|
||||
### 2. Batch IFF-to-Raw Converter
|
||||
|
||||
```c
|
||||
void ConvertILBMtoRaw(STRPTR input, STRPTR output)
|
||||
{
|
||||
struct IFFHandle *iff = AllocIFF();
|
||||
iff->iff_Stream = (ULONG)Open(input, MODE_OLDFILE);
|
||||
InitIFFasDOS(iff); OpenIFF(iff, IFFF_READ);
|
||||
|
||||
StopChunk(iff, ID_ILBM, ID_BMHD);
|
||||
StopChunk(iff, ID_ILBM, ID_BODY);
|
||||
|
||||
struct BitMapHeader bmhd;
|
||||
UBYTE *bodyData = NULL;
|
||||
|
||||
while (ParseIFF(iff, IFFPARSE_SCAN) == 0)
|
||||
{
|
||||
struct ContextNode *cn = CurrentChunk(iff);
|
||||
if (cn->cn_ID == ID_BMHD)
|
||||
ReadChunkBytes(iff, &bmhd, sizeof(bmhd));
|
||||
else if (cn->cn_ID == ID_BODY)
|
||||
{
|
||||
bodyData = AllocMem(cn->cn_Size, MEMF_ANY);
|
||||
ReadChunkBytes(iff, bodyData, cn->cn_Size);
|
||||
|
||||
/* Decompress if needed */
|
||||
if (bmhd.bmh_Compression == 1)
|
||||
{
|
||||
ULONG rawSize = ((bmhd.bmh_Width + 15) / 16 * 2)
|
||||
* bmhd.bmh_Height * bmhd.bmh_Depth;
|
||||
UBYTE *raw = AllocMem(rawSize, MEMF_ANY);
|
||||
DecompressByteRun1(bodyData, raw, rawSize);
|
||||
FreeMem(bodyData, cn->cn_Size);
|
||||
bodyData = raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write raw planar data to output file */
|
||||
if (bodyData)
|
||||
{
|
||||
BPTR fh = Open(output, MODE_NEWFILE);
|
||||
ULONG bodySize = ((bmhd.bmh_Width + 15) / 16 * 2)
|
||||
* bmhd.bmh_Height * bmhd.bmh_Depth;
|
||||
Write(fh, bodyData, bodySize);
|
||||
Close(fh);
|
||||
FreeMem(bodyData, bodySize);
|
||||
}
|
||||
|
||||
CloseIFF(iff); Close((BPTR)iff->iff_Stream); FreeIFF(iff);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Building a Composite IFF (Sprite Sheet from Multiple ILBMs)
|
||||
|
||||
```c
|
||||
/* Create a CAT ILBM containing multiple sub-images: */
|
||||
struct IFFHandle *iff = AllocIFF();
|
||||
iff->iff_Stream = (ULONG)Open("sprites.iff", MODE_NEWFILE);
|
||||
InitIFFasDOS(iff);
|
||||
OpenIFF(iff, IFFF_WRITE);
|
||||
|
||||
PushChunk(iff, ID_ILBM, ID_CAT, IFFSIZE_UNKNOWN);
|
||||
|
||||
for (int i = 0; i < numFrames; i++)
|
||||
{
|
||||
/* Each frame is a FORM ILBM */
|
||||
PushChunk(iff, ID_ILBM, ID_FORM, IFFSIZE_UNKNOWN);
|
||||
/* Write BMHD, CMAP, BODY for this frame... */
|
||||
PopChunk(iff); /* close this FORM */
|
||||
}
|
||||
|
||||
PopChunk(iff); /* close the CAT */
|
||||
CloseIFF(iff); Close((BPTR)iff->iff_Stream); FreeIFF(iff);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## IFF as a Container — Comparison Across Eras
|
||||
|
||||
IFF was not just another image format — it was a **general-purpose binary container** for any kind of structured data. The FOURCC + size + data + pad pattern is a design template that recurred in dozens of later formats. This section compares IFF against both historical contemporaries and modern containers on container-design dimensions.
|
||||
|
||||
### Container Feature Matrix
|
||||
|
||||
| Dimension | IFF (1985) | RIFF (1991) | TIFF (1986) | PNG (1996) | ISOBMFF / MP4 (2001) | Matroska / EBML (2002) | ZIP (1989) |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **Identification** | `FORM`/`LIST`/`CAT ` FOURCC at file start | `RIFF`/`RIFX` + form-type FOURCC | `II` or `MM` magic + offset to first IFD | `\x89PNG\r\n\x1a\n` 8-byte signature | `ftyp` box with brand code | `\x1a\x45\xdf\xa3` EBML header + DocType | `PK\x03\x04` local file header |
|
||||
| **Chunk ID scheme** | 4-char ASCII FOURCC (`BMHD`, `CMAP`) | 4-char ASCII FOURCC (`fmt `, `data`) | 16-bit numeric tag IDs (256, 257...) | 4-char ASCII (`IHDR`, `IDAT`, `IEND`) | 4-char ASCII (`moov`, `mdat`, `trak`) | Variable-length integer Element ID | Filename string (directory-based) |
|
||||
| **Unknown-chunk skip** | ✅ Safe — reader skips by `size` field | ✅ Same as IFF | ⚠️ IFD chain must be parsed first | ✅ Same as IFF — fundamental design goal | ✅ Box size in header enables skip | ✅ EBML variable-length size | ❌ Not applicable — files are opaque entries |
|
||||
| **Nesting depth** | Arbitrary (FORM in LIST in FORM) | Same as IFF | 2 levels (IFD → sub-IFD) | Single-level (chunks within file only) | Arbitrary (boxes contain boxes) | Arbitrary (elements contain elements) | Flat (file list, no nesting) |
|
||||
| **Size-at-write-unknown** | ✅ `IFFSIZE_UNKNOWN` + seek-and-patch | ✅ Same mechanism | ❌ Must know sizes before writing IFD offsets | ❌ `IDAT` size must be determined at write time | ✅ `mdat` can be large + `moov` offsets patched | ✅ EBML Unknown-Size marker | ✅ Central directory written after file data |
|
||||
| **Multi-stream** | ✅ Multiple FORMs in LIST/CAT | ✅ Multiple LIST chunks | ✅ Multiple IFDs (multi-page TIFF) | ❌ Single image only (APNG extends later) | ✅ Multiple tracks (video + audio + subtitles) | ✅ Multiple tracks + attachments + chapters | ✅ Multiple files in archive |
|
||||
| **Endianness** | Big-Endian (Motorola) | Little-Endian (RIFF) or Big (RIFX) | Both (`II`=LE, `MM`=BE) | Big-Endian (network byte order) | Big-Endian (network byte order) | Big-Endian | Not applicable (per-file) |
|
||||
| **Compression model** | Per-chunk optional (BODY may be ByteRun1, others raw) | Per-chunk optional (codec in format chunk) | Per-image codec tag | Mandatory: all IDAT is zlib-deflated | Per-track codec | Per-track codec | Per-file (deflate, store, bzip2...) |
|
||||
| **Extensibility mindset** | Add new chunks — old readers skip them | Add new chunks — old readers skip them | Add new tags — old readers skip unknown | Add ancillary chunks — critical chunks can't be skipped | Add new boxes — old readers skip unknown | Add new elements — old readers skip unknown | Add new files to archive |
|
||||
| **Primary use case** | Any structured data (images, audio, text, hypertext) | Multimedia (WAV audio, AVI video, ANI cursors) | Document imaging, DTP, scanning | Lossless compressed images | MP4 video, M4A audio, 3GP mobile | MKV/MKA/MKS — video, audio, subtitles | Generic file bundling |
|
||||
|
||||
### What Each Container Got Right (and Wrong)
|
||||
|
||||
**IFF (1985)** — The original template. Its single greatest contribution was the **skip-unknown-chunk contract**: a reader that encounters an unrecognized FOURCC simply reads the 4-byte size and advances past the data. This means any IFF file created in 1986 can be opened by any IFF reader written in 2026, regardless of what chunks were added in between. The limitation was that IFF required chunks to be strictly nested (no overlapping), which turned out to be too rigid for some streaming scenarios.
|
||||
|
||||
**RIFF (1991)** — Microsoft and IBM essentially copied IFF and flipped the byte order to little-endian. This was a pragmatic decision (x86 native), but it broke interoperability with IFF files. RIFF added the `RIFX` variant for big-endian, but almost nothing used it. RIFF's dominance came from WAV (1991) and AVI (1992) — formats that still underpin audio and video on Windows. The RIFF container is a direct IFF descendant wearing an Intel shirt.
|
||||
|
||||
**TIFF (1986)** — Aldus took a different approach: instead of FOURCC chunk IDs, TIFF uses numeric tags in an Image File Directory (IFD). This is more compact and machine-friendly, but harder to inspect with a hex editor. TIFF's key innovation was **tag typing** (BYTE, ASCII, SHORT, LONG, RATIONAL) — a type system for metadata that IFF lacked. The downside: a TIFF reader must parse the IFD chain to locate image data, while an IFF reader can scan linearly. TIFF won desktop publishing; IFF won multimedia interchange.
|
||||
|
||||
**PNG (1996)** — PNG is IFF's most successful spiritual descendant. The 4-character chunk IDs (`IHDR`, `IDAT`, `IEND`, `tEXt`, `zTXt`), the skip-unknown-chunk rule, and the big-endian byte order are all direct IFF heritage. PNG added two innovations: **critical vs ancillary chunks** (unknown ancillary chunks are skipped; unknown critical chunks cause error), and a **mandatory compression layer** (zlib). The critical/ancillary distinction solved IFF's unresolved question: what if a chunk is essential to rendering? In IFF, the answer was always "skip it anyway" — in PNG, you can declare that a chunk must be understood or the file is invalid.
|
||||
|
||||
**ISOBMFF / MP4 (2001)** — The ISO Base Media File Format (used by MP4, 3GP, MOV) is a box-structured container that descends conceptually from IFF. Each box has a 4-byte size, 4-char type, and optional 8-byte extended size — nearly identical to IFF chunks. The key difference: ISOBMFF boxes can reference each other via file offsets (e.g., `moov` box has a table pointing into `mdat`), creating a **non-linear structure** that IFF cannot express. This enables streaming (metadata at the end is still usable) but requires a two-pass read for some operations.
|
||||
|
||||
**Matroska / EBML (2002)** — Matroska uses Extensible Binary Meta Language (EBML), which generalizes IFF's chunk concept to **variable-length element IDs and sizes**. A 1-byte element ID means "this is a common field" and a 4-byte ID means "this is a rare extension". This variable-length encoding is more space-efficient than IFF's fixed 4-byte FOURCCs, but it requires a schema (the `DocType`) to interpret element meaning — you can't just read the bytes and know what you're looking at. IFF's FOURCCs are self-documenting (`CMAP` in a hex dump is obviously "color map"); EBML's numeric IDs are not.
|
||||
|
||||
**ZIP (1989)** — A different paradigm: ZIP is a **directory-indexed archive**, not a structured container. Files are stored sequentially with local headers, and a central directory at the end maps filenames to offsets. This is the same "size unknown at write time" problem solved differently: IFF seeks back and patches the chunk size; ZIP appends a separate index. For bundling unrelated files, ZIP wins (one file = many independent entries). For structured data with internal relationships, IFF wins (one file = one logical object with nested parts).
|
||||
|
||||
### IFF's Container Design — Pros and Cons
|
||||
|
||||
| ✅ Advantage | ❌ Limitation |
|
||||
|---|---|
|
||||
| **Self-describing**: FOURCCs are human-readable ASCII — `BMHD` is obviously "bitmap header" in a hex dump | **Fixed 4-byte IDs**: wastes space on common chunks that could use 1-byte IDs (EBML solved this) |
|
||||
| **Linear scannable**: reader can walk file start-to-end without back-references | **No random access**: cannot jump directly to a specific chunk without scanning from the start (TIFF IFD offsets solved this) |
|
||||
| **Pad-byte rule**: simple and deterministic — always align to word boundary | **No compression at container level**: compression is per-chunk, not per-file (ZIP/zlib solved this) |
|
||||
| **Unknown-chunk skip**: forward-compatible forever — the defining feature | **No critical vs ancillary distinction**: IFF cannot declare a chunk as mandatory (PNG solved this) |
|
||||
| **Write-friendly**: `IFFSIZE_UNKNOWN` enables streaming writes with retroactive size patching | **Seek required**: `PopChunk` must seek back to patch the size — breaks on non-seekable streams |
|
||||
| **Rich nesting**: LIST/PROP/CAT cover structured documents, sprite sheets, and hypertext | **No cross-references**: chunks cannot point to data in other chunks by offset (ISOBMFF solved this) |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `libraries/iffparse.h`, `datatypes/pictureclass.h`
|
||||
- EA IFF-85 specification: the original format definition
|
||||
- ADCD 2.1: iffparse.library autodocs
|
||||
- See also: [datatypes.md](datatypes.md) — higher-level data loading system that supersedes iffparse for most application use cases
|
||||
- See also: [ham_ehb_modes.md](../08_graphics/ham_ehb_modes.md) — HAM-encoded ILBM files
|
||||
- See also: [amigaguide.md](amigaguide.md) — AmigaGuide hypertext system (IFF FTXT+ILBM under the hood)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
[← Home](../README.md) · [Libraries](README.md)
|
||||
|
||||
# locale.library — Internationalisation
|
||||
# locale.library — Internationalization
|
||||
|
||||
## Overview
|
||||
|
||||
`locale.library` (OS 2.1+) provides the Amiga's internationalisation (i18n) framework: language-aware string lookup via catalogues, locale-sensitive date/number/currency formatting, and character classification. Applications that use locale.library display in the user's preferred language automatically.
|
||||
`locale.library` (OS 2.1+) provides the Amiga's internationalization (i18n) framework: language-aware string lookup via catalogs, locale-sensitive date/number/currency formatting, and character classification. Applications that use locale.library display in the user's preferred language automatically.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
APP["Application"] -->|"GetCatalogStr(cat, ID, fallback)"| LOC["locale.library"]
|
||||
LOC -->|"Looks up string ID"| CAT["myapp.catalog<br/>(LOCALE:Catalogs/deutsch/)"]
|
||||
CAT -->|"Found"| DE["'Datei öffnen'"]
|
||||
LOC -->|"Not found"| FB["Fallback:<br/>'Open File'"]
|
||||
LOC -->|"Not found"| FB["Fallback:<br/>'Open File'<br/><i>(built into executable)</i>"]
|
||||
|
||||
style LOC fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style CAT fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
|
|
@ -19,7 +19,7 @@ flowchart TD
|
|||
|
||||
---
|
||||
|
||||
## Catalogue System
|
||||
## Catalog System
|
||||
|
||||
### Creating String IDs
|
||||
|
||||
|
|
@ -39,12 +39,12 @@ static const char *builtinStrings[] = {
|
|||
};
|
||||
```
|
||||
|
||||
### Using Catalogues
|
||||
### Using Catalogs
|
||||
|
||||
```c
|
||||
struct Library *LocaleBase = OpenLibrary("locale.library", 38);
|
||||
|
||||
/* Open the application's catalogue: */
|
||||
/* Open the application's catalog: */
|
||||
struct Catalog *cat = OpenCatalog(NULL, "myapp.catalog",
|
||||
OC_BuiltInLanguage, (ULONG)"english",
|
||||
TAG_DONE);
|
||||
|
|
@ -52,7 +52,7 @@ struct Catalog *cat = OpenCatalog(NULL, "myapp.catalog",
|
|||
|
||||
/* Get localised string (with English fallback): */
|
||||
STRPTR openStr = GetCatalogStr(cat, MSG_OPEN_FILE, "Open File");
|
||||
/* Returns German "Datei öffnen" if German catalogue exists,
|
||||
/* Returns German "Datei öffnen" if German catalog exists,
|
||||
otherwise the fallback "Open File" */
|
||||
|
||||
/* Use throughout the application: */
|
||||
|
|
@ -63,7 +63,7 @@ CloseCatalog(cat);
|
|||
CloseLibrary(LocaleBase);
|
||||
```
|
||||
|
||||
### Catalogue File Structure
|
||||
### Catalog File Structure
|
||||
|
||||
```
|
||||
LOCALE:Catalogs/deutsch/myapp.catalog ← German
|
||||
|
|
@ -71,10 +71,10 @@ LOCALE:Catalogs/français/myapp.catalog ← French
|
|||
LOCALE:Catalogs/italiano/myapp.catalog ← Italian
|
||||
```
|
||||
|
||||
Catalogues are compiled from `.cd` (catalogue description) and `.ct` (catalogue translation) files using **CatComp** or **FlexCat**:
|
||||
Catalogs are compiled from `.cd` (catalog description) and `.ct` (catalog translation) files using **CatComp** or **FlexCat**:
|
||||
|
||||
```
|
||||
; myapp.cd — catalogue description
|
||||
; myapp.cd — catalog description
|
||||
MSG_OPEN_FILE (1//)
|
||||
Open File
|
||||
;
|
||||
|
|
@ -182,8 +182,84 @@ LONG result = StrnCmp(loc, str1, str2, -1, SC_COLLATE2);
|
|||
|
||||
---
|
||||
|
||||
## Historical Perspective — i18n in the Early 1990s
|
||||
|
||||
locale.library shipped with Amiga OS 2.1 in 1992, at a time when internationalization was barely on the radar of most operating systems. The landscape was fragmented and primitive:
|
||||
|
||||
### The State of Internationalization (1990–1992)
|
||||
|
||||
| Platform | i18n Mechanism | String Lookup | Recompilation Required? | Number/Date Locale | Character Classification |
|
||||
|---|---|---|---|---|---|
|
||||
| **Amiga OS 2.1** | locale.library + external catalogs | Numeric ID → catalog file | ❌ No — drop a new `.catalog` file in the locale directory | ✅ FormatDate, FormatString, decimal/group separators | ✅ IsAlpha, IsUpper, collation (SC_COLLATE1/2) |
|
||||
| **Classic Mac OS (System 7)** | STR# resources + `GetIndString` | Integer ID → resource fork | ❌ No — ResEdit the resource fork | ❌ No — TextUtils had minimal formatting | ❌ No — ASCII-only on 68k Macs |
|
||||
| **Windows 3.1** | String table resources (`.rc` → `.res`) | Integer ID → resource DLL | ✅ Yes — re-link the resource DLL if separate; otherwise recompile | ❌ No — `GetNumberFormat`/`GetDateFormat` arrived in Win32 (NT 3.1, 1993) | ❌ No — `IsCharAlpha` was ASCII-only |
|
||||
| **Unix / X11 (Motif)** | `catgets()` (XPG3) or manual `#ifdef LANG_DE` | Numeric set+message ID → `.cat` file | ❌ No — message catalogs external | ⚠️ Partial — `setlocale()` + `nl_langinfo()`, but implementation was spotty across vendors | ⚠️ Partial — `isalpha()` was locale-aware only if the C library supported it |
|
||||
| **Atari ST (TOS)** | Nothing | — | — | — | — |
|
||||
| **NeXTSTEP 3.x** | `.strings` files + `NSLocalizedString()` | Natural-language key → `.strings` file | ❌ No — `.strings` files loaded at runtime | ✅ NSNumberFormatter, NSDateFormatter | ✅ NSString character methods |
|
||||
|
||||
### What Made locale.library Innovative
|
||||
|
||||
**1. External catalog files with zero-recompile extensibility.** This was the single most important design decision. An Amiga application ships with English strings compiled in. A translator in Germany creates `myapp.catalog` using CatComp/FlexCat, drops it into `LOCALE:Catalogs/deutsch/`, and the application immediately speaks German — no patches, no resource editing, no recompilation. Windows 3.1 could do this with separate resource DLLs, but only if the developer shipped them separately from the start — retrofitting an existing English-only app required recompilation. Mac OS System 7 required editing the resource fork with ResEdit. locale.library made localization a deployment decision, not a build decision.
|
||||
|
||||
**2. Numeric string IDs decouple code from language.** The application never hard-codes a German string — it hard-codes `MSG_OPEN_FILE = 1`. The catalog maps `1 → "Datei öffnen"`. This means translators never touch source code, developers never touch translations, and the same binary works in any language. Compare with early Unix practices where developers sprinkled `#ifdef LANG_DE ... #elif LANG_FR ...` throughout the source.
|
||||
|
||||
**3. Graceful fallback is built-in, not bolted-on.** `GetCatalogStr(cat, ID, "Open File")` always returns *something* — either the translated string from the catalog, or the English fallback. If the catalog file is missing, corrupted, or doesn't contain the requested ID, the application still works. This is the same principle that makes IFF safe (skip unknown chunks) — the system degrades gracefully under incomplete data.
|
||||
|
||||
**4. Locale-aware formatting is separate from string translation.** This was unusual for 1992. `FormatDate()` uses the user's locale preferences for date ordering (`DD/MM/YY` vs `MM/DD/YY`), weekday/month names, and decimal separators — but it doesn't require the application to be translated. A German user running an English application still sees `"Mittwoch, 23. April 1992"` if their locale is set to German, even though menu strings remain in English. This separation of concerns — content translation vs presentation formatting — wouldn't become standard until the late 1990s.
|
||||
|
||||
**5. Hook-based formatting for extensibility.** `FormatDate()` takes a `struct Hook *` callback rather than writing to a fixed buffer. The callback receives one character at a time, giving the application complete control over output — write to a window, send to a printer, stream to a file, or concatenate into a growing buffer. Qt's `QLocale` and macOS's `NSDateFormatter` return fixed `QString`/`NSString` objects; the Amiga's hook approach was both more primitive and more flexible.
|
||||
|
||||
> [!NOTE]
|
||||
> **NeXTSTEP deserves special mention.** It had `NSLocalizedString()` and `.strings` files in 1991, predating locale.library. NeXT's approach used **natural-language keys** (`"Open File" = "Datei öffnen";`) rather than numeric IDs — simpler for humans but fragile: if the developer tweaked the English string, the key changed and every translation broke. locale.library's numeric IDs are stable across English string edits.
|
||||
|
||||
---
|
||||
|
||||
## Modern Analogies — locale.library vs Today's Frameworks
|
||||
|
||||
Amiga's i18n architecture maps surprisingly well to modern frameworks — many of its design patterns became industry standards:
|
||||
|
||||
### Framework Comparison
|
||||
|
||||
| Concept | Amiga locale.library (1992) | Qt (1995–) | POSIX gettext (1990s–) | macOS / iOS (2000s–) | Android (2008–) |
|
||||
|---|---|---|---|---|---|
|
||||
| **String lookup function** | `GetCatalogStr(cat, ID, fallback)` | `QObject::tr("source")` or `QT_TR_NOOP()` | `gettext("original")` | `NSLocalizedString(@"key", @"comment")` | `getString(R.string.id)` |
|
||||
| **Translation key type** | Numeric integer ID (stable) | Source string as key (fragile) | Source string as key (fragile) | Natural-language key string (stable if chosen carefully) | Auto-generated integer ID from XML |
|
||||
| **Translation storage** | Binary `.catalog` (compiled from `.cd` + `.ct`) | `.qm` (compiled from `.ts` XML) | `.mo` (compiled from `.po`) | `.strings` plist/dictionary | `strings.xml` in `res/values-<locale>/` |
|
||||
| **External file** | ✅ `LOCALE:Catalogs/<lang>/` | ✅ loaded from filesystem | ✅ `/usr/share/locale/<lang>/LC_MESSAGES/` | ✅ `.lproj` bundles | ✅ APK resources, no post-install modification |
|
||||
| **Fallback chain** | Built-in English string passed as parameter | Source string in code → untranslated | Source string in code → untranslated | Key string → untranslated | Default `strings.xml` → key shown |
|
||||
| **Number/date formatting** | `FormatDate()`, `FormatString()`, `loc_DecimalPoint` | `QLocale::toString()`, `QDate::toString()` | `strftime()`, `nl_langinfo()` | `NSDateFormatter`, `NSNumberFormatter` | `DateFormat`, `NumberFormat` |
|
||||
| **Character classification** | `IsAlpha()`, `IsUpper()`, `ConvToUpper()`, `StrnCmp()` with collation | `QChar::isLetter()`, `QString::localeAwareCompare()` | `iswalpha()`, `wcscoll()` (C95) | `NSCharacterSet`, `localizedCompare:` | `Character.isLetter()`, `Collator` |
|
||||
| **Post-install translation** | ✅ Drop `.catalog` file — no app restart needed | ⚠️ `.qm` loaded at startup; need `QTranslator::load()` signal | ⚠️ `.mo` cached; need `bindtextdomain` reload | ❌ `.strings` in bundle — app resigning required | ❌ Resources baked into APK |
|
||||
| **Translator tooling** | CatComp, FlexCat | `lupdate`/`lrelease` + Qt Linguist | `xgettext`/`msgfmt` + Poedit | `genstrings` + Xcode | Android Studio translation editor |
|
||||
|
||||
### Architecture Patterns That Survived
|
||||
|
||||
**External resource files.** Every modern i18n system separates translations from code. locale.library was among the first consumer-OS frameworks to make this the *default* rather than a bolt-on. The directory convention `LOCALE:Catalogs/<language>/` prefigured `/usr/share/locale/` and `res/values-de/` by years.
|
||||
|
||||
**String ID indirection.** Whether it's `MSG_OPEN_FILE = 1` (Amiga), `R.string.open_file` (Android), or a `.strings` key (macOS), the pattern is identical: code references an abstract identifier, and the runtime resolves it to a language-specific string. locale.library's numeric IDs are arguable the most robust form — they can't accidentally change when someone rewrites the source string.
|
||||
|
||||
**Fallback chains.** `GetCatalogStr(cat, ID, "Open File")` prefigures Qt's `tr("Open File")` and Android's resource fallback. The key difference: locale.library requires the fallback as an explicit parameter, which means it's always in the executable. Qt and gettext use the source string as both key and fallback — simpler API but couples translation to the exact English phrasing.
|
||||
|
||||
**Formatter/translator separation.** The fact that `FormatDate()` works independently of catalog files — formatting weekday names from the active locale even when the app is untranslated — is a design subtlety that took other platforms years to replicate. Windows didn't separate locale formatting from UI language until Vista (2006).
|
||||
|
||||
### locale.library Design — Pros and Cons
|
||||
|
||||
| Advantage | Limitation |
|
||||
|---|---|
|
||||
| **Zero-recompile localization** — the killer feature. A translator never touches source code | **Binary catalog format** — proprietary and unversioned. No way to inspect a `.catalog` file without CatComp tools |
|
||||
| **Stable numeric IDs** — changing an English string doesn't invalidate translations | **Numeric IDs require tooling** — you need CatComp or FlexCat to generate the `#define` header. Manual ID assignment invites collisions |
|
||||
| **Graceful fallback** — missing catalog = English, not crash | **Zero-recompile also means zero-deploy tooling** — there's no package manager, so `.catalog` files are manually copied. AmigaOS never solved distribution |
|
||||
| **Formatting independent of translation** — German dates even in English apps | **Single-byte character set only** — locale.library predates Unicode. Languages with >256 characters (Japanese, Chinese, Korean) are impossible without hacks |
|
||||
| **Hook-based formatting** — application controls output destination | **No plural rules** — `GetCatalogStr` returns one string. Modern gettext has `ngettext()` for plural forms; Qt has `tr("%n files", "", n)` |
|
||||
| **Built into the OS** — no third-party library needed, consistent across all apps | **Limited adoption outside Amiga** — Commodore's bankruptcy meant the approach never influenced broader industry standards |
|
||||
|
||||
> [!NOTE]
|
||||
> **The Qt connection is not accidental.** Qt's `tr()` system — source strings as keys, compiled `.qm` files, `lupdate`/`lrelease` toolchain, and `QLocale` for formatting — is the closest spiritual successor to locale.library in the modern world. Both share the same core insight: i18n must be a **runtime deployment decision**, not a compile-time `#ifdef`. The key architectural difference: Qt uses source strings as translation keys (simpler for developers, fragile on rewrites); locale.library uses numeric IDs (stable, but requires tooling to generate).
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `libraries/locale.h`
|
||||
- ADCD 2.1: locale.library autodocs
|
||||
- CatComp / FlexCat documentation for catalogue compilation
|
||||
- CatComp / FlexCat documentation for catalog compilation
|
||||
|
|
|
|||
|
|
@ -201,11 +201,11 @@ graph TB
|
|||
| `68060.library` 40.1 | — | Phase5 | For Blizzard/CyberStorm accelerators |
|
||||
| `68060.library` 46.x | — | Motorola FPSP reference | Best precision and compatibility |
|
||||
| `Mu68040.library` | — | Thomas Richter (MMULib) | Enhanced, with MMU support |
|
||||
| `Mu68060.library` | — | Thomas Richter (MMULib) | Enhanced, with MMU + optimisations |
|
||||
| `Mu68060.library` | — | Thomas Richter (MMULib) | Enhanced, with MMU + optimizations |
|
||||
|
||||
### Three CPU Flavours
|
||||
|
||||
| CPU | FPU | Math Behaviour |
|
||||
| CPU | FPU | Math Behavior |
|
||||
|---|---|---|
|
||||
| **68040** (full) | ✅ Built-in | Basic FPU ops in hardware. Transcendentals trapped → 68040.library |
|
||||
| **68LC040** | ❌ No FPU | ALL FPU ops trap → needs 68040.library + SoftIEEE or full emulation |
|
||||
|
|
@ -232,7 +232,7 @@ Performance-critical code should use lookup tables, CORDIC algorithms, or polyno
|
|||
|
||||
## Third-Party Replacement Libraries
|
||||
|
||||
The Amiga's library system allows **drop-in replacements** — you simply copy an optimised `.library` to `LIBS:` and it supersedes the ROM version. Several third-party packages exploit this for dramatic speedups.
|
||||
The Amiga's library system allows **drop-in replacements** — you simply copy an optimized `.library` to `LIBS:` and it supersedes the ROM version. Several third-party packages exploit this for dramatic speedups.
|
||||
|
||||
### HSMathLibs (Matthias Henze)
|
||||
|
||||
|
|
@ -261,7 +261,7 @@ The most comprehensive third-party math library replacement. Written entirely in
|
|||
|
||||
### Mu680x0Libs / MMULib (Thomas Richter)
|
||||
|
||||
Part of the MMULib package, these provide optimised CPU-specific libraries including math support:
|
||||
Part of the MMULib package, these provide optimized CPU-specific libraries including math support:
|
||||
|
||||
| Package | Aminet |
|
||||
|---|---|
|
||||
|
|
@ -312,8 +312,8 @@ graph TB
|
|||
LIBS -->|"LIBS: copy wins"| HS
|
||||
LIBS -->|"No LIBS: copy"| ROM
|
||||
ROM --> STD
|
||||
HS -->|"040 version"| OPT040["68040-optimised code"]
|
||||
HS -->|"060 version"| OPT060["68060-optimised code"]
|
||||
HS -->|"040 version"| OPT040["68040-optimized code"]
|
||||
HS -->|"060 version"| OPT060["68060-optimized code"]
|
||||
MU --> FPSP["FPSP: handles<br/>unimplemented FPU ops"]
|
||||
```
|
||||
|
||||
|
|
@ -453,7 +453,7 @@ vc +aos68k prog.c -lmieee # software IEEE
|
|||
|
||||
4. **68LC040/060 crashes** — Code that works on full 68040 Gurus on LC variants. Install `SoftIEEE` or equivalent FPSP to trap Line-F exceptions from unimplemented FPU instructions.
|
||||
|
||||
5. **HSMathLibs CPU mismatch** — Installing the 040-optimised libraries on a 060 (or vice versa) can cause incorrect results or reduced performance. The pipeline timings are architecturally different.
|
||||
5. **HSMathLibs CPU mismatch** — Installing the 040-optimized libraries on a 060 (or vice versa) can cause incorrect results or reduced performance. The pipeline timings are architecturally different.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
536
11_libraries/translator.md
Normal file
536
11_libraries/translator.md
Normal file
|
|
@ -0,0 +1,536 @@
|
|||
[← Home](../README.md) · [Libraries](README.md)
|
||||
|
||||
# translator.library — English-to-Phonetic Translation for Speech Synthesis
|
||||
|
||||
## Overview
|
||||
|
||||
`translator.library` is the front half of the Amiga's built-in text-to-speech pipeline: a single-function library that converts unrestricted English text into **phonetic strings** — the expanded ARPABET phoneme codes used by `narrator.device` to generate human-like speech through the Amiga's audio hardware. Introduced with AmigaOS 1.2 and distributed as a disk-based library in `LIBS:`, it encapsulates over 450 context-sensitive pronunciation rules, an exception dictionary for irregular words (through, though, cough), abbreviation expansion (Dr., Prof., lb.), and automatic content-word accentuation — all in a single call: `Translate()`. The output is a string of space-delimited phoneme codes with stress markers that can be passed directly to `narrator.device` via `CMD_WRITE`, stored for later playback, or analyzed for phonetic research. While hand-coded phonetics always produce higher-quality speech, `Translate()` is the only practical option when the input is arbitrary user text at runtime.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### The Amiga Speech Pipeline
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph INPUT["Input Layer"]
|
||||
ENG["English Text<br/>(ASCII)"]
|
||||
PHON["Hand-Coded<br/>Phonetic String"]
|
||||
end
|
||||
|
||||
subgraph TRANSLATOR["translator.library"]
|
||||
TR["Translate()<br/>English → Phonetic"]
|
||||
RULES["450+ Context Rules<br/>Exception Dictionary<br/>Abbreviation Expansion"]
|
||||
end
|
||||
|
||||
subgraph NARRATOR["narrator.device"]
|
||||
SYNTH["Speech Synthesizer<br/>Formant Model"]
|
||||
MOUTH["Mouth Shape<br/>Generator"]
|
||||
end
|
||||
|
||||
subgraph OUTPUT["Output Layer"]
|
||||
AUDIO["audio.device<br/>DMA Audio Channels"]
|
||||
MOUTHDATA["mouth_rb<br/>Width/Height"]
|
||||
end
|
||||
|
||||
ENG --> TR
|
||||
TR --> RULES
|
||||
RULES --> TR
|
||||
TR -->|"Phonetic String"| SYNTH
|
||||
PHON --> SYNTH
|
||||
SYNTH --> AUDIO
|
||||
SYNTH --> MOUTH
|
||||
MOUTH --> MOUTHDATA
|
||||
|
||||
style TR fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style SYNTH fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
```
|
||||
|
||||
### Library Base
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| `TranslatorBase` | `struct Library *` | Library base pointer returned by `OpenLibrary()` |
|
||||
| `ITranslator` | Interface pointer (OS 4.x+) | Interface-based access for AmigaOS 4+ |
|
||||
|
||||
`translator.library` is a **disk-based** library — it lives in `LIBS:translator.library`, not in ROM. This means `OpenLibrary()` can fail if the file is missing, and the library can be expunged from memory under low-memory conditions.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
| Decision | Rationale |
|
||||
|---|---|
|
||||
| **Single-function API** | Translation is inherently stateless — input text, output phonetics. No session, no configuration |
|
||||
| **Disk-based, not ROM** | Phonetic dictionary is large (~20+ KB of rules); keeping it out of ROM saves Kickstart space |
|
||||
| **Negative return codes for overflow** | Allows progressive translation of long texts without pre-allocating a huge buffer |
|
||||
| **Rule-based, not neural** | 1985 technology couldn't run a neural TTS; the 450 context-sensitive rules were state-of-the-art for the era |
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Opening and Closing
|
||||
|
||||
```c
|
||||
/* Classic AmigaOS (1.x–3.x) — LVO -30 */
|
||||
struct Library *TranslatorBase;
|
||||
|
||||
TranslatorBase = OpenLibrary("translator.library", 0);
|
||||
if (!TranslatorBase) { /* LIBS:translator.library not found */ }
|
||||
|
||||
/* ... use Translate() ... */
|
||||
|
||||
CloseLibrary(TranslatorBase);
|
||||
```
|
||||
|
||||
```c
|
||||
/* AmigaOS 4.x — Interface-based */
|
||||
struct Library *TranslatorBase;
|
||||
struct TranslatorIFace *ITranslator;
|
||||
|
||||
TranslatorBase = IExec->OpenLibrary("translator.library", 0);
|
||||
if (TranslatorBase)
|
||||
{
|
||||
ITranslator = (struct TranslatorIFace *)
|
||||
IExec->GetInterface(TranslatorBase, "main", 1, NULL);
|
||||
if (ITranslator)
|
||||
{
|
||||
/* ... use ITranslator->Translate() ... */
|
||||
}
|
||||
IExec->DropInterface((struct Interface *)ITranslator);
|
||||
}
|
||||
IExec->CloseLibrary(TranslatorBase);
|
||||
```
|
||||
|
||||
### Translate()
|
||||
|
||||
```c
|
||||
/* LVO -36 — Converts English text to phonetic string */
|
||||
LONG Translate(STRPTR input, /* a0: English input string */
|
||||
LONG inputLen, /* d0: length of input */
|
||||
STRPTR output, /* a1: output buffer for phonetics */
|
||||
LONG outputSize /* d0: size of output buffer */);
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|---|---|
|
||||
| `input` | Null-terminated or length-delimited English ASCII string. Case-insensitive; punctuation is preserved where it affects pronunciation |
|
||||
| `inputLen` | Number of characters to translate from `input`. Use `strlen(input)` for the full string |
|
||||
| `output` | Pre-allocated buffer to receive the phonetic string. **Must be large enough** — phonetics are typically 2–4× the input length |
|
||||
| `outputSize` | Size of the output buffer in bytes |
|
||||
|
||||
**Return value:**
|
||||
|
||||
| Return | Meaning |
|
||||
|---|---|
|
||||
| `0` | Full translation succeeded; output buffer was large enough |
|
||||
| **Negative** value | Buffer overflow — translation stopped at a word boundary. `-(rtnCode)` is the character offset in the input string where translation ended. Resume by calling `Translate(input + offset, inputLen - offset, output, outputSize)` |
|
||||
| Other non-zero | Translation error (unlikely — the library tries to translate literally if rules fail) |
|
||||
|
||||
> [!NOTE]
|
||||
> The negative return value always stops at a **word boundary** (space or punctuation), not mid-word. This prevents split phonemes and makes resumption seamless.
|
||||
|
||||
### Output Format
|
||||
|
||||
The output is a space-delimited string of **ARPABET phoneme codes** with **stress markers** appended to vowels:
|
||||
|
||||
```
|
||||
Input: "This is Amiga speaking."
|
||||
Output: "DH IH1 Z IH1 Z AE1 M IH0 G AH0 S P IY1 K IH0 NG ."
|
||||
└─ "This" ─┘ └"is"─┘ └─── "Amiga" ───┘ └─── "speaking" ───┘
|
||||
```
|
||||
|
||||
| Marker | Meaning | Example |
|
||||
|---|---|---|
|
||||
| `0` | No stress (unstressed vowel) | `IH0` = unstressed "i" (as in "rabbit") |
|
||||
| `1` | Primary stress | `IY1` = stressed "ee" (as in "speak") |
|
||||
| `2` | Secondary stress | `OW2` = secondary "oh" (as in "overflow") |
|
||||
| `3` | Emphatic stress (rare) | Used for contrastive emphasis |
|
||||
|
||||
---
|
||||
|
||||
## Phonetic Output Examples
|
||||
|
||||
| English Input | Phonetic Output (approx.) |
|
||||
|---|---|
|
||||
| `Hello world.` | `HH EH0 L OW1 W ER1 L D .` |
|
||||
| `The quick brown fox.` | `DH AH0 K W IH1 K B R AW1 N F AA1 K S .` |
|
||||
| `Amiga` | `AE1 M IH0 G AH0` or `AH0 M IY1 G AH0` (both valid) |
|
||||
| `Commodore` | `K AA1 M AH0 D AO1 R` |
|
||||
| `Guru Meditation` | `G UH1 R UW0 M EH2 D IH0 T EY1 SH AH0 N` |
|
||||
|
||||
> [!WARNING]
|
||||
> The translator library was designed for **American English** pronunciation. British spellings (colour, centre) and non-English words will be translated using American phonetic rules and may sound odd.
|
||||
|
||||
---
|
||||
|
||||
## Integration with narrator.device
|
||||
|
||||
The standard workflow:
|
||||
|
||||
```c
|
||||
#include <devices/narrator.h>
|
||||
#include <clib/translator_protos.h>
|
||||
|
||||
/* 1. Open translator */
|
||||
struct Library *TranslatorBase = OpenLibrary("translator.library", 0);
|
||||
|
||||
/* 2. Open narrator device */
|
||||
struct MsgPort *mp = CreatePort(NULL, 0);
|
||||
struct narrator_rb *voiceIO = (struct narrator_rb *)
|
||||
CreateExtIO(mp, sizeof(struct narrator_rb));
|
||||
OpenDevice("narrator.device", 0, (struct IORequest *)voiceIO, 0);
|
||||
|
||||
/* 3. Translate English → phonetic */
|
||||
#define PHONBUF_SIZE 2048
|
||||
STRPTR english = "Welcome to the Amiga speech system.";
|
||||
UBYTE phonBuffer[PHONBUF_SIZE];
|
||||
LONG result = Translate(english, strlen(english),
|
||||
(STRPTR)phonBuffer, PHONBUF_SIZE);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
/* 4. Configure voice parameters */
|
||||
voiceIO->rate = 150; /* words per minute */
|
||||
voiceIO->pitch = 110; /* Hz baseline */
|
||||
voiceIO->sex = 0; /* 0=male, 1=female */
|
||||
voiceIO->volume = 64; /* 0–64 */
|
||||
voiceIO->sampfreq = 22200; /* Hz (Amiga native rate) */
|
||||
|
||||
/* 5. Send to narrator */
|
||||
voiceIO->message.io_Command = CMD_WRITE;
|
||||
voiceIO->message.io_Data = phonBuffer;
|
||||
voiceIO->message.io_Length = strlen((STRPTR)phonBuffer);
|
||||
DoIO((struct IORequest *)voiceIO);
|
||||
}
|
||||
|
||||
/* 6. Cleanup */
|
||||
CloseDevice((struct IORequest *)voiceIO);
|
||||
DeleteExtIO((struct IORequest *)voiceIO);
|
||||
DeletePort(mp);
|
||||
CloseLibrary(TranslatorBase);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Use / When NOT to Use
|
||||
|
||||
| Scenario | Use `Translate()`? | Rationale |
|
||||
|---|---|---|
|
||||
| **Unrestricted user input** (text editor, terminal, chat) | ✅ Yes | Only practical option — you can't pre-code phonetics for arbitrary text |
|
||||
| **Fixed application strings** (game dialog, error messages) | ❌ No | Hand-code phonetics once; ship the phonetic strings. Much better quality |
|
||||
| **Accessibility screen reader** | ✅ Yes | Essential — must speak whatever is on screen |
|
||||
| **Demo/game with iconic lines** | ❌ No | Hand-tune phonetics, stress, and timing for maximum impact |
|
||||
| **Multi-language support** | ❌ No | translator.library is English-only; use a third-party TTS or pre-recorded samples |
|
||||
| **Phonetic research/analysis** | ⚠️ Maybe | Output is useful for analysis but not linguistically rigorous — use as a starting point |
|
||||
| **Speaking numbers/dates** | ⚠️ Maybe | Library handles some abbreviations but not all; pre-process complex formats into spelled-out words |
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls & Common Mistakes
|
||||
|
||||
### 1. Underestimating Phonetic Buffer Size
|
||||
|
||||
The phonetic representation is **always longer** than the input English. A 100-character sentence typically produces 300–500 bytes of phonetics:
|
||||
|
||||
```c
|
||||
/* BAD: Same-sized buffer — will overflow on first long word */
|
||||
UBYTE phonBuf[256];
|
||||
STRPTR english = "The extraordinarily complicated implementation...";
|
||||
LONG result = Translate(english, strlen(english), (STRPTR)phonBuf, 256);
|
||||
/* result will be negative — phonetic for "extraordinarily" alone is ~40 chars */
|
||||
|
||||
/* CORRECT: Allocate 4× input length, minimum 512 bytes */
|
||||
#define PHONBUF_SIZE(maxInput) (((maxInput) * 4) + 512)
|
||||
UBYTE *phonBuf = AllocMem(PHONBUF_SIZE(strlen(english)), MEMF_ANY);
|
||||
```
|
||||
|
||||
### 2. Ignoring Negative Return Code
|
||||
|
||||
A negative return from `Translate()` is a **resumption offset**, not a fatal error:
|
||||
|
||||
```c
|
||||
/* BAD: Treats partial translation as failure */
|
||||
LONG rtn = Translate(text, len, buf, size);
|
||||
if (rtn != 0) { /* panic — but text was partially translated! */ }
|
||||
|
||||
/* CORRECT: Resume from offset on negative return */
|
||||
LONG offset = 0;
|
||||
while (offset < len)
|
||||
{
|
||||
LONG rtn = Translate(text + offset, len - offset, buf, BUF_SIZE);
|
||||
if (rtn == 0) break; /* done */
|
||||
if (rtn < 0) offset += (-rtn); /* resume from word boundary */
|
||||
else { /* unexpected error */ break; }
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Passing Non-Null-Terminated Input with Wrong Length
|
||||
|
||||
If `inputLen` doesn't match the actual string, `Translate()` reads garbage or stops early:
|
||||
|
||||
```c
|
||||
/* BAD: strlen() on a buffer that may not be null-terminated */
|
||||
UBYTE buf[256];
|
||||
Read(fh, buf, 256); /* may fill entire buffer — no terminator */
|
||||
Translate((STRPTR)buf, strlen((STRPTR)buf), out, 1024);
|
||||
/* strlen() may read past the buffer! */
|
||||
|
||||
/* CORRECT: Use the explicit read count */
|
||||
LONG actual = Read(fh, buf, 256);
|
||||
Translate((STRPTR)buf, actual, out, 1024);
|
||||
```
|
||||
|
||||
### 4. Not Checking for Missing Disk-Based Library
|
||||
|
||||
Unlike ROM libraries, `translator.library` may not be present:
|
||||
|
||||
```c
|
||||
/* BAD: Assumes library is always available */
|
||||
struct Library *TranslatorBase = OpenLibrary("translator.library", 0);
|
||||
Translate("Hello", 5, buf, 512); /* crash if TranslatorBase == NULL! */
|
||||
|
||||
/* CORRECT: Always check the return */
|
||||
struct Library *TranslatorBase = OpenLibrary("translator.library", 0);
|
||||
if (TranslatorBase)
|
||||
{
|
||||
Translate("Hello", 5, buf, 512);
|
||||
CloseLibrary(TranslatorBase);
|
||||
}
|
||||
else
|
||||
{
|
||||
Printf("Speech not available — translator.library missing\n");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Named Antipatterns
|
||||
|
||||
### "The Mumbler" — Unrealistic Rate/Pitch
|
||||
|
||||
Setting `rate` extremely high makes speech unintelligible, but the translator itself has nothing to do with it — the problem is feeding valid phonetics to a misconfigured narrator:
|
||||
|
||||
```c
|
||||
/* BAD: Chipmunk speech */
|
||||
voiceIO->rate = 400; /* 400 words/min — unintelligible */
|
||||
voiceIO->pitch = 255; /* extremely high pitch */
|
||||
|
||||
/* Sensible defaults: */
|
||||
voiceIO->rate = 150; /* natural conversational speed */
|
||||
voiceIO->pitch = 110; /* male baseline (85–110 for male, 160–220 for female) */
|
||||
voiceIO->sex = 0; /* 0=male, 1=female */
|
||||
```
|
||||
|
||||
### "The Silent Speaker" — Mismatched Audio Allocation
|
||||
|
||||
The narrator device must allocate audio channels. If another application holds all four channels, `OpenDevice("narrator.device", ...)` succeeds but speech may not be audible:
|
||||
|
||||
```c
|
||||
/* BAD: No check on audio channel availability */
|
||||
OpenDevice("narrator.device", 0, (struct IORequest *)voiceIO, 0);
|
||||
/* Speech may be silent if audio channels are all in use */
|
||||
|
||||
/* CORRECT: Set channel mask to request specific channels */
|
||||
UBYTE chanMasks[] = { 0x03, 0x0C, 0x30, 0xC0 }; /* try channels 0-1, 2-3, 4-5, 6-7 */
|
||||
voiceIO->ch_masks = chanMasks;
|
||||
voiceIO->nm_masks = 4;
|
||||
```
|
||||
|
||||
### "The Echo" — Forgetting io_Data Nesting
|
||||
|
||||
When you send a `CMD_WRITE` to the narrator device, the `io_Data` pointer must remain valid until the I/O completes. Using a stack buffer with `DoIO()` is fine (blocking); using `SendIO()` (asynchronous) with a stack buffer is not:
|
||||
|
||||
```c
|
||||
/* BAD: Stack buffer with async I/O */
|
||||
void SpeakAsync(STRPTR text)
|
||||
{
|
||||
UBYTE phonBuf[512]; /* stack — disappears on return! */
|
||||
Translate(text, strlen(text), (STRPTR)phonBuf, 512);
|
||||
voiceIO->message.io_Data = phonBuf;
|
||||
SendIO((struct IORequest *)voiceIO); /* async — phonBuf gone when this returns */
|
||||
}
|
||||
|
||||
/* CORRECT: Allocate or use static buffer for async */
|
||||
UBYTE phonBuf[2048]; /* static — stays valid */
|
||||
void SpeakAsync(STRPTR text)
|
||||
{
|
||||
Translate(text, strlen(text), (STRPTR)phonBuf, sizeof(phonBuf));
|
||||
voiceIO->message.io_Data = phonBuf;
|
||||
SendIO((struct IORequest *)voiceIO);
|
||||
/* phonBuf lives until AbortIO or CMD_FLUSH */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Can I use translator.library without narrator.device?**
|
||||
|
||||
Yes. The phonetic output is a plain ASCII string — you can save it, analyze it, send it over a network, or use it as input to a custom speech synthesizer. The translator and narrator are independent.
|
||||
|
||||
**Q: Why does the same word sometimes translate differently?**
|
||||
|
||||
The translator uses **context-sensitive** rules. The pronunciation of "read" depends on surrounding tense markers; "record" as a noun vs. verb gets different stress. The same word in different sentences may produce different phonetics — this is correct behavior.
|
||||
|
||||
**Q: How do I make the narrator sound female?**
|
||||
|
||||
Set `voiceIO->sex = 1` (female). This adjusts formant frequencies and baseline pitch. For manual fine-tuning, adjust `voiceIO->pitch` (160–220 Hz for female) and `voiceIO->F1adj` through `F3adj` (formant shifts).
|
||||
|
||||
**Q: Can translator.library handle multiple languages?**
|
||||
|
||||
No. The rule set and exception dictionary are English-only. German, French, or other languages will be treated as misspelled English and produce garbled phonetics. Use locale-specific TTS solutions for non-English speech.
|
||||
|
||||
**Q: How big is the output buffer really needed?**
|
||||
|
||||
Empirically, 4× the input length plus a 512-byte safety margin. The longest single English word phonetics (like "supercalifragilisticexpialidocious") is roughly 80 characters from 34 input characters. A typical sentence expands 2.5–3×.
|
||||
|
||||
**Q: Does Translate() handle punctuation?**
|
||||
|
||||
Yes. Punctuation marks (`.`, `,`, `?`, `!`, `;`, `:`) are passed through to the phonetic output. The narrator device interprets them as prosody cues: `.` = falling intonation, `?` = rising intonation.
|
||||
|
||||
---
|
||||
|
||||
## Use-Case Cookbook
|
||||
|
||||
### 1. Simple One-Shot Speech
|
||||
|
||||
The blocking pattern — suitable for alert messages, game notifications, short announcements:
|
||||
|
||||
```c
|
||||
void Say(STRPTR english)
|
||||
{
|
||||
struct Library *TranslatorBase = OpenLibrary("translator.library", 0);
|
||||
if (!TranslatorBase) return;
|
||||
|
||||
UBYTE phonBuf[2048];
|
||||
LONG rtn = Translate(english, strlen(english),
|
||||
(STRPTR)phonBuf, sizeof(phonBuf));
|
||||
if (rtn == 0)
|
||||
{
|
||||
struct MsgPort *mp = CreatePort(NULL, 0);
|
||||
struct narrator_rb *vio = (struct narrator_rb *)
|
||||
CreateExtIO(mp, sizeof(struct narrator_rb));
|
||||
|
||||
if (OpenDevice("narrator.device", 0, (struct IORequest *)vio, 0) == 0)
|
||||
{
|
||||
vio->rate = 150;
|
||||
vio->pitch = 110;
|
||||
vio->volume = 64;
|
||||
vio->sampfreq = 22200;
|
||||
|
||||
vio->message.io_Command = CMD_WRITE;
|
||||
vio->message.io_Data = phonBuf;
|
||||
vio->message.io_Length = strlen((STRPTR)phonBuf);
|
||||
DoIO((struct IORequest *)vio);
|
||||
|
||||
CloseDevice((struct IORequest *)vio);
|
||||
}
|
||||
DeleteExtIO((struct IORequest *)vio);
|
||||
DeletePort(mp);
|
||||
}
|
||||
CloseLibrary(TranslatorBase);
|
||||
}
|
||||
|
||||
/* Usage: */
|
||||
Say("Game over. Insert coin to continue.");
|
||||
```
|
||||
|
||||
### 2. Animated Talking Head (with Mouth Shapes)
|
||||
|
||||
The narrator can generate mouth width/height data while speaking:
|
||||
|
||||
```c
|
||||
/* Open two I/O requests — one for speech, one for mouth data */
|
||||
struct narrator_rb *voiceIO = /* ... */;
|
||||
struct mouth_rb *mouthIO = (struct mouth_rb *)
|
||||
CreateExtIO(mp, sizeof(struct mouth_rb));
|
||||
|
||||
/* Enable mouth shape generation */
|
||||
voiceIO->mouths = 1; /* non-zero = generate mouth data */
|
||||
|
||||
/* Send speech command */
|
||||
voiceIO->message.io_Command = CMD_WRITE;
|
||||
voiceIO->message.io_Data = phonBuf;
|
||||
voiceIO->message.io_Length = strlen((STRPTR)phonBuf);
|
||||
SendIO((struct IORequest *)voiceIO);
|
||||
|
||||
/* While speaking, read mouth shapes */
|
||||
while (!CheckIO((struct IORequest *)voiceIO))
|
||||
{
|
||||
mouthIO->voice.message.io_Command = CMD_READ;
|
||||
mouthIO->voice.message.io_Data = phonBuf; /* same buffer — narrator correlates */
|
||||
mouthIO->voice.message.io_Length = strlen((STRPTR)phonBuf);
|
||||
DoIO((struct IORequest *)mouthIO);
|
||||
|
||||
/* mouthIO->width = 0..255 (closed → wide open) */
|
||||
/* mouthIO->height = 0..255 (closed → wide open) */
|
||||
AnimateMouth(mouthIO->width, mouthIO->height);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Progressive Translation of Long Text
|
||||
|
||||
For documents or long-form text where a single 2 KB buffer won't suffice:
|
||||
|
||||
```c
|
||||
LONG TranslateLongText(STRPTR text, LONG totalLen, BPTR outputFH)
|
||||
{
|
||||
UBYTE phonBuf[2048];
|
||||
LONG offset = 0;
|
||||
|
||||
while (offset < totalLen)
|
||||
{
|
||||
LONG bytesAvail = totalLen - offset;
|
||||
LONG rtn = Translate(text + offset, bytesAvail,
|
||||
(STRPTR)phonBuf, sizeof(phonBuf));
|
||||
|
||||
if (rtn == 0)
|
||||
{
|
||||
/* Final chunk — write and done */
|
||||
LONG phonLen = strlen((STRPTR)phonBuf);
|
||||
Write(outputFH, phonBuf, phonLen);
|
||||
break;
|
||||
}
|
||||
else if (rtn < 0)
|
||||
{
|
||||
/* Write completed portion, resume at word boundary */
|
||||
LONG phonLen = strlen((STRPTR)phonBuf);
|
||||
Write(outputFH, phonBuf, phonLen);
|
||||
offset += (-rtn);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* unexpected error */
|
||||
return rtn;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modern Analogies
|
||||
|
||||
| Amiga Concept | Modern Equivalent | Why It Maps | Where It Diverges |
|
||||
|---|---|---|---|
|
||||
| **translator.library** | macOS `NSSpeechSynthesizer` / Windows SAPI Text-to-Speech | Both accept English text and produce speech. The API philosophy — text in, audio out — is identical | Modern APIs bundle translation and synthesis; Amiga splits them into library (translate) and device (speak) |
|
||||
| **ARPABET phonemes** | IPA (International Phonetic Alphabet) | Both encode pronunciation as discrete symbols. ARPABET is a machine-readable subset of IPA | ARPABET is English-only; IPA is universal. ARPABET uses ASCII, IPA uses Unicode |
|
||||
| **450 context-sensitive rules** | Modern TTS neural networks (Tacotron, FastSpeech) | Both learn pronunciation from data — rules are a 1985 hand-crafted "model" | Neural TTS requires gigabytes of training data; rule-based works with zero training |
|
||||
| **narrator.device formant synthesis** | Vocaloid / singing synthesis | Both use formant models (F0, F1, F2...) to generate vocal sounds | Narrator.device is a 1985-era 8-bit formant synth; Vocaloid uses concatenative sampling + ML |
|
||||
| **`Say` command / `speak:` handler** | `say` command on macOS / `espeak` on Linux | Both provide command-line text-to-speech | Amiga `Say` feeds translator.library → narrator.device; macOS `say` uses a system-wide speech server |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- ADCD 2.1: *ROM Kernel Reference Manual: Libraries* — Chapter 36: Translator Library
|
||||
- ADCD 2.1: *ROM Kernel Reference Manual: Devices* — Chapter 8: Narrator Device
|
||||
- NDK 3.9: `devices/narrator.h` — `narrator_rb` and `mouth_rb` structures
|
||||
- NDK 3.9: `clib/translator_protos.h` — `Translate()` prototype
|
||||
- AmigaOS Documentation Wiki: [Narrator Device](https://wiki.amigaos.net/wiki/Narrator_Device) — complete phoneme table and phonetic writing guide
|
||||
- AmigaOS Documentation Wiki: [Translator Library](https://wiki.amigaos.net/wiki/Translator_Library) — OS 4.x interface reference
|
||||
- See also: [audio.md](../10_devices/audio.md) — audio.device DMA channel allocation used by narrator
|
||||
- See also: [iffparse.md](iffparse.md) — IFF FTXT parsing (the AmigaGuide format sometimes wraps speech metadata in IFF chunks)
|
||||
|
|
@ -142,7 +142,7 @@ ULONG __saveds __asm MyHookFunc(
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Initialise the hook: */
|
||||
/* Initialize the hook: */
|
||||
struct Hook myHook;
|
||||
myHook.h_Entry = (HOOKFUNC)HookEntry; /* utility.library glue */
|
||||
myHook.h_SubEntry = (HOOKFUNC)MyHookFunc;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ AmigaOS has no built-in TCP/IP stack. Third-party stacks (AmiTCP, Miami, Roadsho
|
|||
| File | Description |
|
||||
|---|---|
|
||||
| [tcp_ip_stacks.md](tcp_ip_stacks.md) | Stack architecture: Amiga vs Unix model, SANA-II integration, PPP/SLIP dial-up, modem setup, Ethernet cards, MiSTer virtual NIC, detailed stack comparison |
|
||||
| [bsdsocket.md](bsdsocket.md) | BSD socket API: per-task library base, LVO table, WaitSelect with Exec signals, error handling, BSD/POSIX differences |
|
||||
| [bsdsocket.md](bsdsocket.md) | **BSD socket API deep dive: per-task library base, LVO table, WaitSelect with Exec signals, event-loop cookbook, non-blocking I/O, multi-socket patterns, performance, pitfalls** |
|
||||
| [sana2.md](sana2.md) | SANA-II driver specification: buffer management hooks, send/receive patterns, device query, driver requirements |
|
||||
| [protocols.md](protocols.md) | Protocol implementation: DNS resolution, TCP client/server, WaitSelect integration, UDP, DHCP sequence |
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -39,7 +39,7 @@ flowchart TD
|
|||
| **Per-process state** | fd table in kernel | Socket "library base" per opener |
|
||||
| **Protection** | Full memory protection | None — any process can corrupt stack state |
|
||||
| **Signal integration** | select/poll/epoll | WaitSelect + Exec signal bits |
|
||||
| **Performance** | Optimised kernel path | No syscall overhead, but no DMA offload |
|
||||
| **Performance** | Optimized kernel path | No syscall overhead, but no DMA offload |
|
||||
|
||||
### The Full Network Stack
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ flowchart LR
|
|||
### PPP Connection Sequence
|
||||
|
||||
```
|
||||
1. Modem initialisation:
|
||||
1. Modem initialization:
|
||||
→ ATZ (reset modem)
|
||||
← OK
|
||||
→ AT&F1 (factory defaults, hardware flow control)
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ Development tools for building Amiga software, from native compilers to modern c
|
|||
| File | Description |
|
||||
|---|---|
|
||||
| [gcc_amiga.md](gcc_amiga.md) | m68k-amigaos-gcc cross-compiler: bebbo's toolchain, Docker setup, CPU targets, libnix/ixemul startup |
|
||||
| [vbcc.md](vbcc.md) | **VBCC: Volker Barthelmann's portable C compiler — `__reg()` storage class, AmigaOS/MorphOS/AROS targets, vlink integration, cross-compilation** |
|
||||
| [sasc.md](sasc.md) | SAS/C 6.x: pragma format with register encoding, compiler/linker flags, __saveds/__asm idioms, SAS/C vs GCC comparison |
|
||||
| [stormc.md](stormc.md) | StormC native IDE: C/C++ with exceptions, integrated debugger, PowerPC support, version history |
|
||||
| [vasm_vlink.md](vasm_vlink.md) | vasm assembler and vlink linker |
|
||||
| [vasm_vlink.md](vasm_vlink.md) | **vasm assembler & vlink linker: modular architecture (CPU/syntax/output modules), Devpac/PhxAss compatibility, optimization system, linker scripts, multi-file projects, C↔asm interop, 30+ output formats, cross-platform workflows** |
|
||||
| [fd_files.md](fd_files.md) | FD/SFD file format and LVO generation |
|
||||
| [pragmas.md](pragmas.md) | Compiler pragmas and inline stubs: SAS/C pragmas, GCC inline asm, proto headers, fd2pragma |
|
||||
| [ndk.md](ndk.md) | NDK versions (3.1/3.9/3.2): contents, downloads, cross-compiler integration |
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ flowchart TD
|
|||
| Tool | Type | Requires | Best For |
|
||||
|---|---|---|---|
|
||||
| **Enforcer** | MMU memory watchdog | 68020+ with MMU | Catching NULL ptr, illegal reads/writes |
|
||||
| **MuForce** | MMU memory watchdog | 68040/060 | Same as Enforcer, optimised for 040/060 |
|
||||
| **MuForce** | MMU memory watchdog | 68040/060 | Same as Enforcer, optimized for 040/060 |
|
||||
| **MuGuardianAngel** | Stack overflow detector | 68040/060 | Catching stack overflow crashes |
|
||||
| **BareFoot** | Serial kernel debugger | Serial cable + terminal | Exec internals, boot crashes, system hangs |
|
||||
| **wack / SAD** | ROM-resident debugger | Guru Meditation (crash) | Post-crash analysis, ROM debugging |
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ m68k-amigaos-gcc -noixemul -o hello hello.c
|
|||
# -m68040 — target 68040
|
||||
# -m68060 — target 68060
|
||||
# -m68881 — use 68881/68882 FPU
|
||||
# -Os — optimise for size
|
||||
# -O2 — optimise for speed
|
||||
# -Os — optimize for size
|
||||
# -O2 — optimize for speed
|
||||
# -fomit-frame-pointer — free up A5
|
||||
# -fbaserel — base-relative addressing (small data model)
|
||||
# -resident — generate resident-capable code
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
SAS/C (originally Lattice C) was the dominant commercial C compiler for AmigaOS from the mid-1980s through the mid-1990s. Version **6.58** is the final release. It produces highly optimised 68k code with deep AmigaOS integration, including pragma-based system calls, SAS-specific debug formats, and a built-in profiler.
|
||||
SAS/C (originally Lattice C) was the dominant commercial C compiler for AmigaOS from the mid-1980s through the mid-1990s. Version **6.58** is the final release. It produces highly optimized 68k code with deep AmigaOS integration, including pragma-based system calls, SAS-specific debug formats, and a built-in profiler.
|
||||
|
||||
Most existing Amiga source code and documentation assumes SAS/C conventions. Understanding its idioms is essential for working with legacy codebases.
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ lc -v -O -b0 -j73 main.c util.c LINK TO myapp
|
|||
| Flag | Meaning |
|
||||
|---|---|
|
||||
| `-v` | Verbose output |
|
||||
| `-O` | Enable global optimiser |
|
||||
| `-O` | Enable global optimizer |
|
||||
| `-b0` | Small data model (A4-relative, max 64 KB globals) |
|
||||
| `-b1` | Large data model (absolute addressing) |
|
||||
| `-j30` | Generate 68030 code |
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
| Version | Year | Key Features |
|
||||
|---|---|---|
|
||||
| StormC 1.0 | 1996 | Initial release, C compiler, basic IDE |
|
||||
| StormC 2.0 | 1997 | C++ support, improved optimiser |
|
||||
| StormC 2.0 | 1997 | C++ support, improved optimizer |
|
||||
| StormC 3.0 | 1998 | Full C++ with exceptions, STL, PowerPC support |
|
||||
| StormC 4.0 | 1999 | Final version, OS 3.5 integration |
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ stormc -O2 -m68020 -o myapp main.c util.c
|
|||
; -m68020 Target 68020+
|
||||
; -m68040 Target 68040
|
||||
; -m68060 Target 68060
|
||||
; -O0 to -O3 Optimisation level
|
||||
; -O0 to -O3 Optimization level
|
||||
; -g Debug info
|
||||
; -c Compile only (no link)
|
||||
; -I<path> Include path
|
||||
|
|
@ -78,7 +78,7 @@ stormc -O2 -m68020 -o myapp main.c util.c
|
|||
| **IDE** | No (CLI + editor) | No (CLI + any editor) | **Yes (native GUI)** |
|
||||
| **Debugger** | External (CodeProbe) | GDB remote | **Integrated** |
|
||||
| **Cross-compile** | No (native only) | **Yes (Linux/macOS host)** | No (native only) |
|
||||
| **Optimiser quality** | Excellent | Good | Good |
|
||||
| **Optimizer quality** | Excellent | Good | Good |
|
||||
| **PowerPC** | No | No | Yes (v3+) |
|
||||
| **Availability** | Abandonware | Free / open source | Abandonware |
|
||||
| **Legacy code compat** | High (dominant compiler) | Moderate (GCC differences) | Moderate |
|
||||
|
|
|
|||
|
|
@ -1,113 +1,842 @@
|
|||
[← Home](../README.md) · [Toolchain](README.md)
|
||||
|
||||
# vasm and vlink — Assembler and Linker
|
||||
# vasm & vlink — Portable Assembler and Linker for Amiga
|
||||
|
||||
## Overview
|
||||
|
||||
**vasm** is a modern, free, multi-target assembler with excellent Amiga support. **vlink** is its companion linker. Together they form the primary open-source toolchain for 68k Amiga development.
|
||||
**vasm** is a modern, free, portable assembler by Frank Wille and Volker Barthelmann.
|
||||
|
||||
**vlink** is its companion linker.
|
||||
|
||||
Together they form the primary open-source toolchain for 68k Amiga development — replacing the proprietary Devpac/PhxAss assemblers and the aging `blink` linker from SAS/C.
|
||||
|
||||
**vasm** compiles on any host (Linux, macOS, Windows, AmigaOS, MorphOS, Atari TOS) and targets 17+ CPU families with 4 syntax dialects and 30+ output formats.
|
||||
|
||||
Unlike legacy assemblers tied to one platform, vasm's modular architecture lets you **cross-assemble** Amiga executables from a modern development machine. Combined with vlink's multi-format linking, GNU-style linker scripts, and support for Amiga hunk, ELF, a.out, and raw binary formats, this toolchain bridges retro development with modern CI/CD workflows.
|
||||
|
||||
---
|
||||
|
||||
## Architecture — The Modular Engine
|
||||
|
||||
### Three Independent Module Layers
|
||||
|
||||
vasm separates concerns into three orthogonal modules. You pick one of each at compile time:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Compile-Time Selection"
|
||||
CPU["CPU Module<br/>m68k, ppc, arm, x86, z80, 6502…"]
|
||||
SYNTAX["Syntax Module<br/>mot, std, madmac, oldstyle"]
|
||||
OUTPUT["Output Module<br/>hunk, elf, a.out, bin, vobj…"]
|
||||
end
|
||||
|
||||
subgraph "Runtime Pipeline"
|
||||
SRC["Source<br/>.s / .asm"] --> PARSE["Syntax Parser<br/>(SYNTAX module)"]
|
||||
PARSE --> ENCODE["Opcode Encoder<br/>(CPU module)"]
|
||||
ENCODE --> OPT["Optimizer<br/>(CPU module)"]
|
||||
OPT --> EMIT["Object Emitter<br/>(OUTPUT module)"]
|
||||
EMIT --> OBJ["Object / Binary<br/>.o / raw"]
|
||||
end
|
||||
|
||||
CPU -.-> ENCODE
|
||||
SYNTAX -.-> PARSE
|
||||
OUTPUT -.-> EMIT
|
||||
|
||||
style CPU fill:#e8f4fd,stroke:#2196f3
|
||||
style SYNTAX fill:#fff9c4,stroke:#f9a825
|
||||
style OUTPUT fill:#e8f5e9,stroke:#4caf50
|
||||
```
|
||||
|
||||
This design means the M68k backend works identically whether you write Motorola syntax (Devpac-compatible), GNU-as style (`std`), Atari MadMac syntax, or old-style 8-bit mnemonics. Adding a new CPU requires only a new CPU module — all existing syntax and output modules work immediately.
|
||||
|
||||
### CPU Modules
|
||||
|
||||
| Module | Target | Amiga Relevance |
|
||||
|---|---|---|
|
||||
| **m68k** | 68000–68060, CPU32, 68881/2, 68851 MMU, Apollo 68080 | Primary Amiga target |
|
||||
| **ppc** | POWER, 40x, 440, 6xx, 7xx, Book-E, e300, e500 | WarpOS / AmigaOS 4 |
|
||||
| **coldfire** | V2, V3, V4, V4e | Amiga clone hardware |
|
||||
| **arm** | ARMv1–v4, THUMB | Cross-platform / emulator tools |
|
||||
| **x86** | IA32 8/16/32-bit (AT&T syntax) | AROS / cross-tools |
|
||||
| **6502** | 6502, 65C02, 65816, Mega65 | Retro platforms |
|
||||
| **z80** | Z80, 8080, 8085, GBZ80 | Retro platforms |
|
||||
|
||||
### Syntax Modules
|
||||
|
||||
| Module | Style | Equivalent To |
|
||||
|---|---|---|
|
||||
| **mot** | Motorola 68k | Devpac, PhxAss, AsmOne, Barfly |
|
||||
| **std** | GNU-as AT&T | `m68k-elf-as`, `powerpc-elf-as` |
|
||||
| **madmac** | Atari MadMac | Atari ST assemblers (6502, 68k, Jaguar) |
|
||||
| **oldstyle** | Classic 8-bit | 6502/Z80 era assemblers |
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### From Source (All Platforms)
|
||||
|
||||
```bash
|
||||
# Build from source:
|
||||
# vasm — assembler
|
||||
wget http://sun.hasenbraten.de/vasm/release/vasm.tar.gz
|
||||
tar xzf vasm.tar.gz && cd vasm
|
||||
make CPU=m68k SYNTAX=mot # Motorola syntax
|
||||
# or:
|
||||
make CPU=m68k SYNTAX=madmac # Atari MadMac syntax
|
||||
|
||||
# vlink:
|
||||
# Build M68k with Motorola syntax (Amiga target):
|
||||
make CPU=m68k SYNTAX=mot
|
||||
# Produces: vasmm68k_mot
|
||||
|
||||
# Build with Devpac compatibility flags baked in:
|
||||
make CPU=m68k SYNTAX=mot
|
||||
# Use -devpac flag at runtime for full compatibility
|
||||
|
||||
# vlink — linker
|
||||
wget http://sun.hasenbraten.de/vlink/release/vlink.tar.gz
|
||||
tar xzf vlink.tar.gz && cd vlink && make
|
||||
tar xzf vlink.tar.gz && cd vlink
|
||||
make
|
||||
# Produces: vlink
|
||||
```
|
||||
|
||||
### Host-Specific Makefiles
|
||||
|
||||
vasm ships with platform-specific Makefiles for native Amiga and retro hosts:
|
||||
|
||||
| Makefile | Target Platform |
|
||||
|---|---|
|
||||
| `Makefile` | Linux / macOS / Unix (gcc) |
|
||||
| `Makefile.68k` | AmigaOS 68020+ (vbcc) |
|
||||
| `Makefile.OS4` | AmigaOS 4 (vbcc) |
|
||||
| `Makefile.MOS` | MorphOS (vbcc) |
|
||||
| `Makefile.WOS` | WarpOS (vbcc) |
|
||||
| `Makefile.TOS` | Atari TOS 68000 (vbcc) |
|
||||
| `Makefile.MiNT` | Atari MiNT 68020+ (vbcc) |
|
||||
| `Makefile.Win32` | Windows (MSVC) |
|
||||
| `Makefile.Win32FromLinux` | Cross-compile Windows binary from Linux |
|
||||
|
||||
### CMake Build
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake -DVASM_CPU=m68k -DVASM_SYNTAX=mot ..
|
||||
make
|
||||
```
|
||||
|
||||
The resulting binary is named `vasm<CPU>_<SYNTAX>` — e.g. `vasmm68k_mot`, `vasmppc_std`.
|
||||
|
||||
### Pre-Built Binaries
|
||||
|
||||
Both daily snapshots and tagged release binaries are available from the official site for Windows, AmigaOS, MorphOS, and Linux. These are the easiest path for beginners.
|
||||
|
||||
---
|
||||
|
||||
## vasm Usage
|
||||
## vasm Usage — Comprehensive Reference
|
||||
|
||||
### Basic Invocation
|
||||
|
||||
```bash
|
||||
# Assemble to Amiga hunk object:
|
||||
vasmm68k_mot -Fhunk -o output.o input.s
|
||||
|
||||
# Common flags:
|
||||
# -Fhunk — output Amiga hunk format
|
||||
# -Fbin — raw binary
|
||||
# -Felf — ELF format
|
||||
# -m68000 — target 68000 (default)
|
||||
# -m68020 — target 68020+
|
||||
# -m68040 — target 68040
|
||||
# -m68060 — target 68060
|
||||
# -no-opt — disable optimisations
|
||||
# -I<path> — include path
|
||||
# -D<sym>=<val> — define symbol
|
||||
# -phxass — PhxAss compatibility mode
|
||||
# -devpac — Devpac compatibility mode
|
||||
```
|
||||
|
||||
---
|
||||
### Complete Flag Reference
|
||||
|
||||
## vlink Usage
|
||||
| Flag | Description |
|
||||
|---|---|
|
||||
| `-Fhunk` | Output Amiga hunk format object file |
|
||||
| `-Felf` | Output ELF object file |
|
||||
| `-Fbin` | Output raw binary (no headers, no relocations) |
|
||||
| `-Fvobj` | Output VOBJ (versatile object format, vlink-native) |
|
||||
| `-Faout` | Output a.out object file |
|
||||
| `-o <file>` | Output file name |
|
||||
| `-L <file>` | Generate listing file |
|
||||
| `-I<path>` | Add include path |
|
||||
| `-D<sym>=<val>` | Define symbol with value |
|
||||
| `-D<sym>` | Define symbol = 1 |
|
||||
| `-m68000` | Target 68000 (default) |
|
||||
| `-m68020` | Target 68020+ |
|
||||
| `-m68030` | Target 68030+ |
|
||||
| `-m68040` | Target 68040+ |
|
||||
| `-m68060` | Target 68060 |
|
||||
| `-no-opt` | Disable all assembler optimizations |
|
||||
| `-no-fpu` | Disable FPU instructions (68881/68882) |
|
||||
| `-no-mmu` | Disable MMU instructions (68851) |
|
||||
| `-devpac` | Devpac compatibility mode |
|
||||
| `-phxass` | PhxAss compatibility mode |
|
||||
| `-chklabels` | Warn on unused/redefined labels (default) |
|
||||
| `-nocase` | Case-insensitive symbols |
|
||||
| `-align` | Enable automatic alignment |
|
||||
| `-spaces` | Allow spaces in operands (Devpac compatible) |
|
||||
| `-ldots` | Accept `...` for local labels (PhxAss compatible) |
|
||||
| `-warnunaligned` | Warn on odd-address memory accesses |
|
||||
| `-quiet` | Suppress banner and progress |
|
||||
| `-version` | Print version and module info |
|
||||
|
||||
### Devpac Compatibility Mode (`-devpac`)
|
||||
|
||||
Enables the full Devpac dialect: unsigned right-shifts, named macro arguments, `OPT` directive parsing, `STRUCT`/`RS` directives with Devpac semantics, and Devpac-style local label scoping (`\@`).
|
||||
|
||||
```bash
|
||||
# Link hunk objects into executable:
|
||||
vlink -bamigahunk -o output input1.o input2.o -Llib -lexec -ldos
|
||||
|
||||
# Common flags:
|
||||
# -bamigahunk — output Amiga hunk executable
|
||||
# -brawbin1 — raw binary
|
||||
# -s — strip symbols
|
||||
# -L<path> — library search path
|
||||
# -l<lib> — link with library
|
||||
# -Rshort — use short (16-bit) relocations where possible
|
||||
vasmm68k_mot -Fhunk -devpac -o output.o input.s
|
||||
```
|
||||
|
||||
### PhxAss Compatibility Mode (`-phxass`)
|
||||
|
||||
Enables PhxAss-specific extensions: `NEAR CODE`/`NEAR DATA`, `OPT` as PhxAss directive, and PhxAss local label rules.
|
||||
|
||||
---
|
||||
|
||||
## Example: Minimal Amiga Executable
|
||||
## M68k CPU Module — Deep Dive
|
||||
|
||||
### Supported Instructions
|
||||
|
||||
Full 68000 through 68060 instruction sets including:
|
||||
- All integer instructions per CPU level
|
||||
- 68881/68882 FPU (`FMOVE`, `FADD`, `FMUL`, `FDIV`, `FSQRT`, etc.)
|
||||
- 68851 PMMU (`PLOAD`, `PTEST`, `PFLUSH`, `PMOVE`, etc.)
|
||||
- Apollo 68080 extensions (`MOVEIW`, `ADDIW`, `SUBIW`, `CMPIW`, `LPSTOP`)
|
||||
|
||||
### Automatically Selected Instruction Variants
|
||||
|
||||
When targeting higher CPUs, vasm selects the appropriate encoding automatically:
|
||||
|
||||
```asm
|
||||
; hello.s — minimal AmigaOS executable
|
||||
SECTION code,CODE
|
||||
; These produce different encodings depending on -m68000 vs -m68020:
|
||||
MOVE.L D0, (A0)+ ; 68000: MOVE.L (An)+ form
|
||||
; 68020+: uses scaled addressing if beneficial
|
||||
|
||||
MULS.L D1, D2:D3 ; 68000: ERROR (32-bit MULS requires 68020+)
|
||||
; 68020+: valid
|
||||
|
||||
BFEXTU (A0){4:8}, D0 ; 68000: ERROR
|
||||
; 68020+: valid bitfield extract
|
||||
|
||||
; Apollo 68080 special:
|
||||
ANDI.L #$FF, D0 ; -m68080: optimized to EXTUB.L D0
|
||||
```
|
||||
|
||||
### Addressing Mode Optimization
|
||||
|
||||
vasm automatically optimizes addressing modes:
|
||||
|
||||
```asm
|
||||
; Written by programmer:
|
||||
LEA label(PC), A0 ; PC-relative LEA
|
||||
MOVE.L label, D0 ; Absolute long → may relax to PC-relative
|
||||
|
||||
; vasm may rewrite:
|
||||
MOVE.L label, D0 ; → MOVE.L label(PC), D0 (shorter, position-independent)
|
||||
BRA far_target ; → JMP far_target if out of 16-bit range
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optimization System
|
||||
|
||||
vasm performs multiple optimization passes by default. All optimizations are safe for position-independent code.
|
||||
|
||||
### Default Optimizations (always on)
|
||||
|
||||
| Optimization | Description |
|
||||
|---|---|
|
||||
| **Branch shortening** | Choose smallest encoding: `BRA.S` vs `BRA.W` vs `JMP` |
|
||||
| **Absolute → PC-relative** | Convert `MOVE.L abs, Dn` to `MOVE.L abs(PC), Dn` |
|
||||
| **Addressing mode** | Replace slower addressing with faster equivalent |
|
||||
| **LEA optimization** | Convert `LEA (An), An` to `MOVE.L An, An` |
|
||||
| **MOVEQ** | `MOVE.L #0–127, Dn` → `MOVEQ #n, Dn` |
|
||||
| **ADDQ/SUBQ** | `ADDI #1–8, Dn` → `ADDQ #n, Dn` |
|
||||
| **CLR** | `MOVE.L #0, Dn` → `CLR.L Dn` (or `MOVEQ #0, Dn`) |
|
||||
|
||||
### CPU-Specific Optimizations
|
||||
|
||||
```
|
||||
-m68020+: MOVE.W #0, (An)+ → CLR.W (An)+
|
||||
-m68040+: Use MOVE16 for block copies (when beneficial)
|
||||
-m68060: Avoid pipeline stalls (instruction reordering lite)
|
||||
-m68080: EXTUB.L, ADDIW/SUBIW, CMPIW, LPSTOP
|
||||
```
|
||||
|
||||
### Disabling Optimizations
|
||||
|
||||
```bash
|
||||
vasmm68k_mot -no-opt -Fhunk -o output.o input.s ; All optimizations off
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Disabling optimizations is useful when comparing output with another assembler, or when generating exact byte-identical builds from known-good disassembly.
|
||||
|
||||
---
|
||||
|
||||
## vlink — Architecture & Usage
|
||||
|
||||
### Overview
|
||||
|
||||
vlink is a multi-format linker that can read and write 30+ object and executable formats. For Amiga development, its primary role is linking hunk-format object files into AmigaOS executables, but it also handles ELF→hunk conversion, binary output for ROM images, and cross-format linking.
|
||||
|
||||
### How vlink Differs from blink
|
||||
|
||||
| Feature | vlink | SAS/C blink |
|
||||
|---|---|---|
|
||||
| **Input formats** | hunk, ELF, a.out, VOBJ, TOS, o65 | hunk only |
|
||||
| **Output formats** | hunk, ELF, a.out, raw, hex, S-rec | hunk only |
|
||||
| **Linker scripts** | GNU-style `.cmd` files | Manual `FROM`/`TO` |
|
||||
| **Dead-code elimination** | `KEEP()` / garbage collection | None |
|
||||
| **Cross-platform** | Linux, macOS, Windows, Amiga | Amiga only |
|
||||
| **Active maintenance** | Yes (v0.18a, 2025) | No (abandoned 1990s) |
|
||||
|
||||
### Basic Invocation
|
||||
|
||||
```bash
|
||||
# Link hunk objects into Amiga executable:
|
||||
vlink -bamigahunk -o myapp input1.o input2.o -Llib -lexec -ldos
|
||||
|
||||
# Link with a linker script:
|
||||
vlink -bamigahunk -o myapp input.o -L. -T vlink.cmd
|
||||
```
|
||||
|
||||
### Complete Flag Reference
|
||||
|
||||
| Flag | Description |
|
||||
|---|---|
|
||||
| `-bamigahunk` | Output Amiga hunk executable |
|
||||
| `-brawbin` | Output raw binary |
|
||||
| `-belf32m68k` | Output ELF 32-bit M68k |
|
||||
| `-o <file>` | Output file name |
|
||||
| `-s` | Strip all symbols from output |
|
||||
| `-x` | Strip local symbols only |
|
||||
| `-r` | Relocatable output (partial link) |
|
||||
| `-L<path>` | Library search path |
|
||||
| `-l<lib>` | Link with library (`lib<lib>.a` or `<lib>.lib`) |
|
||||
| `-T <file>` | Use linker script |
|
||||
| `-Map <file>` | Generate link map |
|
||||
| `-Rshort` | Prefer short (16-bit) relocations |
|
||||
| `-minalign <n>` | Minimum section alignment |
|
||||
| `-nostdlib` | Don't link standard startup/libraries |
|
||||
| `-e <sym>` | Set entry point symbol |
|
||||
| `-defsym <sym>=<val>` | Define symbol |
|
||||
| `-baseoff` | Output base-relative (position-independent) |
|
||||
| `-kick1` | Kickstart 1.x compatible executable |
|
||||
| `-wfail` | Treat warnings as errors |
|
||||
|
||||
### Linker Scripts — GNU-Style Control
|
||||
|
||||
vlink linker scripts use a memory-regions + section-mapping model:
|
||||
|
||||
```ld
|
||||
/* vlink.cmd — Linker script for AmigaOS executable */
|
||||
|
||||
/* Define memory regions */
|
||||
MEMORY
|
||||
{
|
||||
CODE: ORIGIN=0x00000000 LENGTH=512K
|
||||
DATA: ORIGIN=0x00080000 LENGTH=256K
|
||||
}
|
||||
|
||||
/* Map input sections to output sections */
|
||||
SECTIONS
|
||||
{
|
||||
.text :
|
||||
{
|
||||
*(.text .text.*) /* All code sections */
|
||||
*(.rodata .rodata.*) /* Read-only data */
|
||||
KEEP(*(.init .init.*)) /* Never garbage-collect init */
|
||||
KEEP(*(.fini .fini.*))
|
||||
} > CODE
|
||||
|
||||
.data :
|
||||
{
|
||||
*(.data .data.*)
|
||||
*(COMMON)
|
||||
} > DATA
|
||||
|
||||
.bss (NOLOAD) :
|
||||
{
|
||||
*(.bss .bss.*)
|
||||
} > DATA
|
||||
|
||||
/* Constructor/destructor lists */
|
||||
VBCC_CONSTRUCTORS
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> When no linker script is provided, vlink uses sensible defaults: all code sections merged into one HUNK_CODE, all data into HUNK_DATA, and BSS into HUNK_BSS.
|
||||
|
||||
### Library Resolution
|
||||
|
||||
vlink resolves library references using the standard Amiga naming conventions:
|
||||
|
||||
```bash
|
||||
# These are equivalent on Amiga:
|
||||
vlink -lamiga myapp.o -Llib:
|
||||
# Links against lib:amiga.lib
|
||||
|
||||
# -l<name> searches for <name>.lib or lib<name>.a in -L paths
|
||||
vlink -lexec -ldos myapp.o -Llib: -L.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Worked Examples
|
||||
|
||||
### Example 1: Minimal Amiga Executable (Motorola Syntax)
|
||||
|
||||
```asm
|
||||
; hello.s — Minimal AmigaOS executable using vasm mot-syntax
|
||||
SECTION code,CODE
|
||||
|
||||
start:
|
||||
move.l 4.w,a6 ; ExecBase
|
||||
lea dosname(pc),a1
|
||||
moveq #0,d0
|
||||
jsr -552(a6) ; OpenLibrary
|
||||
tst.l d0
|
||||
beq.s .exit
|
||||
move.l d0,a6 ; DOSBase
|
||||
move.l 4.w,a6 ; SysBase → A6
|
||||
lea dosname(pc),a1 ; library name
|
||||
moveq #0,d0 ; any version
|
||||
jsr -552(a6) ; OpenLibrary()
|
||||
|
||||
jsr -60(a6) ; Output() → stdout handle
|
||||
move.l d0,d1
|
||||
lea msg(pc),a0
|
||||
move.l a0,d2
|
||||
moveq #12,d3
|
||||
jsr -48(a6) ; Write(fh, buf, len)
|
||||
tst.l d0
|
||||
beq.s .exit ; library not found
|
||||
move.l d0,a6 ; DOSBase → A6
|
||||
|
||||
move.l a6,a1
|
||||
move.l 4.w,a6
|
||||
jsr -414(a6) ; CloseLibrary
|
||||
; Write("Hello Amiga!\n") to stdout
|
||||
jsr -60(a6) ; Output() → stdout handle
|
||||
move.l d0,d1 ; D1 = file handle
|
||||
lea msg(pc),a0
|
||||
move.l a0,d2 ; D2 = buffer pointer
|
||||
moveq #13,d3 ; D3 = length
|
||||
jsr -48(a6) ; Write(fh, buf, len)
|
||||
|
||||
; Cleanup
|
||||
move.l a6,a1 ; DOSBase
|
||||
move.l 4.w,a6 ; SysBase → A6
|
||||
jsr -414(a6) ; CloseLibrary()
|
||||
|
||||
.exit:
|
||||
moveq #0,d0
|
||||
rts
|
||||
moveq #0,d0 ; return code
|
||||
rts
|
||||
|
||||
dosname: dc.b "dos.library",0
|
||||
msg: dc.b "Hello Amiga",10
|
||||
EVEN
|
||||
msg: dc.b "Hello Amiga!",10
|
||||
EVEN
|
||||
```
|
||||
|
||||
Build and link:
|
||||
|
||||
```bash
|
||||
vasmm68k_mot -Fhunk -o hello.o hello.s
|
||||
vlink -bamigahunk -o hello hello.o
|
||||
```
|
||||
|
||||
### Example 2: Multi-File Project with Data Section
|
||||
|
||||
```asm
|
||||
; main.s — Code hunk
|
||||
SECTION code,CODE
|
||||
xdef _start
|
||||
|
||||
_start:
|
||||
move.l 4.w,a6
|
||||
lea libname(pc),a1
|
||||
moveq #0,d0
|
||||
jsr -552(a6) ; OpenLibrary("dos.library")
|
||||
move.l d0,a5
|
||||
beq .exit
|
||||
|
||||
; Print pre-initialized string from data section
|
||||
jsr -60(a5) ; Output()
|
||||
move.l d0,d1
|
||||
lea message,a0 ; Absolute reference → needs reloc
|
||||
move.l a0,d2
|
||||
moveq #msg_len,d3
|
||||
jsr -48(a5) ; Write()
|
||||
|
||||
move.l a5,a1
|
||||
move.l 4.w,a6
|
||||
jsr -414(a6) ; CloseLibrary()
|
||||
.exit: moveq #0,d0
|
||||
rts
|
||||
|
||||
libname: dc.b "dos.library",0
|
||||
|
||||
; data.s — Data hunk
|
||||
SECTION data,DATA
|
||||
xdef message,msg_len
|
||||
|
||||
message: dc.b "Hello from DATA hunk!",10
|
||||
msg_len equ *-message
|
||||
```
|
||||
|
||||
Build:
|
||||
|
||||
```bash
|
||||
vasmm68k_mot -Fhunk -o main.o main.s
|
||||
vasmm68k_mot -Fhunk -o data.o data.s
|
||||
vlink -bamigahunk -o myapp main.o data.o
|
||||
```
|
||||
|
||||
### Example 3: Calling C from Assembly (vbcc Integration)
|
||||
|
||||
```asm
|
||||
; asm_part.s — Assembly file linked with C
|
||||
SECTION code,CODE
|
||||
xdef _Multiply
|
||||
; int32_t Multiply(int32_t a, int32_t b);
|
||||
; Args: D0 = a, D1 = b. Return: D0
|
||||
|
||||
_Multiply:
|
||||
muls.l d1,d0 ; D0 = a * b (requires 68020+)
|
||||
rts
|
||||
|
||||
xdef _SwapBytes
|
||||
; uint32_t SwapBytes(uint32_t val);
|
||||
; Args: D0 = val. Return: D0
|
||||
|
||||
_SwapBytes:
|
||||
ror.w #8,d0
|
||||
swap d0
|
||||
ror.w #8,d0
|
||||
rts
|
||||
```
|
||||
|
||||
```c
|
||||
// main.c — C file compiled with vbcc
|
||||
extern int32_t Multiply(int32_t a, int32_t b);
|
||||
extern uint32_t SwapBytes(uint32_t val);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int32_t result = Multiply(7, 6); // → 42
|
||||
uint32_t swapped = SwapBytes(0x12345678); // → 0x78563412
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Build with vasm + vbcc + vlink:
|
||||
|
||||
```bash
|
||||
vasmm68k_mot -Fhunk -m68020 -o asm_part.o asm_part.s
|
||||
vc +aos68k -c main.c -o main.o
|
||||
vlink -bamigahunk -o program main.o asm_part.o -L$VBCC/ targets/m68k-amigaos/lib -lvc
|
||||
```
|
||||
|
||||
### Example 4: Linker Script for ROM Image
|
||||
|
||||
```asm
|
||||
; rom.s — Code for a ROM image (absolute addressing)
|
||||
SECTION rom_code,CODE
|
||||
org $00F80000 ; ROM base in Amiga address space
|
||||
|
||||
ROM_Entry:
|
||||
move.w #$4EF9,(ROM_Jump) ; JMP opcode
|
||||
lea InitCode(pc),a0
|
||||
move.l a0,(ROM_Jump+2)
|
||||
jmp InitCode
|
||||
ROM_Jump: ds.l 2
|
||||
|
||||
InitCode:
|
||||
; Hardware initialization
|
||||
lea $DFF000,a0
|
||||
move.w #$7FFF,$9A(a0) ; Disable all interrupts
|
||||
; ... more init ...
|
||||
rts
|
||||
```
|
||||
|
||||
```ld
|
||||
/* rom.cmd — Linker script for ROM binary */
|
||||
MEMORY
|
||||
{
|
||||
ROM: ORIGIN=0x00F80000 LENGTH=512K
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.text :
|
||||
{
|
||||
*(.text .text.*)
|
||||
} > ROM
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
vasmm68k_mot -Fhunk -o rom.o rom.s
|
||||
vlink -brawbin -T rom.cmd -o kickstart.rom rom.o
|
||||
```
|
||||
|
||||
### Example 5: Macros and Conditional Assembly
|
||||
|
||||
```asm
|
||||
; macros.i — Include file demonstrating vasm macro features
|
||||
|
||||
; Debug print macro (conditional on DEBUG symbol)
|
||||
ifd DEBUG
|
||||
DEBUG_PRINT macro
|
||||
movem.l d0-d1/a0-a1,-(sp)
|
||||
move.l \1,d1 ; file handle
|
||||
lea \2(pc),a0 ; string
|
||||
move.l a0,d2
|
||||
moveq #\3,d3 ; length
|
||||
jsr -48(a6)
|
||||
movem.l (sp)+,d0-d1/a0-a1
|
||||
endm
|
||||
else
|
||||
DEBUG_PRINT macro
|
||||
; No-op when DEBUG not defined
|
||||
endm
|
||||
endc
|
||||
|
||||
; Struct definition using RS directives
|
||||
STRUCTURE Player,0
|
||||
LONG px,py ; Position
|
||||
WORD hp ; Hit points
|
||||
BYTE alive ; 0 = dead, 1 = alive
|
||||
BYTE pad
|
||||
LONG sprite_ptr ; Pointer to sprite data
|
||||
sizeof Player_SIZE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comparison with Other Amiga Assemblers
|
||||
|
||||
### Feature Matrix
|
||||
|
||||
| Feature | vasm | Devpac 3 | PhxAss 4 | AsmOne | Barfly |
|
||||
|---|---|---|---|---|---|
|
||||
| **Free / Open Source** | ✓ | ✗ | ✓ | ✗ | ✗ |
|
||||
| **Cross-platform host** | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| **M68k up to 68060** | ✓ | 68030 | 68040+FPU | 68060 | 68060 |
|
||||
| **68080 (Apollo)** | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| **Automatic optimizations** | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| **Branch relaxation** | ✓ | ✗ | ✗ | ✗ | ✓ |
|
||||
| **Macro system** | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| **Multiple output formats** | 30+ | 1 (hunk) | 1 (hunk) | 1 (hunk) | 2 |
|
||||
| **Linker** | vlink (multi-format) | blink | blink | blink | blink |
|
||||
| **Linker scripts** | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| **Active development** | ✓ (2026) | ✗ (1994) | ✗ (1998) | ✗ (1996) | ✗ (2000) |
|
||||
| **IDE / debugger** | ✗ | ✓ (MonAm) | ✗ | ✓ (built-in) | ✗ |
|
||||
|
||||
### When to Choose Each
|
||||
|
||||
| Assembler | Best For |
|
||||
|---|---|
|
||||
| **vasm** | Cross-development, modern CI/CD, multi-platform projects, optimizing for size |
|
||||
| **Devpac** | Native Amiga development with MonAm debugger, legacy source compatibility |
|
||||
| **PhxAss** | Legacy source that uses `NEAR CODE`/`NEAR DATA` or PhxAss-specific macros |
|
||||
| **AsmOne** | Interactive debugging on native hardware, quick test cycles |
|
||||
| **Barfly** | Legacy projects already using Barfly-specific features |
|
||||
|
||||
---
|
||||
|
||||
## Integration with the Amiga Toolchain
|
||||
|
||||
vasm and vlink are the assembler/linker pair used by **vbcc** and **bebbo's GCC 6.x toolchain**. The typical workflow:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "Compiler Path (C)"
|
||||
C_SRC["main.c"] --> VBCC["vbcc / gcc"]
|
||||
VBCC --> C_OBJ["main.o (hunk)"]
|
||||
end
|
||||
|
||||
subgraph "Assembler Path (Asm)"
|
||||
ASM_SRC["code.s"] --> VASM["vasmm68k_mot"]
|
||||
VASM --> ASM_OBJ["code.o (hunk)"]
|
||||
end
|
||||
|
||||
C_OBJ --> VLINK["vlink"]
|
||||
ASM_OBJ --> VLINK
|
||||
VLINK --> EXE["Executable (hunk)"]
|
||||
|
||||
style VASM fill:#fff9c4,stroke:#f9a825
|
||||
style VLINK fill:#e8f4fd,stroke:#2196f3
|
||||
```
|
||||
|
||||
### Companion Tools
|
||||
|
||||
| Tool | Role | Integration |
|
||||
|---|---|---|
|
||||
| [vbcc](vbcc.md) | C compiler | Uses vasm as assembler, vlink as linker |
|
||||
| **bebbo's GCC** | C/C++ cross-compiler | Uses vasm/vlink (configurable) |
|
||||
| **IRA** | Disassembler → reassembler | Emits vasm-compatible Motorola syntax |
|
||||
| **Aira Force** | Reassembler pipeline | Uses vasm as reassembly engine |
|
||||
| [FD files](fd_files.md) | Library jump table generation | Used with `fd2pragma` → C headers |
|
||||
|
||||
---
|
||||
|
||||
## Decision Guide — When to Use vasm/vlink
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Starting an Amiga<br/>asm project?"] --> B{"Cross-compiling from<br/>modern OS?"}
|
||||
B -->|Yes| C["✅ vasm/vlink<br/>(only option)"]
|
||||
B -->|No| D{"Need optimizing<br/>assembler?"}
|
||||
D -->|Yes| E["✅ vasm/vlink"]
|
||||
D -->|No| F{"Need interactive<br/>debugger?"}
|
||||
F -->|Yes| G["AsmOne or Devpac<br/>(native only)"]
|
||||
F -->|No| H{"Legacy source<br/>compatibility?"}
|
||||
H -->|Devpac| I["vasm -devpac<br/>or Devpac 3"]
|
||||
H -->|PhxAss| J["vasm -phxass<br/>or PhxAss 4"]
|
||||
H -->|No| K["✅ vasm/vlink<br/>(recommended)"]
|
||||
|
||||
style C fill:#e8f5e9,stroke:#4caf50
|
||||
style E fill:#e8f5e9,stroke:#4caf50
|
||||
style K fill:#e8f5e9,stroke:#4caf50
|
||||
```
|
||||
|
||||
### When to Use vasm/vlink
|
||||
|
||||
1. **Cross-compiling** from Linux, macOS, or Windows — native assemblers won't run
|
||||
2. **New Amiga projects** — no legacy constraints, modern toolchain from day one
|
||||
3. **Integrating C and assembly** — vlink handles mixed-language linking cleanly
|
||||
4. **Automated builds / CI/CD** — command-line only, no GUI dependency
|
||||
5. **Targeting 68020+ features** — vasm has better 020/030/040/060 support than legacy assemblers
|
||||
6. **ROM / absolute code** — vlink linker scripts give precise memory layout control
|
||||
7. **Size optimization** — vasm's automatic optimization often produces smaller code than hand-tuned assembly
|
||||
|
||||
### When NOT to Use vasm/vlink
|
||||
|
||||
1. **Interactive debugging on native hardware** — AsmOne's built-in debugger (breakpoints, single-step, register dump) has no equivalent in the vasm/vlink workflow
|
||||
2. **Legacy source with undocumented Devpac quirks** — some old source relies on Devpac bugs or undocumented behavior that `-devpac` mode doesn't replicate
|
||||
3. **Macro-heavy PhxAss source** — PhxAss has a unique macro syntax parser; `-phxass` mode covers most but not all edge cases
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always specify `-Fhunk` explicitly** — don't rely on defaults; be explicit about output format
|
||||
2. **Use `-devpac` for legacy source** — avoids porting Devpac-specific directives by hand
|
||||
3. **Let the optimizer work** — don't disable optimizations unless debugging output differences
|
||||
4. **Use `xdef`/`xref` consistently** — vasm enforces symbol visibility; undeclared `xref` symbols generate warnings (use `-chklabels` to catch typos)
|
||||
5. **Separate code and data into sections** — use `SECTION code,CODE` and `SECTION data,DATA` for proper hunk separation
|
||||
6. **Use a linker script for complex layouts** — ROM images, overlay systems, and custom memory maps benefit from explicit script control
|
||||
7. **Build both debug and release configurations** — use `-DDEBUG` for conditional debug code (see Example 5)
|
||||
8. **Pin tool versions for reproducibility** — vasm/vlink are actively developed; use the same version in CI that you develop with
|
||||
|
||||
### Antipatterns
|
||||
|
||||
| Antipattern | Why It's Wrong | Correct Approach |
|
||||
|---|---|---|
|
||||
| **The Vanishing Reference** | Using `xref` but forgetting to actually provide the symbol in another object → linker error at final link | `xdef` the symbol in exactly one source file; verify with `-Map` output |
|
||||
| **The Mixed Syntax** | Writing half Devpac, half GNU-as syntax in the same file | Pick one syntax module (`mot` or `std`) at project start and stick with it |
|
||||
| **The Naked ORG** | Using `org` without a linker script → sections overlap silently | Use linker scripts (`-T`) to define memory regions for absolute code |
|
||||
| **The Optimization Gambler** | Disabling optimizations everywhere for "consistency" → larger, slower code | Only disable per-file when debugging output differences; use `-no-opt` surgically |
|
||||
| **The Sectionless Source** | Not declaring sections → all code goes into a default unnamed section → no control over hunk layout | Always use `SECTION name,type` at the top of each file |
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls
|
||||
|
||||
### 1. Case Sensitivity of Symbols
|
||||
|
||||
vasm symbols are case-sensitive by default. Devpac was case-insensitive. If you get "undefined symbol" errors when assembling old Devpac source:
|
||||
|
||||
```bash
|
||||
# Fix: enable case-insensitive mode
|
||||
vasmm68k_mot -Fhunk -nocase -o output.o input.s
|
||||
```
|
||||
|
||||
### 2. Section Attributes for Hunk Output
|
||||
|
||||
The hunk output module reads the **second argument** of the `SECTION` directive to determine the hunk type:
|
||||
|
||||
```asm
|
||||
SECTION code,CODE ; → HUNK_CODE ($3E9)
|
||||
SECTION data,DATA ; → HUNK_DATA ($3EA)
|
||||
SECTION bss,BSS ; → HUNK_BSS ($3EB) — zero-filled, no content emitted
|
||||
```
|
||||
|
||||
Using `SECTION text,CODE` (GNU convention) works but `SECTION mycode` (no type) produces a generic hunk that may confuse the Amiga loader.
|
||||
|
||||
### 3. Absolute vs PC-Relative References
|
||||
|
||||
vasm may convert absolute references to PC-relative silently. This is correct behavior but can surprise developers inspecting the binary:
|
||||
|
||||
```asm
|
||||
MOVE.L MyData, D0 ; May become MOVE.L MyData(PC), D0
|
||||
```
|
||||
|
||||
If you need an absolute reference (e.g., writing to custom chip registers at fixed addresses), it will remain absolute. Only relocatable symbols are eligible for conversion.
|
||||
|
||||
### 4. Library Resolution Order
|
||||
|
||||
vlink resolves libraries **left-to-right**. If `libA` depends on symbols from `libB`, `libB` must appear after `libA` on the command line:
|
||||
|
||||
```bash
|
||||
# WRONG: libA needs libB symbols, but libB is searched first
|
||||
vlink -bamigahunk -o app main.o -lB -lA
|
||||
|
||||
# CORRECT: libA searched first, then libB resolves its dependencies
|
||||
vlink -bamigahunk -o app main.o -lA -lB
|
||||
```
|
||||
|
||||
### 5. Kickstart 1.x Compatibility
|
||||
|
||||
AmigaOS 1.x (`kickstart 1.2`/`1.3`) has a simpler hunk loader. Use the `-kick1` flag to generate compatible executables:
|
||||
|
||||
```bash
|
||||
vlink -bamigahunk -kick1 -o app main.o -lamiga
|
||||
```
|
||||
|
||||
Without this flag, vlink may emit hunk structures (HUNK_RELRELOC32, HUNK_DEBUG, HUNK_BREAK) that Kickstart 1.x doesn't understand.
|
||||
|
||||
---
|
||||
|
||||
## Historical Context
|
||||
|
||||
### The Assembler Landscape (1985–1995)
|
||||
|
||||
In the Amiga's commercial era, developers chose between several closed-source assemblers:
|
||||
|
||||
| Assembler | Era | Developer | Fate |
|
||||
|---|---|---|---|
|
||||
| **AssemPro** | 1986–1990 | Softec | Abandoned |
|
||||
| **A68k** | 1987–1992 | Alpha Software | Abandoned |
|
||||
| **ArgAsm** | 1987–1991 | Argonaut Software | Abandoned |
|
||||
| **Devpac** | 1989–1994 | HiSoft | Abandoned |
|
||||
| **AsmOne** | 1991–1996 | Rune Gram-Madsen | Abandoned |
|
||||
| **Barfly** | 1993–2000 | K. J. Down | Abandoned |
|
||||
| **PhxAss** | 1993–1998 | Frank Wille | Became vasm foundation |
|
||||
|
||||
PhxAss, also by Frank Wille, was the direct ancestor of vasm. When vbcc (by Volker Barthelmann) needed a portable assembler that could run on any host, the PhxAss compiler code was refactored into the modular vasm architecture. The M68k + mot-syntax combination retains deep PhxAss/Devpac compatibility.
|
||||
|
||||
### Modern Relevance
|
||||
|
||||
vasm/vlink are now the de facto standard assembler/linker for:
|
||||
- The **Amiga demo scene** — size-constrained productions (4K/64K intros, demos)
|
||||
- **MiSTer FPGA** and Minimig development — cross-compiling from ARM/Linux
|
||||
- **AmigaOS 4** and **MorphOS** development — vasm targets PPC, vlink handles EHF and ELF
|
||||
- **Game preservation** — reassembling disassembled game code with IRA → vasm → vlink
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- vasm: http://sun.hasenbraten.de/vasm/
|
||||
- vlink: http://sun.hasenbraten.de/vlink/
|
||||
### Official Sources
|
||||
|
||||
- **vasm home**: http://sun.hasenbraten.de/vasm/
|
||||
- **vlink home**: http://sun.hasenbraten.de/vlink/
|
||||
- **vbcc home**: http://sun.hasenbraten.de/vbcc/ (vasm/vlink are part of this toolchain)
|
||||
- **vasm manual (PDF)**: http://sun.hasenbraten.de/vasm/release/vasm.pdf
|
||||
- **vlink manual (PDF)**: http://sun.hasenbraten.de/vlink/release/vlink.pdf
|
||||
|
||||
### GitHub Mirrors & Community Repos
|
||||
|
||||
| Repository | Description |
|
||||
|---|---|
|
||||
| [retro-vault/vasm](https://github.com/retro-vault/vasm) | Git mirror of vasm — updated regularly |
|
||||
| [retro-vault/vlink](https://github.com/retro-vault/vlink) | Git mirror of vlink — updated regularly |
|
||||
| [StarWolf3000/vasm-mirror](https://github.com/StarWolf3000/vasm-mirror) | Git mirror of vasm (formerly hosted at mbitsnbites) |
|
||||
| [dbuchwald/vasm](https://github.com/dbuchwald/vasm) | Git mirror with CI integration |
|
||||
| [ezrec/vasm](https://github.com/ezrec/vasm) | Mirror with additional CPU backends |
|
||||
| [kusma/amiga-dev](https://github.com/kusma/amiga-dev) | Pre-packaged Amiga dev environment (VBCC + vasm + vlink + PosixLib) |
|
||||
|
||||
### Related Articles in This Knowledge Base
|
||||
|
||||
- [vbcc](vbcc.md) — C compiler that uses vasm/vlink as its assembler/linker
|
||||
- [gcc_amiga.md](gcc_amiga.md) — bebbo's GCC toolchain (configurable to use vasm/vlink)
|
||||
- [FD files](fd_files.md) — FD/SFD file format; `fd2pragma` generates C headers used with vbcc+vasm
|
||||
- [Makefiles](makefiles.md) — Makefile patterns for vasm/vlink and mixed C+asm projects
|
||||
- [HUNK Format](../03_loader_and_exec_format/hunk_format.md) — The executable format vlink outputs and vasm can emit
|
||||
- [Debugging](debugging.md) — Tools that work with vasm/vlink output (Enforcer, SnoopDOS, FS-UAE GDB)
|
||||
- [Register Conventions](../04_linking_and_libraries/register_conventions.md) — ABI for C↔asm interop with vbcc
|
||||
|
||||
### Community Resources
|
||||
|
||||
- **English Amiga Board (EAB)**: Active forum with vasm/vlink troubleshooting threads
|
||||
- **Atari-Forum**: Cross-platform vasm/vlink discussions (many Amiga developers also target Atari)
|
||||
- **AmigaSource**: Curated link collection pointing to vasm/vlink resources
|
||||
- **Reaktor Crash Course**: Tutorial for Amiga assembly with vasm: https://www.reaktor.com/insights-and-events/crash-course-to-amiga-assembly-programming
|
||||
|
|
|
|||
703
13_toolchain/vbcc.md
Normal file
703
13_toolchain/vbcc.md
Normal file
|
|
@ -0,0 +1,703 @@
|
|||
[← Home](../README.md) · [Toolchain](README.md)
|
||||
|
||||
# VBCC — Volker Barthelmann's Portable C Compiler for AmigaOS
|
||||
|
||||
## Overview
|
||||
|
||||
**VBCC** is a portable, retargetable ISO C89 compiler created by **Volker Barthelmann** in the mid-1990s. It is the second most widely used cross-compiler for AmigaOS after GCC bebbo, and unlike GCC it was designed from the ground up as a lightweight, fast compiler with aggressively optimized code generation. The Amiga m68k backend (`vbccm68k`) is actively maintained and supports AmigaOS 3.x, AmigaOS 4, MorphOS, and AROS from a single toolchain.
|
||||
|
||||
The defining feature of VBCC on Amiga is the **`__reg()` storage class** — a compiler extension that lets C programmers explicitly place variables and function arguments in specific 68000 registers. This maps naturally onto the AmigaOS register-based library calling convention, making OS API calls intuitive without the macro gymnastics required by GCC inline assembly.
|
||||
|
||||
Key constraints:
|
||||
- **C89 only** — no C++, no GNU extensions (use `-c99` for limited C99 support)
|
||||
- **Cross-compiler** — runs on Linux, macOS, Windows; no native Amiga IDE
|
||||
- **vlink required** — VBCC does not use GNU `ld`; it links with `vlink` (also by the vbcc team)
|
||||
- **Config-driven** — target behavior is controlled by text config files in `vbcc/config/`
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Compiler Pipeline
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Frontend"
|
||||
SRC["Source (.c)"]
|
||||
VC["vc (driver)"]
|
||||
PP["Preprocessor"]
|
||||
FE["Parser / IR Generator"]
|
||||
end
|
||||
|
||||
subgraph "Backend"
|
||||
CG["vbccm68k\nCode Generator"]
|
||||
OPT["Peephole Optimizer"]
|
||||
end
|
||||
|
||||
subgraph "Linker"
|
||||
VL["vlink"]
|
||||
OBJ["Object (.o)"]
|
||||
EXE["Amiga HUNK"]
|
||||
end
|
||||
|
||||
SRC --> VC
|
||||
VC --> PP --> FE --> CG --> OPT --> OBJ
|
||||
OBJ --> VL --> EXE
|
||||
|
||||
style CG fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style VL fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
```
|
||||
|
||||
### The Config System
|
||||
|
||||
VBCC uses **text-based target configuration files** rather than hardcoded backend behavior. The driver `vc` reads `vbcc/config/` to determine which preprocessor, compiler, assembler, and linker to invoke:
|
||||
|
||||
```
|
||||
vbcc/
|
||||
bin/
|
||||
vc ; frontend driver
|
||||
vbccm68k ; m68k code generator
|
||||
config/
|
||||
m68k-amigaos ; AmigaOS 3.x target config
|
||||
m68k-amigaos4 ; AmigaOS 4 target
|
||||
ppc-amigaos ; PPC MorphOS / AmigaOS 4
|
||||
targets/
|
||||
m68k-amigaos/
|
||||
; startup code, runtime libraries
|
||||
```
|
||||
|
||||
The config file (`m68k-amigaos`) is a plain-text script that tells `vc`:
|
||||
- Which preprocessor flags to pass
|
||||
- Which code generator binary to run (`vbccm68k`)
|
||||
- Which assembler syntax to emit (Motorola)
|
||||
- Which linker (`vlink`) and which linker script
|
||||
- Default include paths and library paths
|
||||
|
||||
This design means adding a new Amiga-like target requires only a new config file and a backend — no recompilation of the compiler itself.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Linux / macOS Cross-Compiler Setup
|
||||
|
||||
```bash
|
||||
# 1. Download latest binaries from vbcc homepage:
|
||||
# http://sun.hasenbraten.de/vbcc/
|
||||
|
||||
# 2. Extract and set environment:
|
||||
export VBCC=/opt/vbcc
|
||||
export PATH=$VBCC/bin:$PATH
|
||||
|
||||
# 3. Untar the m68k-amigaos target archive into $VBCC/
|
||||
# This populates targets/m68k-amigaos/ with startup code and libs
|
||||
|
||||
# 4. Verify:
|
||||
vc +m68k-amigaos -v hello.c
|
||||
# Should compile and link to Amiga hunk format
|
||||
```
|
||||
|
||||
### Windows (MSYS2 / WSL)
|
||||
|
||||
The same binaries work under WSL. For native Windows, use the provided `vc.exe` and `vlink.exe` with MinGW or MSYS2 paths.
|
||||
|
||||
---
|
||||
|
||||
## The `__reg()` Storage Class
|
||||
|
||||
VBCC's most powerful Amiga-specific extension is `__reg()`, which places C variables in named CPU registers. This eliminates the need for inline assembly wrappers when calling AmigaOS libraries.
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
```c
|
||||
/* Place a variable in register D0 */
|
||||
int __reg("d0") result;
|
||||
|
||||
/* Place a pointer in register A0 */
|
||||
char * __reg("a0") buffer;
|
||||
|
||||
/* Function with register arguments — matches AmigaOS .fd conventions */
|
||||
BPTR __reg("d0") MyOpen(
|
||||
__reg("d1") CONST_STRPTR name,
|
||||
__reg("d2") LONG accessMode);
|
||||
```
|
||||
|
||||
### AmigaOS Library Call Example
|
||||
|
||||
```c
|
||||
#include <proto/dos.h>
|
||||
|
||||
/* Without __reg() — compiler uses stack, stub fixes it up */
|
||||
/* With __reg() — compiler generates exact register layout */
|
||||
|
||||
struct DosLibrary *DOSBase;
|
||||
|
||||
void writeHello(void)
|
||||
{
|
||||
BPTR fh = Open("CON:0/0/640/200/Hello", MODE_NEWFILE);
|
||||
if (fh)
|
||||
{
|
||||
char msg[] = "Hello from VBCC\n";
|
||||
Write(fh, msg, sizeof(msg) - 1);
|
||||
Close(fh);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `<proto/dos.h>` header for VBCC uses `__reg()` internally:
|
||||
|
||||
```c
|
||||
/* proto/dos.h (VBCC version) — simplified */
|
||||
#pragma amicall(DOSBase, 0x1E, Open(__reg("d1") CONST_STRPTR name,
|
||||
__reg("d2") LONG accessMode))
|
||||
```
|
||||
|
||||
This produces the same machine code as hand-written assembly:
|
||||
|
||||
```asm
|
||||
; VBCC-generated code for Open("foo", MODE_OLDFILE):
|
||||
MOVEA.L _DOSBase, A6
|
||||
LEA .str_foo, A0
|
||||
MOVE.L A0, D1 ; name → D1
|
||||
MOVEQ #1002, D2 ; MODE_OLDFILE → D2
|
||||
JSR -30(A6) ; Open() LVO = -30
|
||||
; D0 = file handle
|
||||
```
|
||||
|
||||
### Register Allocation Rules
|
||||
|
||||
| Register | `__reg()` String | Safe For |
|
||||
|---|---|---|
|
||||
| D0 | `"d0"` | Return values, scratch |
|
||||
| D1–D7 | `"d1"` … `"d7"` | Arguments, local variables |
|
||||
| A0 | `"a0"` | Arguments, scratch pointers |
|
||||
| A1–A3 | `"a1"` … `"a3"` | Arguments |
|
||||
| A4 | `"a4"` | Small-data base (see below) |
|
||||
| A5 | `"a5"` | Frame pointer (rarely used) |
|
||||
| A6 | `"a6"` | Library base (OS calls only) |
|
||||
| A7 | `"a7"` | Stack pointer — **never use** |
|
||||
|
||||
> [!WARNING]
|
||||
> `__reg("a6")` is reserved for library base pointers during OS calls. Using it for general variables will corrupt the next `JSR -LVO(A6)` and crash. `__reg("a7")` is the stack pointer — the compiler will reject this or generate broken code.
|
||||
|
||||
---
|
||||
|
||||
## Pragmas and FD File Conversion
|
||||
|
||||
### VBCC Pragma Format
|
||||
|
||||
VBCC pragmas are simpler than SAS/C's numeric encoding:
|
||||
|
||||
```c
|
||||
/* dos_pragmas.h for VBCC */
|
||||
#pragma amicall(DOSBase, 0x1E, Open(d1,d2))
|
||||
#pragma amicall(DOSBase, 0x24, Close(d1))
|
||||
#pragma amicall(DOSBase, 0x2A, Read(d1,d2,d3))
|
||||
```
|
||||
|
||||
- `0x1E` = LVO offset (hex) — the compiler computes the actual JSR offset
|
||||
- `Open(d1,d2)` = function name and argument registers (left to right)
|
||||
- The compiler generates `__reg()` assignments automatically
|
||||
|
||||
### Converting FD Files with `cvinclude.pl`
|
||||
|
||||
VBCC ships `cvinclude.pl` — a Perl script that converts Amiga `.fd` files into VBCC pragma headers:
|
||||
|
||||
```bash
|
||||
# Generate VBCC pragmas from an FD file:
|
||||
cvinclude.pl -v -a dos_lib.fd > dos_pragmas.h
|
||||
|
||||
# Generate inline-style prototypes:
|
||||
cvinclude.pl -v -i exec_lib.fd > exec_inline.h
|
||||
```
|
||||
|
||||
| Tool | Input | Output | Compiler |
|
||||
|---|---|---|---|
|
||||
| `fd2pragma` | `.fd` | SAS/C `#pragma libcall` | SAS/C |
|
||||
| `fd2inline` | `.fd` | GCC inline asm | GCC |
|
||||
| `cvinclude.pl` | `.fd` | VBCC `#pragma amicall` | VBCC |
|
||||
|
||||
---
|
||||
|
||||
## Compilation Workflow
|
||||
|
||||
### Command-Line Usage
|
||||
|
||||
```bash
|
||||
# Compile and link in one step:
|
||||
vc +m68k-amigaos -o myapp myapp.c
|
||||
|
||||
# Compile only (object file):
|
||||
vc +m68k-amigaos -c -o myapp.o myapp.c
|
||||
|
||||
# Link separately:
|
||||
vlink -bamigahunk -o myapp myapp.o -L/opt/vbcc/targets/m68k-amigaos/lib -lamiga
|
||||
```
|
||||
|
||||
### Common Flags
|
||||
|
||||
| Flag | Description |
|
||||
|---|---|
|
||||
| `+m68k-amigaos` | Select AmigaOS 3.x target config |
|
||||
| `+m68k-amigaos4` | Select AmigaOS 4 target |
|
||||
| `-O` | Enable optimizations |
|
||||
| `-speed` | Optimize for speed (aggressive inlining, loop unrolling) |
|
||||
| `-size` | Optimize for code size |
|
||||
| `-m68000` | Target plain 68000 (default) |
|
||||
| `-m68020` | Target 68020+ |
|
||||
| `-m68030` | Target 68030 |
|
||||
| `-m68040` | Target 68040 |
|
||||
| `-m68060` | Target 68060 |
|
||||
| `-fpu` | Enable FPU instructions (68881/68882/040/060) |
|
||||
| `-c99` | Enable C99 features (limited support) |
|
||||
| `-I<path>` | Add include path |
|
||||
| `-L<path>` | Add library path |
|
||||
| `-l<lib>` | Link with library |
|
||||
| `-g` | Include debug info (HUNK_DEBUG) |
|
||||
| `-s` | Strip symbols |
|
||||
| `-v` | Verbose output |
|
||||
| `-k` | Keep intermediate files |
|
||||
|
||||
### CPU Target Decision Matrix
|
||||
|
||||
| Target | Use When | Code Size | Notes |
|
||||
|---|---|---|---|
|
||||
| `-m68000` | A500, A1000, A2000, CDTV | Smallest | No 32-bit multiply/divide in hardware |
|
||||
| `-m68020` | A1200, A3000, A4000/030 | Medium | `MULS.L`, `DIVS.L`, `BFEXTU` available |
|
||||
| `-m68030` | A4000/030, accelerated systems | Medium | Adds `MMU` instructions (rarely used) |
|
||||
| `-m68040` | 040 accelerators | Larger | Hardware FPU; disable with `-m68040 -no-fpu` for LC/EC |
|
||||
| `-m68060` | 060 accelerators | Largest | Superscalar; some instructions emulated in software |
|
||||
|
||||
---
|
||||
|
||||
## Startup Code and Runtime
|
||||
|
||||
VBCC provides several startup modules for different application types:
|
||||
|
||||
| Startup Module | Use For | Notes |
|
||||
|---|---|---|
|
||||
| `minstart.o` | Minimal CLI programs | No C library; smallest possible binary |
|
||||
| `amiga.o` / `startup.o` | Standard CLI programs | Opens DOS, sets up stdin/stdout |
|
||||
| `wbstart.o` | Workbench programs | Handles WBStartup message, tooltypes |
|
||||
| `libinit.o` | Shared libraries | RTF_AUTOINIT compatible entry point |
|
||||
|
||||
The startup module is linked automatically by the target config unless you override it.
|
||||
|
||||
```bash
|
||||
# Link with custom startup (no default C runtime):
|
||||
vc +m68k-amigaos -nostdlib -o raw.bin main.c minstart.o
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## VBCC vs. GCC vs. SAS/C
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Compiler"
|
||||
V["VBCC"]
|
||||
G["GCC bebbo"]
|
||||
S["SAS/C 6.58"]
|
||||
end
|
||||
|
||||
subgraph "Code Style"
|
||||
VREG["__reg(d1) args"]
|
||||
GASM["inline asm stubs"]
|
||||
SPRA["#pragma libcall"]
|
||||
end
|
||||
|
||||
subgraph "Output"
|
||||
HUNK["Amiga HUNK"]
|
||||
end
|
||||
|
||||
V --> VREG --> HUNK
|
||||
G --> GASM --> HUNK
|
||||
S --> SPRA --> HUNK
|
||||
|
||||
style V fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style G fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
style S fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
```
|
||||
|
||||
| Feature | VBCC | GCC bebbo | SAS/C 6.58 | Clang/LLVM (conceptual) |
|
||||
|---|---|---|---|---|
|
||||
| **License** | Free (personal use) | GPL | Commercial (abandonware) | Apache 2.0 |
|
||||
| **Host OS** | Linux, macOS, Windows | Linux, macOS, Windows | AmigaOS only | Linux, macOS, Windows |
|
||||
| **C standard** | C89 (+ `-c99` partial) | C11 (GCC 6.5) | C89 | C11/C17/C23 |
|
||||
| **C++** | No | Yes | No | Yes (full C++20/23) |
|
||||
| **Register control** | `__reg()` | `__asm("d1")` inline | `#pragma libcall` | Not needed (no m68k backend) |
|
||||
| **Code quality** | Excellent, tight | Good, mature | Good (best for 1980s-era 68000) | Excellent (LLVM optimizer) |
|
||||
| **Optimizations** | Aggressive peephole | GCC -O3 | Global optimizer + peephole | Multi-pass SSA-based |
|
||||
| **Compile speed** | **Fast** | Slow | Medium | Medium-fast |
|
||||
| **Linker** | `vlink` | `vlink` or GNU `ld` | `blink` (integrated) | `lld` (or system linker) |
|
||||
| **Active dev** | Yes (2020s) | Yes (bebbo) | No (1990s) | Yes (very active) |
|
||||
| **AmigaOS 4** | Yes | Yes | No | No m68k backend |
|
||||
| **MorphOS** | Yes | Limited | No | No m68k backend |
|
||||
| **AROS** | Yes | Yes | No | No m68k backend |
|
||||
| **Debugging** | HUNK_DEBUG | HUNK_DEBUG + GDB remote | SAS stabs | DWARF (no Amiga output) |
|
||||
| **Architecture** | Single monolithic backend per target | Monolithic with frontend/middle/backend layers | Monolithic (native only) | Modular: frontend → IR → optimizer → backend |
|
||||
|
||||
### Architectural Similarities to Clang/LLVM
|
||||
|
||||
VBCC and Clang share a surprising number of design philosophies despite being separated by two decades and targeting opposite ends of the performance spectrum:
|
||||
|
||||
| Design Aspect | VBCC | Clang/LLVM | Why It's Similar |
|
||||
|---|---|---|---|
|
||||
| **Driver + backend split** | `vc` driver invokes `vbccm68k` | `clang` driver invokes LLVM backend | Both separate the user-facing interface from the code generator; the driver handles flags, preprocessing, and toolchain orchestration |
|
||||
| **Retargetable by design** | New target = new config file + backend binary | New target = new LLVM backend (TableGen `.td` files) | Both were architected from day one to support multiple architectures, unlike GCC which grew retargetability organically |
|
||||
| **Text-based target description** | `config/m68k-amigaos` plain-text scripts | LLVM TableGen `.td` files (declarative DSL) | Both externalize target knowledge into data rather than hardcoding it in the compiler source. VBCC's config is procedural; Clang's is declarative |
|
||||
| **Library-based architecture** | Backend is a separate binary (`vbccm68k`) | LLVM is a set of libraries (`libLLVMCore`, `libLLVMCodeGen`) | Both avoid monolithic compiler design. VBCC communicates via pipes/processes; LLVM communicates in-process via API calls |
|
||||
| **Focus on clean IR** | Simple three-address IR with virtual registers | LLVM IR (SSA-based, typed) | Both use an intermediate representation to decouple the frontend from code generation. VBCC's IR is much simpler (no SSA, no phi nodes) |
|
||||
| **Peephole optimization emphasis** | Primary optimization strategy | `llvm/lib/CodeGen/PeepholeOptimizer.cpp` | Both recognize that target-specific peephole patterns yield the biggest wins for simple ISAs like m68k |
|
||||
|
||||
### Where VBCC Diverges from Clang/LLVM
|
||||
|
||||
| Design Aspect | VBCC | Clang/LLVM | Practical Impact |
|
||||
|---|---|---|---|
|
||||
| **IR complexity** | Linear three-address code, no SSA | Full SSA with phi nodes, metadata, debug info | VBCC is much simpler to understand and debug; LLVM enables far more sophisticated optimizations (LTO, PGO, vectorization) |
|
||||
| **Optimization pipeline** | Single peephole pass | Multi-pass: SROA, GVN, LICM, inlining, vectorization, etc. | VBCC cannot match LLVM on complex C++ or compute-heavy workloads, but is perfectly adequate for the C89-level code typical of Amiga programs |
|
||||
| **Frontend language support** | C only (hand-written parser) | C, C++, ObjC, ObjC++, Swift, Rust, Julia, etc. | VBCC's parser is ~15,000 lines; Clang's is millions. The tradeoff is compile speed vs language coverage |
|
||||
| **ABI knowledge** | All ABI logic in the backend config | TargetInfo + ABIInfo in Clang, calling convention in LLVM backend | VBCC's approach is simpler and more transparent for AmigaOS's register-based convention; LLVM's is more general but harder to customize for exotic ABIs |
|
||||
| **Intermediate output** | Assembly text only (Motorola syntax) | LLVM IR (`.ll`), bitcode (`.bc`), assembly, object files | LLVM's multi-level IR enables link-time optimization and cross-module analysis; VBCC has no equivalent |
|
||||
| **Open source** | Source available on request, free for personal use | Fully open source (Apache 2.0) | LLVM can be forked, modified, and redistributed freely; VBCC is maintained by a single author with controlled distribution |
|
||||
|
||||
### When to Choose VBCC
|
||||
|
||||
- **Rapid iteration** — VBCC compiles significantly faster than GCC, making it ideal for edit-compile-test cycles
|
||||
- **Standards compliance** — stricter C89 checking than GCC; catches subtle portability bugs
|
||||
- **Clean assembly output** — easier to read and audit than GCC's heavily macro-expanded output
|
||||
- **Modern Amiga targets** — best support for AmigaOS 4, MorphOS, and AROS from one toolchain
|
||||
- **Smaller binaries** — with `-size`, VBCC often beats GCC on code density
|
||||
|
||||
### When to Choose GCC Instead
|
||||
|
||||
- **C++ required** — VBCC does not support C++ at all
|
||||
- **GNU extensions** — code using `__attribute__`, nested functions, or other GCCisms
|
||||
- **Existing GCC codebase** — projects already using bebbo toolchain or libnix
|
||||
- **GDB debugging** — GCC's GDB remote debugging is more mature than VBCC's limited debug output
|
||||
|
||||
---
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Minimal VBCC Program (CLI)
|
||||
|
||||
```c
|
||||
/* hello.c */
|
||||
#include <proto/dos.h>
|
||||
#include <proto/exec.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Printf("Hello, AmigaOS from VBCC!\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
vc +m68k-amigaos -O -o hello hello.c
|
||||
```
|
||||
|
||||
### Workbench Program with Tooltypes
|
||||
|
||||
```c
|
||||
/* wbhello.c */
|
||||
#include <proto/dos.h>
|
||||
#include <proto/exec.h>
|
||||
#include <workbench/startup.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct WBStartup *wbmsg = NULL;
|
||||
|
||||
if (argc == 0)
|
||||
{
|
||||
/* Launched from Workbench */
|
||||
wbmsg = (struct WBStartup *)argv;
|
||||
/* Read tooltypes from wbmsg->sm_ArgList->wa_ToolTypes */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Launched from CLI */
|
||||
Printf("Args: %ld\n", argc);
|
||||
}
|
||||
|
||||
Printf("VBCC Workbench program running\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
vc +m68k-amigaos -O -o wbhello wbhello.c
|
||||
```
|
||||
|
||||
### Custom Register Function (Hand-Optimized Blitter Call)
|
||||
|
||||
```c
|
||||
/* blitfast.c — hand-optimized blitter call using __reg() */
|
||||
#include <hardware/custom.h>
|
||||
#include <hardware/blit.h>
|
||||
|
||||
extern volatile struct Custom custom;
|
||||
|
||||
/* Match the hardware register layout exactly */
|
||||
void __reg("d0") BlitWait(void)
|
||||
{
|
||||
/* Wait for blitter DMA to finish */
|
||||
while (custom.dmaconr & (1 << 14)) /* BBUSY bit */
|
||||
;
|
||||
}
|
||||
|
||||
void __reg("a0") SetupBlit(
|
||||
__reg("d0") UWORD bltcon0,
|
||||
__reg("d1") UWORD bltcon1,
|
||||
__reg("d2") APTR srcA,
|
||||
__reg("d3") APTR srcB,
|
||||
__reg("d4") APTR dst)
|
||||
{
|
||||
BlitWait();
|
||||
custom.bltcon0 = bltcon0;
|
||||
custom.bltcon1 = bltcon1;
|
||||
custom.bltapt = srcA;
|
||||
custom.bltbpt = srcB;
|
||||
custom.bltdpt = dst;
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
vc +m68k-amigaos -O -speed -o blitfast blitfast.c
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> `__reg()` on function parameters is powerful but dangerous. If you mismatch the register with the actual hardware or OS expectation, you get silent corruption. Always verify against the `.fd` file or hardware register documentation.
|
||||
|
||||
---
|
||||
|
||||
## Best Practices & Antipatterns
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. Always use `+m68k-amigaos` explicitly — do not rely on a default config
|
||||
2. Use `-O` or `-speed` for release builds; `-size` for tight memory constraints
|
||||
3. Prefer `__reg()` in performance-critical paths; let the compiler choose registers for general code
|
||||
4. Verify register assignments against NDK `.fd` files before hand-rolling OS calls
|
||||
5. Use `vlink` for linking — do not mix GNU `ld` with VBCC object files
|
||||
6. Set `VBCC` environment variable correctly — the driver searches paths relative to it
|
||||
7. Use `-g` for debug builds; VBCC emits HUNK_DEBUG compatible with most Amiga debuggers
|
||||
8. Test on real hardware or accurate emulator — VBCC's optimizer may reorder memory accesses in ways that behave differently on 68000 vs 68060
|
||||
9. For AmigaOS 4 or MorphOS, use the dedicated target configs (`+m68k-amigaos4`, `+ppc-morphos`)
|
||||
10. Keep a local copy of generated pragma headers — `cvinclude.pl` is a build dependency
|
||||
|
||||
### Named Antipatterns
|
||||
|
||||
#### "The Register Mismatch" — Wrong `__reg()` for OS Calls
|
||||
|
||||
```c
|
||||
/* BAD: D0 and D1 swapped — Open() expects name in D1, mode in D2 */
|
||||
BPTR __reg("d0") Open(__reg("d0") CONST_STRPTR name,
|
||||
__reg("d1") LONG mode);
|
||||
|
||||
/* The JSR -30(A6) will place name in D0 (wrong) and mode in D1 (wrong).
|
||||
Crash or silent corruption guaranteed. */
|
||||
```
|
||||
|
||||
```c
|
||||
/* CORRECT: Match the .fd specification exactly */
|
||||
/* dos_lib.fd: Open(name,accessMode)(d1,d2) */
|
||||
#pragma amicall(DOSBase, 0x1E, Open(d1,d2))
|
||||
```
|
||||
|
||||
#### "The GCC Refugee" — Using `__asm()` Inline for OS Calls
|
||||
|
||||
```c
|
||||
/* BAD: Porting GCC inline asm to VBCC — unnecessary and fragile */
|
||||
static __inline APTR AllocMem(ULONG size, ULONG flags)
|
||||
{
|
||||
register APTR _r __asm("d0"); /* GCC syntax — not VBCC! */
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
```c
|
||||
/* CORRECT: Use VBCC's __reg() or proto headers */
|
||||
#include <proto/exec.h>
|
||||
APTR mem = AllocMem(1024, MEMF_CLEAR);
|
||||
```
|
||||
|
||||
#### "The Missing vlink" — Linking with GNU ld
|
||||
|
||||
```bash
|
||||
# BAD: GNU ld does not understand VBCC's HUNK output conventions
|
||||
m68k-amigaos-ld -o myapp myapp.o
|
||||
# Produces broken executable or link errors
|
||||
|
||||
# CORRECT: Use vlink
|
||||
vlink -bamigahunk -o myapp myapp.o -lamiga
|
||||
```
|
||||
|
||||
#### "The A6 Squatter" — Using `__reg("a6")` for Variables
|
||||
|
||||
```c
|
||||
/* BAD: A6 is sacred — it must always point to the library base during OS calls */
|
||||
struct MyData * __reg("a6") myptr;
|
||||
/* ... later ... */
|
||||
JSR -198(A6); /* Calls AllocMem with garbage in A6! Crash. */
|
||||
```
|
||||
|
||||
```c
|
||||
/* CORRECT: Use A2–A5 for persistent pointers */
|
||||
struct MyData * __reg("a2") myptr;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls & Common Mistakes
|
||||
|
||||
### 1. Forgetting the Target Config
|
||||
|
||||
**Symptom:** `vc` compiles but emits x86 code, or fails with "unknown target".
|
||||
|
||||
**Cause:** The `vc` driver requires `+target` to select a backend. Without it, `vc` may default to the host architecture or error out.
|
||||
|
||||
**Fix:** Always specify the target:
|
||||
```bash
|
||||
vc +m68k-amigaos -o app app.c # Correct
|
||||
vc -o app app.c # Wrong — may default to host
|
||||
```
|
||||
|
||||
### 2. C99 Code in C89 Mode
|
||||
|
||||
**Symptom:** Errors like `// comments not allowed` or `variable declaration after statement`.
|
||||
|
||||
**Cause:** VBCC defaults to strict C89. `//` comments, mixed declarations, and `inline` keyword are not accepted.
|
||||
|
||||
**Fix:** Use `-c99` for partial C99 support, or rewrite to C89:
|
||||
```c
|
||||
/* C89 compatible */
|
||||
int i;
|
||||
for (i = 0; i < 10; i++) { }
|
||||
|
||||
/* Use /* */ comments exclusively unless -c99 is set */
|
||||
```
|
||||
|
||||
### 3. Missing `VBCC` Environment Variable
|
||||
|
||||
**Symptom:** `vc` cannot find config files, startup code, or standard headers.
|
||||
|
||||
**Cause:** `vc` resolves config paths relative to the `VBCC` environment variable. If unset, it searches relative to the binary location, which fails on many installations.
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
export VBCC=/opt/vbcc
|
||||
export PATH=$VBCC/bin:$PATH
|
||||
```
|
||||
|
||||
### 4. Optimizer Reordering Hardware Register Accesses
|
||||
|
||||
**Symptom:** Blitter or CIA register writes happen in the wrong order, or hardware state reads return stale values.
|
||||
|
||||
**Cause:** VBCC's optimizer does not know that `volatile struct Custom` memory-mapped I/O has side effects. It may reorder or eliminate reads.
|
||||
|
||||
**Fix:** Always declare hardware pointers as `volatile`:
|
||||
```c
|
||||
extern volatile struct Custom custom; /* hardware/custom.h does this */
|
||||
```
|
||||
|
||||
For custom hardware structs not in the NDK, add `volatile` manually:
|
||||
```c
|
||||
volatile UWORD * const myreg = (volatile UWORD *)0xDFF100;
|
||||
*myreg = 0xFF; /* guaranteed emit, not optimized away */
|
||||
```
|
||||
|
||||
### 5. Mixing VBCC and GCC Object Files
|
||||
|
||||
**Symptom:** Link errors, undefined references, or runtime crashes.
|
||||
|
||||
**Cause:** VBCC and GCC use different calling conventions for their own internal runtime functions (division helpers, stack check prologues). Object files are ABI-incompatible at the runtime-library level.
|
||||
|
||||
**Fix:** Compile the entire project with one compiler. If mixing assembly (vasm) with C, use VBCC for C and vasm for asm, then link with `vlink`.
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Software Built with VBCC
|
||||
|
||||
VBCC is the compiler of choice for many modern Amiga projects because it is actively maintained and produces reliable code:
|
||||
|
||||
| Project | Target | Notes |
|
||||
|---|---|---|
|
||||
| **ScummVM** (Amiga port) | AmigaOS 3 / 4 | Large C codebase; VBCC handles the strict C requirements well |
|
||||
| **DoomAttack** | AmigaOS 3 | 68000-optimized Doom port |
|
||||
| **Various SDL games** | AmigaOS 3 / MorphOS | SDL-Amiga wrappers compiled with VBCC |
|
||||
| **MUI 5 applications** | AmigaOS 3+ | MUI custom classes in C |
|
||||
| **AROS core** | AROS | VBCC is one of the supported compilers for AROS build |
|
||||
| **AmigaOS 4 system software** | AmigaOS 4 | Hyperion's SDK supports VBCC |
|
||||
|
||||
### Typical Project Types
|
||||
|
||||
- **Retro game development** — cross-compile on Linux, test in FS-UAE
|
||||
- **Utility/tools** — small CLI programs where compile speed matters
|
||||
- **Shared libraries** — `libinit.o` startup makes library authoring straightforward
|
||||
- **MorphOS/AROS ports** — single codebase compiles to multiple Amiga-like OSes
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Does VBCC support C++?**
|
||||
A: No. VBCC is a C compiler only. For C++ on Amiga, use GCC bebbo or StormC.
|
||||
|
||||
**Q: Can I use VBCC as a native Amiga compiler?**
|
||||
A: The VBCC compiler itself does not run on AmigaOS — it is a cross-compiler. However, there have been ports of earlier versions. Modern development uses Linux/macOS/Windows hosts.
|
||||
|
||||
**Q: Why does my code compile with GCC but fail with VBCC?**
|
||||
A: VBCC is stricter about C89 compliance. Common issues: `//` comments, variable declarations after executable statements, missing function prototypes, and GNU `__attribute__` extensions.
|
||||
|
||||
**Q: How do I debug VBCC-compiled programs?**
|
||||
A: Use `-g` to emit HUNK_DEBUG information. Load the executable into an Amiga debugger (like the one in UAE/FS-UAE with symbol support) or use `kprintf()` tracing.
|
||||
|
||||
**Q: Can I mix VBCC-compiled C with vasm assembly?**
|
||||
A: Yes — this is the recommended combination. Compile C with `vc`, assemble with `vasmm68k_mot`, link with `vlink`. All three tools share compatible object formats.
|
||||
|
||||
**Q: What is the difference between `-speed` and `-O`?**
|
||||
A: `-O` enables standard optimizations. `-speed` enables aggressive optimizations that may increase code size (loop unrolling, function inlining). Use `-size` for the opposite tradeoff.
|
||||
|
||||
**Q: Does VBCC support 68060-specific optimizations?**
|
||||
A: Yes with `-m68060`. The backend avoids unimplemented instructions and uses the dual-pipeline effectively. However, it does not perform instruction scheduling to exploit superscalar execution — hand-tuning assembly may still outperform the compiler on tight inner loops.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
### VBCC & Related Tools (Volker Barthelmann)
|
||||
|
||||
- VBCC homepage: http://sun.hasenbraten.de/vbcc/
|
||||
- VBCC manual (PDF): http://www.ibaug.de/vbcc/doc/vbcc.pdf
|
||||
- vlink linker: http://sun.hasenbraten.de/vlink/
|
||||
- vasm assembler: http://sun.hasenbraten.de/vasm/
|
||||
- Aminet `dev/c/vbcc` — target archives and updates
|
||||
|
||||
### Other Compilers & Tools Mentioned
|
||||
|
||||
- **GCC bebbo**: [gcc_amiga.md](gcc_amiga.md) · https://franke.ms/git/bebbo/amiga-gcc (mirror: https://github.com/AmigaPorts/m68k-amigaos-gcc)
|
||||
- **SAS/C**: [sasc.md](sasc.md) — SAS/C 6.x native compiler
|
||||
- **StormC**: [stormc.md](stormc.md) — StormC 4 IDE and compiler
|
||||
- **Clang/LLVM**: https://llvm.org/ · https://clang.llvm.org/
|
||||
- **GNU Binutils (ld)**: https://www.gnu.org/software/binutils/
|
||||
- **lld**: https://lld.llvm.org/ — LLVM linker
|
||||
- **GDB**: https://www.sourceware.org/gdb/ — GNU debugger
|
||||
- **libnix**: Aminet `dev/c/libnix` — lightweight Amiga C runtime library
|
||||
|
||||
### FD Toolchain
|
||||
|
||||
- `fd2pragma` / `fd2inline`: shipped with NDK 3.9 (`NDK_3.9/Tools/`)
|
||||
- `cvinclude.pl`: bundled with VBCC in `vbcc/bin/`
|
||||
|
||||
### Related Knowledge Base Articles
|
||||
|
||||
- [vasm_vlink.md](vasm_vlink.md) — vasm assembler and vlink linker
|
||||
- [register_conventions.md](../04_linking_and_libraries/register_conventions.md) — AmigaOS register calling conventions
|
||||
- [inline_stubs.md](../04_linking_and_libraries/inline_stubs.md) — `__reg()` and pragma mechanisms compared across compilers
|
||||
- [compiler_stubs.md](../04_linking_and_libraries/compiler_stubs.md) — compiler-specific library call generation
|
||||
- [compiler_fingerprints.md](../05_reversing/compiler_fingerprints.md) — recognizing VBCC-generated code in disassembly
|
||||
|
|
@ -95,7 +95,7 @@ All Amiga custom chip registers are memory-mapped at base address `$DFF000`. Thi
|
|||
| `$110`–`$11E` | `BPL1DAT`–`BPL8DAT` | W | Bitplane data |
|
||||
| `$120`–`$13E` | `SPR0PTH`–`SPR7PTL` | W | Sprite pointers 0–7 |
|
||||
| `$140`–`$17E` | `SPR0POS`–`SPR7DATB` | W | Sprite position/control/data |
|
||||
| `$180`–`$1BE` | `COLOR00`–`COLOR31` | W | Colour palette registers |
|
||||
| `$180`–`$1BE` | `COLOR00`–`COLOR31` | W | Color palette registers |
|
||||
| `$1C0` | `HTOTAL` | W | H total (ECS) |
|
||||
| `$1C2` | `HSSTOP` | W | H sync stop (ECS) |
|
||||
| `$1C4` | `HBSTRT` | W | H blank start (ECS) |
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ C:LoadModule LIBS:68060.library
|
|||
|
||||
## struct (ROM Tag)
|
||||
|
||||
Both libraries register as `RTF_COLDSTART` resident modules with high priority to ensure they are initialised before any user code runs:
|
||||
Both libraries register as `RTF_COLDSTART` resident modules with high priority to ensure they are initialized before any user code runs:
|
||||
|
||||
```c
|
||||
/* Typical RomTag for 68040.library: */
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ static const APTR funcTable[] = {
|
|||
LONG DevOpen(struct IORequest *ioreq, ULONG unit, ULONG flags,
|
||||
struct MyDevBase *base)
|
||||
{
|
||||
/* Initialise per-unit state if needed */
|
||||
/* Initialize per-unit state if needed */
|
||||
struct MyUnit *u = &base->md_Units[unit];
|
||||
|
||||
ioreq->io_Device = (struct Device *)base;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
**RTG** (Retargetable Graphics) is the framework that allows AmigaOS to use external graphics cards instead of, or alongside, the native custom chipset (OCS/ECS/AGA). RTG was essential for the Amiga's evolution beyond the 15 kHz PAL/NTSC display limitations, enabling VGA-class resolutions (up to 1600×1200) and true-colour (16/24/32-bit) output.
|
||||
**RTG** (Retargetable Graphics) is the framework that allows AmigaOS to use external graphics cards instead of, or alongside, the native custom chipset (OCS/ECS/AGA). RTG was essential for the Amiga's evolution beyond the 15 kHz PAL/NTSC display limitations, enabling VGA-class resolutions (up to 1600×1200) and true-color (16/24/32-bit) output.
|
||||
|
||||
The two major RTG systems are:
|
||||
- **Picasso96 (P96)** — the de facto standard, now maintained by Individual Computers
|
||||
|
|
@ -21,7 +21,7 @@ graph LR
|
|||
subgraph "Native Chipset (OCS/ECS/AGA)"
|
||||
direction TB
|
||||
NC_BW["Max: 1280×512 (AGA)"]
|
||||
NC_CD["Max: 256 colours (8-bit)"]
|
||||
NC_CD["Max: 256 colors (8-bit)"]
|
||||
NC_FM["Planar framebuffer"]
|
||||
NC_FR["15 kHz / 31 kHz PAL/NTSC"]
|
||||
end
|
||||
|
|
@ -29,7 +29,7 @@ graph LR
|
|||
subgraph "RTG Graphics Card"
|
||||
direction TB
|
||||
RTG_BW["Up to: 1600×1200"]
|
||||
RTG_CD["16/24/32-bit True Colour"]
|
||||
RTG_CD["16/24/32-bit True Color"]
|
||||
RTG_FM["Chunky (linear) framebuffer"]
|
||||
RTG_FR["31–100+ kHz VGA"]
|
||||
end
|
||||
|
|
@ -42,21 +42,21 @@ graph LR
|
|||
|
||||
### Planar vs Chunky — The Fundamental Difference
|
||||
|
||||
The native Amiga chipset uses **planar** pixel storage — colour bits are spread across separate bitplanes. RTG cards use **chunky** (linear, packed) pixel storage:
|
||||
The native Amiga chipset uses **planar** pixel storage — color bits are spread across separate bitplanes. RTG cards use **chunky** (linear, packed) pixel storage:
|
||||
|
||||
```
|
||||
PLANAR (Native Amiga — 4 bitplanes = 16 colours):
|
||||
Bitplane 0: 10110010... ← bit 0 of each pixel's colour
|
||||
PLANAR (Native Amiga — 4 bitplanes = 16 colors):
|
||||
Bitplane 0: 10110010... ← bit 0 of each pixel's color
|
||||
Bitplane 1: 01100101... ← bit 1
|
||||
Bitplane 2: 11010001... ← bit 2
|
||||
Bitplane 3: 00101100... ← bit 3
|
||||
|
||||
To read pixel 0's colour: read bit 0 from each plane → combine
|
||||
To read pixel 0's color: read bit 0 from each plane → combine
|
||||
|
||||
CHUNKY (RTG Card — 8-bit indexed):
|
||||
Pixel 0: $0D Pixel 1: $05 Pixel 2: $1B Pixel 3: $0A ...
|
||||
|
||||
Each pixel's colour stored contiguously — one byte per pixel (8-bit)
|
||||
Each pixel's color stored contiguously — one byte per pixel (8-bit)
|
||||
Or: two bytes per pixel (16-bit), three (24-bit), four (32-bit)
|
||||
```
|
||||
|
||||
|
|
@ -116,15 +116,15 @@ flowchart LR
|
|||
|---|---|---|---|
|
||||
| `graphics.library` | `BltBitMap` (−30) | Programs the Blitter DMA to copy rectangular regions between planar bitmaps in Chip RAM. Uses hardware minterm logic for raster ops (AND/OR/XOR). | Checks source/dest bitmap memory addresses. If either is in card VRAM, calls `BoardInfo->BlitRect()` which programs the card's 2D engine to do a VRAM-to-VRAM rectangle copy. If both are Chip RAM, falls through to original Blitter path. Mixed (Chip↔VRAM) requires CPU-mediated copy across the bus. |
|
||||
| `graphics.library` | `BltBitMapRastPort` (−606) | Blits a bitmap into a RastPort (with clipping). Uses native Blitter with layer clip rectangles. | Same VRAM detection. For RTG RastPorts, iterates the ClipRect list and calls `BlitRect` for each visible clip rectangle individually, handling layer obscuring. |
|
||||
| `graphics.library` | `RectFill` (−306) | Programs native Blitter to fill a rectangular region with the RastPort's foreground pen using planar fill mode. | Calls `BoardInfo->FillRect()` which sends a solid-fill command to the card's 2D engine with the pen colour converted to the screen's `RGBFTYPE`. Single register write + blit trigger — extremely fast. |
|
||||
| `graphics.library` | `RectFill` (−306) | Programs native Blitter to fill a rectangular region with the RastPort's foreground pen using planar fill mode. | Calls `BoardInfo->FillRect()` which sends a solid-fill command to the card's 2D engine with the pen color converted to the screen's `RGBFTYPE`. Single register write + blit trigger — extremely fast. |
|
||||
| `graphics.library` | `Move`/`Draw` (−240/−246) | Uses the Blitter's line-draw mode (BLTCON1 bit 0) to draw a one-pixel-wide line between two points in planar memory. | Calls `BoardInfo->DrawLine()` which programs the card's hardware Bresenham line engine. If DrawLine is NULL, falls back to CPU-computed pixel-by-pixel `WritePixel` into VRAM. |
|
||||
| `graphics.library` | `WritePixel` (−324) | Computes bitplane offsets, sets/clears the appropriate bit in each plane for the pen colour. | Computes byte offset in chunky VRAM: `offset = y * BytesPerRow + x * BPP`. Writes the pen colour directly as 1/2/3/4 bytes depending on depth. No hardware assist needed — just a bus write. |
|
||||
| `graphics.library` | `ReadPixel` (−318) | Reads the corresponding bit from each bitplane, assembles the colour index. | Reads 1/2/3/4 bytes from VRAM at the pixel offset. **Very slow on Zorro II** — each read stalls the CPU for a full bus cycle (~1 µs). Avoid in loops. |
|
||||
| `graphics.library` | `WritePixel` (−324) | Computes bitplane offsets, sets/clears the appropriate bit in each plane for the pen color. | Computes byte offset in chunky VRAM: `offset = y * BytesPerRow + x * BPP`. Writes the pen color directly as 1/2/3/4 bytes depending on depth. No hardware assist needed — just a bus write. |
|
||||
| `graphics.library` | `ReadPixel` (−318) | Reads the corresponding bit from each bitplane, assembles the color index. | Reads 1/2/3/4 bytes from VRAM at the pixel offset. **Very slow on Zorro II** — each read stalls the CPU for a full bus cycle (~1 µs). Avoid in loops. |
|
||||
| `graphics.library` | `ScrollRaster` (−396) | Uses native Blitter to shift a rectangular region, then clears the exposed strip. | Calls `BlitRect` to shift the rectangle within VRAM, then `FillRect` to clear the newly exposed area. Alternatively, if the entire screen scrolls, uses `SetPanning()` to simply change the display start address — zero-copy scroll. |
|
||||
| `graphics.library` | `AllocBitMap` (−918) | Allocates a planar bitmap in Chip RAM (`MEMF_CHIP`). Creates N bitplane pointers, one per plane. | If the bitmap is for an RTG screen (friend bitmap is RTG), allocates a contiguous chunky buffer from card VRAM instead. Sets a private flag so all subsequent drawing ops route to the card. The bitmap struct's Planes[0] points to VRAM; Planes[1..7] are NULL (chunky = one plane). |
|
||||
| `graphics.library` | `FreeBitMap` (−924) | Frees planar bitplane memory back to Chip RAM pool. | If bitmap is in VRAM, frees it from the card's VRAM allocator (managed by `rtg.library`). Clears the RTG flag. |
|
||||
| `graphics.library` | `SetAPen`/`SetBPen` | Stores pen index in RastPort for subsequent draw calls. | Same — pen storage is RastPort-local. But for hi/true-colour RTG screens, P96 must also resolve the pen to an RGB value via the screen's colour table when the actual draw call happens. |
|
||||
| `graphics.library` | `LoadRGB32` (−882) | Programs the native colour palette registers ($DFF180–$DFF1BE for OCS, or AGA bank registers). | Calls `BoardInfo->SetColorArray()` which programs the card's RAMDAC palette registers. For 8-bit CLUT screens, this updates the hardware palette. For 16/24/32-bit screens, this updates a software lookup table only. |
|
||||
| `graphics.library` | `SetAPen`/`SetBPen` | Stores pen index in RastPort for subsequent draw calls. | Same — pen storage is RastPort-local. But for hi/true-color RTG screens, P96 must also resolve the pen to an RGB value via the screen's color table when the actual draw call happens. |
|
||||
| `graphics.library` | `LoadRGB32` (−882) | Programs the native color palette registers ($DFF180–$DFF1BE for OCS, or AGA bank registers). | Calls `BoardInfo->SetColorArray()` which programs the card's RAMDAC palette registers. For 8-bit CLUT screens, this updates the hardware palette. For 16/24/32-bit screens, this updates a software lookup table only. |
|
||||
| `intuition.library` | `OpenScreen` (−198) | Creates a ViewPort, allocates Chip RAM bitplanes, builds a Copper list for the display, and programs Denise/Lisa registers. | Intercepts the mode ID. If it's an RTG mode: allocates VRAM for the framebuffer, calls `SetGC()` to program the card's CRTC timing registers, calls `SetDAC()` for pixel format, and calls `SetSwitch(TRUE)` to route the monitor to the card's output. No Copper list is built. |
|
||||
| `intuition.library` | `CloseScreen` (−66) | Tears down ViewPort, frees Copper lists and Chip RAM bitplanes. | Frees VRAM allocations. If this was the last RTG screen, calls `SetSwitch(FALSE)` to return the monitor to native chipset output. |
|
||||
| `intuition.library` | `ScreenToFront` (−252) | Reorders the screen list and rebuilds the Copper list to display this screen on top. | If the new front screen is on different hardware than the current display (e.g., switching from native to RTG), calls `SetSwitch()` to toggle the monitor. If same hardware, just reorders the screen depth. |
|
||||
|
|
@ -319,7 +319,7 @@ RTG cards with a 2D engine can offload these operations from the CPU:
|
|||
|
||||
| Operation | Function | Description | Speedup |
|
||||
|---|---|---|---|
|
||||
| **Rect Fill** | `FillRect()` | Fill rectangle with solid colour | 10–50× |
|
||||
| **Rect Fill** | `FillRect()` | Fill rectangle with solid color | 10–50× |
|
||||
| **Rect Blit** | `BlitRect()` | Copy rectangle (VRAM→VRAM) | 5–20× |
|
||||
| **Screen Scroll** | `BlitRect()` + `SetPanning()` | Scroll display contents | 5–20× |
|
||||
| **Pattern Fill** | `BlitTemplate()` | Fill with pattern/text glyph | 5–15× |
|
||||
|
|
@ -376,7 +376,7 @@ Planar Data (4 planes): Chunky Output (8bpp):
|
|||
This is **extremely CPU-intensive**. The CyberVision 64 included a dedicated **Roxxler** chip for hardware C2P acceleration. Without hardware help, C2P runs at ~2–5 FPS for fullscreen games — which is why RTG was primarily for productivity, not gaming.
|
||||
|
||||
> [!WARNING]
|
||||
> Old games that bang hardware registers directly (writing to `$DFF1xx` colour registers, custom chip DMA) will **never** work on RTG. They must be run on the native chipset display.
|
||||
> Old games that bang hardware registers directly (writing to `$DFF1xx` color registers, custom chip DMA) will **never** work on RTG. They must be run on the native chipset display.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -522,7 +522,7 @@ The MiSTer Amiga core implements RTG as a virtual graphics card:
|
|||
| **Display output** | FPGA scaler renders RTG framebuffer to HDMI |
|
||||
| **Mode switching** | Software-controlled: OSD or automatic (screen drag) |
|
||||
| **Resolution** | Up to 1920×1080 (limited by scaler) |
|
||||
| **Colour depth** | 8/16/32-bit |
|
||||
| **Color depth** | 8/16/32-bit |
|
||||
|
||||
### Display Pipeline
|
||||
|
||||
|
|
@ -568,7 +568,7 @@ C:AddMonitor DEVS:Monitors/PicassoIV
|
|||
|
||||
## RGB Pixel Formats
|
||||
|
||||
RTG cards support multiple chunky pixel formats. The `RGBFTYPE` enum defines how colour channels are packed:
|
||||
RTG cards support multiple chunky pixel formats. The `RGBFTYPE` enum defines how color channels are packed:
|
||||
|
||||
| Format | Constant | BPP | Layout | Byte Order |
|
||||
|---|---|---|---|---|
|
||||
|
|
@ -604,7 +604,7 @@ RTG cards support multiple chunky pixel formats. The `RGBFTYPE` enum defines how
|
|||
### Picasso96API.library
|
||||
|
||||
```c
|
||||
/* Open a 16-bit true-colour screen: */
|
||||
/* Open a 16-bit true-color screen: */
|
||||
#include <libraries/Picasso96.h>
|
||||
|
||||
struct Screen *scr = p96OpenScreenTags(
|
||||
|
|
@ -778,7 +778,7 @@ When the user drags a screen to reveal the one behind it:
|
|||
| PCI (Mediator) | 133 MB/s | ~80 MB/s | Full-speed modern RTG |
|
||||
| MiSTer (DDR) | 800+ MB/s | ~400 MB/s | No bottleneck |
|
||||
|
||||
### Optimisation Patterns
|
||||
### Optimization Patterns
|
||||
|
||||
| Pattern | Description |
|
||||
|---|---|
|
||||
|
|
@ -807,7 +807,7 @@ When the user drags a screen to reveal the one behind it:
|
|||
|---|---|---|
|
||||
| Black screen on mode switch | SetSwitch not toggling pass-through correctly | Check VGA cable; verify DAC enable/disable |
|
||||
| Corrupted display | CRTC timing wrong | Verify HTotal/VTotal/sync values in SetGC |
|
||||
| Garbled colours | Wrong RGBFTYPE (BGR vs RGB swap) | Match format to VGA controller's native order |
|
||||
| Garbled colors | Wrong RGBFTYPE (BGR vs RGB swap) | Match format to VGA controller's native order |
|
||||
| Slow scrolling | No BlitRect acceleration | Implement hardware blit; check VRAM alignment |
|
||||
| Cursor flickers | SoftSpriteWA=TRUE | Implement HW cursor (SetSprite*) |
|
||||
| Guru on screen close | Driver doesn't handle CloseScreen cleanup | Free VRAM allocations in board cleanup |
|
||||
|
|
@ -860,7 +860,7 @@ flowchart LR
|
|||
|
||||
The Native driver's icon supports the `NOBLITTER` tooltype, which controls how native Amiga blitter operations are handled:
|
||||
|
||||
| Setting | Behaviour | When to Use |
|
||||
| Setting | Behavior | When to Use |
|
||||
|---|---|---|
|
||||
| `NOBLITTER=YES` (default) | **All** blits go through CPU. Native Amiga Blitter is completely bypassed. | Default. Best for accelerated systems (68030+). Frees Chip RAM. |
|
||||
| `NOBLITTER=NO` | Native Blitter is used when **both** source and destination are in Chip RAM. Falls back to CPU only for Fast RAM bitmaps. | Use when you want hardware Blitter for DMA-visible operations (e.g., playfield scrolling) but CPU for off-screen work. |
|
||||
|
|
@ -898,13 +898,13 @@ RTG:
|
|||
|
||||
| Category | Example | Root Cause | Fix |
|
||||
|---|---|---|---|
|
||||
| **Hardware banging** | Game writes directly to `$DFF180` colour registers | Bypasses OS entirely | Must run on native screen — unfixable for RTG |
|
||||
| **Hardware banging** | Game writes directly to `$DFF180` color registers | Bypasses OS entirely | Must run on native screen — unfixable for RTG |
|
||||
| **Direct bitplane access** | Demo reads `rp->BitMap->Planes[3]` | Assumes planar layout | Use `ReadPixelArray`/`WritePixelArray` |
|
||||
| **Copper effects** | Colour cycling via Copper list | Copper is native-chipset only | Must use `LoadRGB32` with timer |
|
||||
| **HAM mode** | HAM6/HAM8 encoding | HAM is a planar encoding trick | No RTG equivalent; must use true-colour |
|
||||
| **Copper effects** | Color cycling via Copper list | Copper is native-chipset only | Must use `LoadRGB32` with timer |
|
||||
| **HAM mode** | HAM6/HAM8 encoding | HAM is a planar encoding trick | No RTG equivalent; must use true-color |
|
||||
| **Sprite tricks** | Multiplexed sprites, sprite-field overlays | Hardware sprites are native only | RTG uses software cursor or HW sprite emulation |
|
||||
| **DMA-dependent timing** | Code waits for Blitter by polling `DMACONR` | RTG blits are CPU, not DMA | Use `WaitBlit()` or P96's `WaitBlitter` |
|
||||
| **Fixed palette assumptions** | Expects 4-colour Workbench pen mapping | RTG may have 256+ pens | Use `ObtainBestPen()` |
|
||||
| **Fixed palette assumptions** | Expects 4-color Workbench pen mapping | RTG may have 256+ pens | Use `ObtainBestPen()` |
|
||||
|
||||
### The FBlit Conflict
|
||||
|
||||
|
|
@ -993,7 +993,7 @@ else
|
|||
|---|---|---|
|
||||
| Black screen on mode switch | SetSwitch not toggling pass-through correctly | Check VGA cable; verify DAC enable/disable |
|
||||
| Corrupted display | CRTC timing wrong | Verify HTotal/VTotal/sync values in SetGC |
|
||||
| Garbled colours | Wrong RGBFTYPE (BGR vs RGB swap) | Match format to VGA controller's native order |
|
||||
| Garbled colors | Wrong RGBFTYPE (BGR vs RGB swap) | Match format to VGA controller's native order |
|
||||
| Slow scrolling | No BlitRect acceleration | Implement hardware blit; check VRAM alignment |
|
||||
| Cursor flickers | SoftSpriteWA=TRUE | Implement HW cursor (SetSprite*) |
|
||||
| Guru on screen close | Driver doesn't handle CloseScreen cleanup | Free VRAM allocations in board cleanup |
|
||||
|
|
|
|||
|
|
@ -301,6 +301,7 @@ For any API that involves allocation, messaging, or shared resources, exemplary
|
|||
### 9. The "Impact on FPGA/Emulation" Section
|
||||
|
||||
Since this repository targets MiSTer FPGA developers, the best articles note implementation concerns for hardware reproduction: timing-sensitive code, self-modifying code, custom chip register access patterns, cache coherency requirements.
|
||||
If article is related completely to the Amiga software and has no relationship with FPGA/Emulation - just omit it.
|
||||
|
||||
### What Mediocre Articles Are Missing
|
||||
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -25,7 +25,7 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post
|
|||
| **🎨 Graphics** | Planar bitmaps, Copper deep dive, Blitter deep dive, HAM/EHB modes, sprites, display pipeline |
|
||||
| **🖥️ Intuition** | Screens, windows, IDCMP, GadTools, BOOPSI, menus |
|
||||
| **📟 Devices** | trackdisk, SCSI, serial, parallel, timer, audio, keyboard, console |
|
||||
| **📚 Libraries** | utility, expansion, IFFParse, locale, ARexx, math, layers, diskfont |
|
||||
| **📚 Libraries** | utility, expansion, IFFParse, locale, ARexx, math, layers, diskfont, DataTypes, AmigaGuide, translator (speech) |
|
||||
| **🌐 Networking** | bsdsocket.library API, SANA-II, TCP/IP stacks comparison |
|
||||
| **🛠️ Toolchain** | GCC (bebbo/Codeberg), vasm/vlink, SAS/C, NDK, Makefiles, debugging |
|
||||
| **🔍 Reverse Engineering** | IDA/Ghidra setup, compiler fingerprints, binary patching, 3 case studies |
|
||||
|
|
@ -201,6 +201,7 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post
|
|||
| [idcmp.md](09_intuition/idcmp.md) | IDCMP message classes, IntuiMessage |
|
||||
| [boopsi.md](09_intuition/boopsi.md) | BOOPSI object system, custom classes |
|
||||
| [input_events.md](09_intuition/input_events.md) | InputEvent, Commodities Exchange |
|
||||
| [commodities.md](09_intuition/commodities.md) | Commodities Exchange: hotkeys, screen blankers, CxObject API |
|
||||
|
||||
### 10 — Devices
|
||||
| File | Topic |
|
||||
|
|
@ -223,18 +224,21 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post
|
|||
| [expansion.md](11_libraries/expansion.md) | Zorro bus, AutoConfig |
|
||||
| [icon.md](11_libraries/icon.md) | Workbench icons, DiskObject |
|
||||
| [workbench.md](11_libraries/workbench.md) | WBStartup, AppWindow |
|
||||
| [iffparse.md](11_libraries/iffparse.md) | IFF file parsing, ILBM/8SVX |
|
||||
| [locale.md](11_libraries/locale.md) | Internationalisation, catalogs |
|
||||
| [iffparse.md](11_libraries/iffparse.md) | IFF file parsing: ILBM/8SVX/ANIM, nested chunks, ByteRun1, PBM deinterleaving, clipboard, decision guide vs DataTypes |
|
||||
| [locale.md](11_libraries/locale.md) | Internationalization, catalogs |
|
||||
| [keymap.md](11_libraries/keymap.md) | Keyboard mapping, MapRawKey |
|
||||
| [rexxsyslib.md](11_libraries/rexxsyslib.md) | ARexx interface |
|
||||
| [mathffp.md](11_libraries/mathffp.md) | Floating point libraries, FFP, IEEE |
|
||||
| [layers.md](11_libraries/layers.md) | Window clipping layers |
|
||||
| [diskfont.md](11_libraries/diskfont.md) | Disk-based font loading |
|
||||
| [datatypes.md](11_libraries/datatypes.md) | DataTypes system: object-oriented file loading for images, sound, text, animation |
|
||||
| [amigaguide.md](11_libraries/amigaguide.md) | AmigaGuide hypertext help system: database format, API, context-sensitive help |
|
||||
| [translator.md](11_libraries/translator.md) | translator.library: English-to-phonetic translation, narrator.device integration, ARPABET phonemes |
|
||||
|
||||
### 12 — Networking
|
||||
| File | Topic |
|
||||
|---|---|
|
||||
| [bsdsocket.md](12_networking/bsdsocket.md) | BSD socket API, WaitSelect |
|
||||
| [bsdsocket.md](12_networking/bsdsocket.md) | **BSD socket API deep dive: event-loop patterns, WaitSelect signal integration, non-blocking I/O, multi-socket design, performance** |
|
||||
| [sana2.md](12_networking/sana2.md) | SANA-II device driver interface |
|
||||
| [tcp_ip_stacks.md](12_networking/tcp_ip_stacks.md) | AmiTCP vs Miami vs Roadshow |
|
||||
| [protocols.md](12_networking/protocols.md) | DNS, HTTP, DHCP |
|
||||
|
|
@ -242,8 +246,9 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post
|
|||
### 13 — Toolchain
|
||||
| File | Topic |
|
||||
|---|---|
|
||||
| [vasm_vlink.md](13_toolchain/vasm_vlink.md) | vasm assembler, vlink linker |
|
||||
| [vasm_vlink.md](13_toolchain/vasm_vlink.md) | **vasm assembler & vlink linker: modular architecture, Devpac/PhxAss compatibility, optimization system, linker scripts, C↔asm interop, 30+ output formats** |
|
||||
| [gcc_amiga.md](13_toolchain/gcc_amiga.md) | m68k-amigaos-gcc (bebbo fork, Codeberg) |
|
||||
| [vbcc.md](13_toolchain/vbcc.md) | **VBCC cross-compiler: `__reg()` register control, AmigaOS/MorphOS/AROS, vlink, fast compile times** |
|
||||
| [sasc.md](13_toolchain/sasc.md) | SAS/C 6.x compiler |
|
||||
| [stormc.md](13_toolchain/stormc.md) | StormC native IDE and C/C++ compiler |
|
||||
| [fd_files.md](13_toolchain/fd_files.md) | FD/SFD file format, Python parser |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue