mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
Expand documentation suite: 30+ articles enriched with diagrams, code examples, and hardware details
Graphics: text_fonts (bitmap layout, styles), sprites (DMA, multiplexing), gfx_base (chipset detection), rastport (draw modes, clipping), ham_ehb (mermaid fixes), display_modes (HAM palettes) Devices: scsi (per-model interfaces, Gayle limits, CD-ROM, native vs vendor drivers), console (ANSI sequences, CON:/RAW:), parallel (CIA registers, pinout), timer (resource exhaustion), gameport (quadrature, XOR state) Libraries: workbench (WBStartup, AppWindow/Icon/MenuItem), rexxsyslib (ARexx port hosting, command parsing), diskfont (font directory, colour fonts), keymap (rawkey codes, dead keys), locale (catalogue system, date formatting), layers (ClipRect, refresh types), utility (TagItem chains), icon (DiskObject, ToolTypes), iffparse (IFF structure, ByteRun1), expansion (Zorro AutoConfig) Networking: tcp_ip_stacks (major rewrite - Amiga vs Unix architecture, SANA-II pipeline, PPP/SLIP dial-up, Ethernet cards, MiSTer), bsdsocket (pure API ref), sana2 (buffer hooks, driver requirements), protocols (all code examples). Deduplicated overlap between the three files. Toolchain: debugging (Enforcer patterns, SnoopDOS, GDB remote, kprintf checklist), sasc (pragma encoding, __saveds idioms), stormc (NEW - StormC IDE, C++, PowerPC) References: error_codes (DOS, Exec, trackdisk, Intuition error tables) Driver development: rtg_driver (Native driver analysis, P96 tuning) All 22 README indexes updated. Root README synced with stormc.md entry.
This commit is contained in:
parent
0ded078134
commit
f61c26b542
38 changed files with 6402 additions and 1065 deletions
|
|
@ -2,20 +2,22 @@
|
|||
|
||||
# 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.
|
||||
|
||||
## Section Index
|
||||
|
||||
| File | Description |
|
||||
|---|---|
|
||||
| [gfx_base.md](gfx_base.md) | GfxBase structure and global graphics state |
|
||||
| [gfx_base.md](gfx_base.md) | GfxBase structure, chipset detection (OCS/ECS/AGA), PAL/NTSC, display pipeline (MakeVPort/MrgCop/LoadView), blitter queue |
|
||||
| [bitmap.md](bitmap.md) | BitMap structure, planar layout, allocation |
|
||||
| [display_modes.md](display_modes.md) | Chipset comparison (OCS/ECS/AGA), ModeID system, PAL/NTSC timing, DMA slot budget |
|
||||
| [ham_ehb_modes.md](ham_ehb_modes.md) | HAM6/HAM8 encoding pipeline, EHB half-brite, fringing, palette programming, FPGA decoder logic |
|
||||
| [copper.md](copper.md) | Copper coprocessor, instruction format, UCopList |
|
||||
| [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 |
|
||||
| [sprites.md](sprites.md) | Hardware sprites, SimpleSprite, MoveSprite |
|
||||
| [rastport.md](rastport.md) | RastPort, drawing primitives, layers |
|
||||
| [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 |
|
||||
| [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, TextAttr, OpenFont, Text rendering |
|
||||
| [display_modes.md](display_modes.md) | Display database, ModeID, monitor specs |
|
||||
| [ham_ehb_modes.md](ham_ehb_modes.md) | HAM6, HAM8, and EHB special display modes |
|
||||
| [text_fonts.md](text_fonts.md) | TextFont bitmap layout, baseline rendering, algorithmic styles, AvailFonts enumeration |
|
||||
| [animation.md](animation.md) | AnimOb, BOB, VSprite, GEL system |
|
||||
| [copper_programming.md](copper_programming.md) | Copper deep dive: architecture, examples, system API |
|
||||
| [blitter_programming.md](blitter_programming.md) | Blitter deep dive: minterms, cookie-cut, line draw |
|
||||
|
|
|
|||
|
|
@ -1,74 +1,217 @@
|
|||
[← Home](../README.md) · [Graphics](README.md)
|
||||
|
||||
# Display Modes — Display Database, ModeID, Monitor Specs
|
||||
# Display Modes — Chipset Generations, ModeID, and Timing
|
||||
|
||||
## Overview
|
||||
|
||||
OS 3.0+ provides a **display database** that abstracts monitor/chipset capabilities. Applications query available modes by `ModeID` rather than hardcoding `HIRES`/`LACE` flags.
|
||||
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.
|
||||
|
||||
OS 3.0+ provides a **display database** that abstracts these capabilities. Applications query available modes by `ModeID` rather than hardcoding chipset-specific flags.
|
||||
|
||||
---
|
||||
|
||||
## Chipset Comparison
|
||||
|
||||
| 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) |
|
||||
| **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 |
|
||||
| **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) |
|
||||
|
||||
---
|
||||
|
||||
## Display Timing Fundamentals
|
||||
|
||||
All Amiga display modes are based on PAL or NTSC television timing:
|
||||
|
||||
### PAL (Europe, Australia)
|
||||
|
||||
```
|
||||
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)
|
||||
Pixel clock (hires): 14,187,580 Hz
|
||||
```
|
||||
|
||||
### NTSC (Americas, Japan)
|
||||
|
||||
```
|
||||
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
|
||||
Pixel clock (lores): 7,159,090 Hz
|
||||
Pixel clock (hires): 14,318,180 Hz
|
||||
```
|
||||
|
||||
### Display Cycle Anatomy
|
||||
|
||||
```
|
||||
←── Horizontal line (~64 µs PAL) ──→
|
||||
┌───────────────────────────────────────────────────────────┐
|
||||
│ HSYNC │ Left │ Active Display Area │ Right │
|
||||
│ │Border │ (bitplane DMA + sprite DMA) │Border │
|
||||
│ ~4.7µs│ │ │ │
|
||||
└───────────────────────────────────────────────────────────┘
|
||||
|
||||
Vertical:
|
||||
┌── VSYNC (2.5 lines) ──┐
|
||||
│ Top Border │
|
||||
│ Active Display │ ← 256 lines (PAL) / 200 (NTSC)
|
||||
│ Bottom Border │
|
||||
└────────────────────────┘
|
||||
```
|
||||
|
||||
> **FPGA implication**: the MiSTer core must replicate these exact timings for correct DMA slot allocation. Many programs (especially demos) count DMA cycles and will break if timing is even slightly off.
|
||||
|
||||
---
|
||||
|
||||
## ModeID Format
|
||||
|
||||
A ModeID is a ULONG:
|
||||
A ModeID is a 32-bit value encoding the monitor driver and mode:
|
||||
|
||||
```
|
||||
bits 31–16: Monitor ID
|
||||
bits 15–0: Mode within that monitor
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ 31 16 │ 15 0 │
|
||||
│ Monitor ID │ Mode within monitor │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| ModeID | Name | Resolution |
|
||||
### Standard Mode IDs
|
||||
|
||||
| ModeID | Name | Resolution | Depth | Chipset |
|
||||
|---|---|---|---|---|
|
||||
| `$00000000` | PAL:LORES | 320×256 | 5 | OCS+ |
|
||||
| `$00008000` | PAL:HIRES | 640×256 | 4 | OCS+ |
|
||||
| `$00000004` | PAL:LORES-LACE | 320×512 | 5 | OCS+ |
|
||||
| `$00008004` | PAL:HIRES-LACE | 640×512 | 4 | OCS+ |
|
||||
| `$00080000` | PAL:SUPERHIRES | 1280×256 | 2 | ECS+ |
|
||||
| `$00080004` | PAL:SUPERHIRES-LACE | 1280×512 | 2 | ECS+ |
|
||||
| `$00039000` | DBLPAL:LORES | 320×256 | 8 | AGA |
|
||||
| `$00039004` | DBLPAL:HIRES | 640×256 | 8 | AGA |
|
||||
| `$00039024` | DBLPAL:HIRES-LACE | 640×512 | 8 | AGA |
|
||||
| `$00011000` | DBLNTSC:LORES | 320×200 | 8 | AGA |
|
||||
| `$00011004` | DBLNTSC:HIRES | 640×200 | 8 | AGA |
|
||||
| `$00000800` | HAM | 320×256 HAM6 | 6 | OCS+ |
|
||||
| `$00000080` | EHB | 320×256 EHB | 6 | OCS+ |
|
||||
|
||||
### Mode Flags (bits within ModeID)
|
||||
|
||||
| Bit | Mask | Meaning |
|
||||
|---|---|---|
|
||||
| `$00000000` | LORES | 320×256 (PAL) / 320×200 (NTSC) |
|
||||
| `$00008000` | HIRES | 640×256/200 |
|
||||
| `$00000004` | LORES-LACE | 320×512/400 |
|
||||
| `$00008004` | HIRES-LACE | 640×512/400 |
|
||||
| `$00080000` | SUPERHIRES | 1280×256 (ECS+) |
|
||||
| `$00080004` | SUPERHIRES-LACE | 1280×512 |
|
||||
| `$00039000` | DBLPAL-LORES | 320×256 (AGA scan-doubled) |
|
||||
| `$00039004` | DBLPAL-HIRES | 640×256 (AGA) |
|
||||
| `$00011000` | DBLNTSC-LORES | 320×200 (AGA) |
|
||||
| 2 | `$0004` | LACE — interlaced (double vertical resolution) |
|
||||
| 11 | `$0800` | HAM — Hold-And-Modify mode |
|
||||
| 7 | `$0080` | EHB — Extra Half-Brite mode |
|
||||
| 15 | `$8000` | HIRES — double horizontal resolution |
|
||||
| 19 | `$80000` | SUPERHIRES — quadruple horizontal resolution (ECS+) |
|
||||
|
||||
---
|
||||
|
||||
## AGA Fetch Modes
|
||||
|
||||
AGA introduced wider data fetch widths, reducing DMA overhead:
|
||||
|
||||
| Fetch Mode | Bits per Fetch | FMODE Value | Effect |
|
||||
|---|---|---|---|
|
||||
| 1× | 16 bits | 0 | OCS compatible — 1 word per slot |
|
||||
| 2× | 32 bits | 1 | 2 words per slot — more bandwidth for deeper modes |
|
||||
| 4× | 64 bits | 3 | 4 words per slot — required for 8-plane hires |
|
||||
|
||||
```c
|
||||
/* Set AGA fetch mode (custom register): */
|
||||
custom->fmode = 3; /* 4× fetch — maximum bandwidth */
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> 4× fetch mode causes 64-pixel horizontal alignment constraints. Sprites also widen to 32 or 64 pixels in 2×/4× mode. Many OCS-era programs break if FMODE ≠ 0.
|
||||
|
||||
---
|
||||
|
||||
## Querying the Display Database
|
||||
|
||||
```c
|
||||
/* graphics.library 39+ */
|
||||
/* graphics.library 39+ — enumerate all available modes: */
|
||||
ULONG modeID = INVALID_ID;
|
||||
struct DisplayInfo di;
|
||||
struct DimensionInfo dims;
|
||||
struct MonitorInfo mon;
|
||||
|
||||
while ((modeID = NextDisplayInfo(modeID)) != INVALID_ID) {
|
||||
while ((modeID = NextDisplayInfo(modeID)) != INVALID_ID)
|
||||
{
|
||||
if (GetDisplayInfoData(NULL, (UBYTE *)&di, sizeof(di),
|
||||
DTAG_DISP, modeID)) {
|
||||
if (GetDisplayInfoData(NULL, (UBYTE *)&dims, sizeof(dims),
|
||||
DTAG_DIMS, modeID)) {
|
||||
Printf("ModeID $%08lx: %ldx%ld, %ld colours\n",
|
||||
modeID,
|
||||
dims.Nominal.MaxX - dims.Nominal.MinX + 1,
|
||||
dims.Nominal.MaxY - dims.Nominal.MinY + 1,
|
||||
1 << di.NotAvailable ? 0 : dims.MaxDepth);
|
||||
}
|
||||
DTAG_DISP, modeID))
|
||||
{
|
||||
if (di.NotAvailable) continue; /* skip unavailable modes */
|
||||
|
||||
GetDisplayInfoData(NULL, (UBYTE *)&dims, sizeof(dims),
|
||||
DTAG_DIMS, modeID);
|
||||
GetDisplayInfoData(NULL, (UBYTE *)&mon, sizeof(mon),
|
||||
DTAG_MNTR, modeID);
|
||||
|
||||
Printf("$%08lx: %ldx%ld, %ld colours, %s\n",
|
||||
modeID,
|
||||
dims.Nominal.MaxX - dims.Nominal.MinX + 1,
|
||||
dims.Nominal.MaxY - dims.Nominal.MinY + 1,
|
||||
1L << dims.MaxDepth,
|
||||
mon.Mspc->ms_Node.xln_Name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Mode Selection
|
||||
### Best Mode Selection
|
||||
|
||||
```c
|
||||
ULONG bestMode = BestModeIDA((struct TagItem[]){
|
||||
{ BIDTAG_NominalWidth, 640 },
|
||||
{ BIDTAG_NominalHeight, 480 },
|
||||
{ BIDTAG_Depth, 8 },
|
||||
{ TAG_DONE, 0 }
|
||||
});
|
||||
/* Find the best mode matching desired specs: */
|
||||
ULONG bestMode = BestModeID(
|
||||
BIDTAG_NominalWidth, 640,
|
||||
BIDTAG_NominalHeight, 480,
|
||||
BIDTAG_Depth, 8,
|
||||
BIDTAG_MonitorID, PAL_MONITOR_ID,
|
||||
TAG_DONE);
|
||||
|
||||
if (bestMode != INVALID_ID)
|
||||
Printf("Best mode: $%08lx\n", bestMode);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DMA Slot Budget
|
||||
|
||||
The display system shares DMA bandwidth with other custom chips. Each scanline has a fixed number of DMA slots:
|
||||
|
||||
| DMA Consumer | Slots Used |
|
||||
|---|---|
|
||||
| Disk DMA | 3 words |
|
||||
| Audio (4 channels) | 4 words |
|
||||
| Sprites (8) | 16 words |
|
||||
| Bitplane (lowres, 5 planes) | 40 words |
|
||||
| Bitplane (hires, 4 planes) | 80 words |
|
||||
| Copper | 1 per instruction pair |
|
||||
| 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.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `graphics/displayinfo.h`, `graphics/modeid.h`
|
||||
- NDK39: `graphics/displayinfo.h`, `graphics/modeid.h`, `graphics/gfxbase.h`
|
||||
- HRM: *Amiga Hardware Reference Manual* — Display chapters
|
||||
- ADCD 2.1: `NextDisplayInfo`, `GetDisplayInfoData`, `BestModeIDA`
|
||||
- See also: [views.md](views.md) — ViewPort and View construction
|
||||
- See also: [copper.md](copper.md) — Copper display list programming
|
||||
- See also: [ham_ehb_modes.md](ham_ehb_modes.md) — special display modes
|
||||
|
|
|
|||
|
|
@ -4,7 +4,29 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`graphics.library` manages all display output, drawing primitives, and custom chip programming. `GfxBase` is the library base containing display state, chip revision info, and the system copper lists.
|
||||
`graphics.library` is the core drawing library in AmigaOS. It manages all display output, drawing primitives, font rendering, and custom chip programming. `GfxBase` — the library base — contains the system's display state, chip revision info, system copper lists, and the display mode database.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "GfxBase — Global Graphics State"
|
||||
AV["ActiView<br/>(current display)"]
|
||||
COP["copinit<br/>(system copper list)"]
|
||||
CHIP["ChipRevBits0<br/>(OCS/ECS/AGA detection)"]
|
||||
FONTS["TextFonts<br/>(system font list)"]
|
||||
MODES["ModesList<br/>(display mode database)"]
|
||||
VBF["VBlankFrequency<br/>(50=PAL, 60=NTSC)"]
|
||||
end
|
||||
|
||||
APP["Application"] -->|"OpenLibrary"| GFX["graphics.library"]
|
||||
GFX --> AV
|
||||
GFX --> COP
|
||||
GFX --> CHIP
|
||||
|
||||
AV -->|"MakeVPort / MrgCop /<br/>LoadView"| HW["Custom Chips<br/>(Agnus/Denise)"]
|
||||
|
||||
style GFX fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style HW fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -15,7 +37,7 @@
|
|||
struct GfxBase {
|
||||
struct Library LibNode;
|
||||
struct View *ActiView; /* currently active View */
|
||||
struct copinit *copinit; /* system copper list init */
|
||||
struct copinit *copinit; /* system copper list initialisation */
|
||||
LONG *cia; /* CIA base (deprecated) */
|
||||
LONG *blitter; /* blitter base (deprecated) */
|
||||
UWORD *LOFlist; /* long-frame copper list pointer */
|
||||
|
|
@ -24,26 +46,26 @@ struct GfxBase {
|
|||
struct bltnode *blttl; /* blitter queue tail */
|
||||
struct bltnode *bsblthd;
|
||||
struct bltnode *bsblttl;
|
||||
struct Interrupt vbsrv; /* vertical blank server list */
|
||||
struct Interrupt timsrv;
|
||||
struct Interrupt bltsrv;
|
||||
struct Interrupt vbsrv; /* vertical blank server chain */
|
||||
struct Interrupt timsrv; /* timer server chain */
|
||||
struct Interrupt bltsrv; /* blitter-done server chain */
|
||||
struct List TextFonts; /* system font list */
|
||||
struct TextFont *DefaultFont; /* default system font */
|
||||
struct TextFont *DefaultFont; /* default system font (topaz.8) */
|
||||
UWORD Modes; /* current display mode bits */
|
||||
BYTE VBlankFrequency; /* 50 (PAL) or 60 (NTSC) */
|
||||
BYTE DisplayFlags; /* PAL/NTSC/GENLOCK flags */
|
||||
UWORD NormalDisplayColumns;
|
||||
UWORD NormalDisplayRows;
|
||||
UWORD NormalDisplayColumns; /* default display width */
|
||||
UWORD NormalDisplayRows; /* default display height */
|
||||
UWORD MaxDisplayColumn;
|
||||
UWORD MaxDisplayRow;
|
||||
UWORD ChipRevBits0; /* chip revision flags */
|
||||
UWORD ChipRevBits0; /* chip revision flags — see below */
|
||||
/* ... many more fields ... */
|
||||
struct MonitorSpec *monitor_id;
|
||||
struct List MonitorList;
|
||||
struct List MonitorList; /* installed monitors */
|
||||
struct List ModesList; /* display mode database */
|
||||
UBYTE MemType; /* memory type flags */
|
||||
/* OS 3.x additions */
|
||||
APTR ChunkyToPlanarPtr; /* c2p conversion routine */
|
||||
APTR ChunkyToPlanarPtr; /* c2p conversion routine pointer */
|
||||
};
|
||||
```
|
||||
|
||||
|
|
@ -51,14 +73,16 @@ struct GfxBase {
|
|||
|
||||
## Chip Revision Detection
|
||||
|
||||
The `ChipRevBits0` field identifies which chipset generation is present — essential for FPGA cores that need to report their emulated chipset level:
|
||||
|
||||
```c
|
||||
/* graphics/gfxbase.h */
|
||||
#define GFXB_BIG_BLITS 0 /* big blitter (ECS Agnus) */
|
||||
#define GFXB_HR_AGNUS 0 /* same — hi-res Agnus */
|
||||
#define GFXB_BIG_BLITS 0 /* big blitter (ECS Agnus — 1MB chip) */
|
||||
#define GFXB_HR_AGNUS 0 /* hi-res Agnus (same bit as above) */
|
||||
#define GFXB_HR_DENISE 1 /* ECS Denise (SuperHires, scan-doubling) */
|
||||
#define GFXB_AA_ALICE 2 /* AGA Alice (A1200/A4000) */
|
||||
#define GFXB_AA_LISA 3 /* AGA Lisa */
|
||||
#define GFXB_AA_MLISA 4 /* AGA Lisa (modified) */
|
||||
#define GFXB_AA_LISA 3 /* AGA Lisa (A1200/A4000) */
|
||||
#define GFXB_AA_MLISA 4 /* AGA modified Lisa */
|
||||
|
||||
#define GFXF_BIG_BLITS (1<<0)
|
||||
#define GFXF_HR_AGNUS (1<<0)
|
||||
|
|
@ -66,20 +90,140 @@ struct GfxBase {
|
|||
#define GFXF_AA_ALICE (1<<2)
|
||||
#define GFXF_AA_LISA (1<<3)
|
||||
#define GFXF_AA_MLISA (1<<4)
|
||||
```
|
||||
|
||||
/* Check for AGA: */
|
||||
if (GfxBase->ChipRevBits0 & GFXF_AA_ALICE) {
|
||||
/* AGA chipset — 8-bit planar, 256 colours in indexed mode */
|
||||
### Detecting the Chipset
|
||||
|
||||
```c
|
||||
struct GfxBase *GfxBase = (struct GfxBase *)
|
||||
OpenLibrary("graphics.library", 0);
|
||||
|
||||
if (GfxBase->ChipRevBits0 & GFXF_AA_ALICE)
|
||||
{
|
||||
/* AGA chipset (A1200/A4000) */
|
||||
/* 8-bit planar, 256 colours, 24-bit palette */
|
||||
}
|
||||
else if (GfxBase->ChipRevBits0 & GFXF_HR_DENISE)
|
||||
{
|
||||
/* ECS chipset (A3000/A600) */
|
||||
/* SuperHires, productivity modes, scan-doubler */
|
||||
}
|
||||
else if (GfxBase->ChipRevBits0 & GFXF_HR_AGNUS)
|
||||
{
|
||||
/* ECS Agnus with OCS Denise (A500+/A2000 upgraded) */
|
||||
/* 1 MB Chip RAM support, but no ECS display features */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* OCS chipset (original A500/A1000/A2000) */
|
||||
/* 512 KB Chip RAM, 4096 colour palette */
|
||||
}
|
||||
```
|
||||
|
||||
| Bits Set | Chipset | Systems |
|
||||
|---|---|---|
|
||||
| None | OCS | A500, A1000, A2000 |
|
||||
| `HR_AGNUS` | ECS Agnus only | A500+ (Fat Agnus upgrade) |
|
||||
| `HR_AGNUS + HR_DENISE` | Full ECS | A600, A3000 |
|
||||
| `AA_ALICE + AA_LISA` | AGA | A1200, A4000, CD32 |
|
||||
|
||||
---
|
||||
|
||||
## PAL vs NTSC Detection
|
||||
|
||||
```c
|
||||
if (GfxBase->VBlankFrequency == 50)
|
||||
{
|
||||
/* PAL: 50 Hz, 312 lines/field, 625 total (interlaced) */
|
||||
/* Standard resolution: 320×256 */
|
||||
}
|
||||
else /* 60 */
|
||||
{
|
||||
/* NTSC: 60 Hz, 262 lines/field, 525 total (interlaced) */
|
||||
/* Standard resolution: 320×200 */
|
||||
}
|
||||
|
||||
/* DisplayFlags also has the info: */
|
||||
if (GfxBase->DisplayFlags & PAL) /* PAL system */
|
||||
if (GfxBase->DisplayFlags & NTSC) /* NTSC system */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PAL vs NTSC
|
||||
## The Display Pipeline
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Application"
|
||||
VP["ViewPort<br/>(one per screen)"]
|
||||
end
|
||||
|
||||
subgraph "GfxBase Pipeline"
|
||||
MV["MakeVPort(view, vp)<br/>→ build copper instructions<br/>for this viewport"]
|
||||
MC["MrgCop(view)<br/>→ merge all viewport copper<br/>into single list"]
|
||||
LV["LoadView(view)<br/>→ install merged copper list<br/>into hardware"]
|
||||
end
|
||||
|
||||
subgraph "Hardware"
|
||||
COP["Copper DMA<br/>executes copper list"]
|
||||
CHIPS["Custom Chips<br/>display image"]
|
||||
end
|
||||
|
||||
VP --> MV --> MC --> LV --> COP --> CHIPS
|
||||
|
||||
style MV fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style MC fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style LV fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
```
|
||||
|
||||
### Key Display Functions
|
||||
|
||||
| Function | Purpose |
|
||||
|---|---|
|
||||
| `MakeVPort(view, vp)` | Generate copper instructions for one ViewPort |
|
||||
| `MrgCop(view)` | Merge all ViewPort copper lists into unified system list |
|
||||
| `LoadView(view)` | Install the merged copper list into the hardware (LOF/SHF) |
|
||||
| `WaitTOF()` | Wait for top of frame (vertical blank) — used to sync display updates |
|
||||
| `WaitBOVP(vp)` | Wait for the beam to pass a specific ViewPort |
|
||||
|
||||
---
|
||||
|
||||
## Blitter Queue
|
||||
|
||||
GfxBase maintains a queue of pending Blitter operations (`blthd`/`blttl`). When an application calls `BltBitMap`, the operation may be queued and executed asynchronously by the Blitter interrupt:
|
||||
|
||||
```c
|
||||
if (GfxBase->VBlankFrequency == 50) /* PAL: 50Hz, 625 lines */
|
||||
if (GfxBase->VBlankFrequency == 60) /* NTSC: 60Hz, 525 lines */
|
||||
/* Start a blit — may be queued: */
|
||||
BltBitMap(srcBM, sx, sy, dstBM, dx, dy, w, h, 0xC0, 0xFF, NULL);
|
||||
|
||||
/* Wait for ALL pending blits to complete: */
|
||||
WaitBlit();
|
||||
|
||||
/* Or use OwnBlitter/DisownBlitter for exclusive access: */
|
||||
OwnBlitter(); /* blocks until blitter is free, then locks it */
|
||||
/* ... direct blitter register access ... */
|
||||
DisownBlitter(); /* release for other tasks */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## System Fonts
|
||||
|
||||
```c
|
||||
/* Get the default system font: */
|
||||
struct TextFont *sysFont = GfxBase->DefaultFont;
|
||||
Printf("System font: %s, size %d\n",
|
||||
sysFont->tf_Message.mn_Node.ln_Name,
|
||||
sysFont->tf_YSize);
|
||||
|
||||
/* Enumerate all loaded fonts: */
|
||||
struct Node *node;
|
||||
for (node = GfxBase->TextFonts.lh_Head;
|
||||
node->ln_Succ;
|
||||
node = node->ln_Succ)
|
||||
{
|
||||
Printf("Font: %s\n", node->ln_Name);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -88,3 +232,6 @@ if (GfxBase->VBlankFrequency == 60) /* NTSC: 60Hz, 525 lines */
|
|||
|
||||
- NDK39: `graphics/gfxbase.h`
|
||||
- ADCD 2.1: graphics.library autodocs
|
||||
- See also: [views.md](views.md) — View/ViewPort construction
|
||||
- See also: [copper.md](copper.md) — Copper coprocessor
|
||||
- See also: [display_modes.md](display_modes.md) — display mode database
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -14,121 +14,429 @@ The Amiga offers two unique display modes that squeeze many more colours from li
|
|||
|
||||
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 halved)
|
||||
- Bitplane values 32–63: display the colour from register (value − 32) at **half brightness** (all RGB components shifted right by 1)
|
||||
|
||||
```
|
||||
Bitplanes 0–4 → 5-bit colour index (0–31)
|
||||
Bitplane 5 → "half brightness" flag
|
||||
```mermaid
|
||||
flowchart LR
|
||||
BP["6 Bitplanes"] --> SPLIT{"Bit 5?"}
|
||||
SPLIT -->|"0"| NORMAL["Bits 4-0 = index<br/>Palette lookup"]
|
||||
SPLIT -->|"1"| EHB["Bits 4-0 = index<br/>Palette lookup<br/>then R>>1, G>>1, B>>1"]
|
||||
NORMAL --> DAC["RGB to DAC"]
|
||||
EHB --> DAC
|
||||
|
||||
style EHB fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
style NORMAL fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
### Effective Result
|
||||
```
|
||||
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)
|
||||
|
||||
- 32 programmer-defined colours + 32 fixed half-brightness versions = **64 colours**
|
||||
- Zero additional palette RAM needed
|
||||
- Useful for shadows and smooth gradients
|
||||
Example pixel value = 5 (binary: 000101):
|
||||
Bit 5 = 0 → normal
|
||||
Bits 4-0 = 00101 = palette index 5
|
||||
Output colour = palette[5] (full brightness)
|
||||
```
|
||||
|
||||
### Enabling EHB
|
||||
### Programming EHB
|
||||
|
||||
```c
|
||||
/* In ViewPort ColorMap: */
|
||||
/* Simply use 6 bitplanes with no HAM flag */
|
||||
struct BitMap bm;
|
||||
InitBitMap(&bm, 6, 320, 256); /* 6 planes */
|
||||
/* No EXTRA_HALFBRITE flag needed — it's automatic when depth=6 */
|
||||
/* EHB is automatic when using 6 bitplanes without HAM flag: */
|
||||
struct Screen *scr = OpenScreenTags(NULL,
|
||||
SA_Width, 320,
|
||||
SA_Height, 256,
|
||||
SA_Depth, 6, /* 6 planes → EHB mode */
|
||||
SA_DisplayID, EXTRAHALFBRITE_KEY,
|
||||
TAG_DONE);
|
||||
|
||||
/* Set the 32 base colours: */
|
||||
ULONG colours32[32 * 3 + 2];
|
||||
colours32[0] = 32 << 16; /* count = 32, first = 0 */
|
||||
/* ... fill RGB values ... */
|
||||
colours32[32 * 3 + 1] = 0; /* terminator */
|
||||
LoadRGB32(&scr->ViewPort, colours32);
|
||||
|
||||
/* 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. */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HAM — Hold-And-Modify (OCS/ECS)
|
||||
## HAM6 — Hold-And-Modify (OCS/ECS/AGA)
|
||||
|
||||
### How It Works
|
||||
### Pixel Encoding
|
||||
|
||||
Uses **6 bitplanes**. Each pixel's 6 bits are interpreted as:
|
||||
Uses **6 bitplanes**. Each pixel's 6 bits are split into a 2-bit command and 4-bit data:
|
||||
|
||||
| Bits 5–4 | Meaning | Bits 3–0 |
|
||||
|---|---|---|
|
||||
| `00` | **SET** — index colour register | 4-bit palette index (0–15) |
|
||||
| `01` | **MODIFY BLUE** — hold R,G; set B | New blue nibble |
|
||||
| `10` | **MODIFY RED** — hold G,B; set R | New red nibble |
|
||||
| `11` | **MODIFY GREEN** — hold R,B; set G | New green nibble |
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "6-Bit Pixel Value"
|
||||
direction LR
|
||||
CMD["Bits 5-4<br/>(command)"] --- DATA["Bits 3-0<br/>(data)"]
|
||||
end
|
||||
|
||||
### Effective Result
|
||||
CMD --> C00["00 = SET"]
|
||||
CMD --> C01["01 = MOD BLUE"]
|
||||
CMD --> C10["10 = MOD RED"]
|
||||
CMD --> C11["11 = MOD GREEN"]
|
||||
|
||||
- Each pixel can set one of 16 base colours, OR modify one component of the previous pixel's colour
|
||||
- Theoretical maximum: **4,096 colours** on screen simultaneously
|
||||
- Practical result: colour fringing at sharp edges (each pixel depends on its left neighbour)
|
||||
C00 --> R00["Output = Palette at data<br/>R,G,B all from palette"]
|
||||
C01 --> R01["Output = prev_R, prev_G, data<br/>Only Blue changes"]
|
||||
C10 --> R10["Output = data, prev_G, prev_B<br/>Only Red changes"]
|
||||
C11 --> R11["Output = prev_R, data, prev_B<br/>Only Green changes"]
|
||||
|
||||
### OCS/ECS Limitations
|
||||
style C00 fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
style C01 fill:#bbdefb,stroke:#1565c0,color:#333
|
||||
style C10 fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
style C11 fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
- Only 16 base colours (SET mode uses 4 bits → 16 palette entries)
|
||||
- Only 4-bit component modification → 16 levels per channel
|
||||
- Total colour space: 12-bit (4096 colours)
|
||||
### How the Hardware Decodes — Per-Pixel Pipeline
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
PREV["Previous pixel<br/>R=A G=7 B=3"] --> DECODE{"Command?"}
|
||||
DECODE -->|"00 (SET)"| PAL["Palette[data]<br/>R=F G=0 B=8"]
|
||||
DECODE -->|"01 (MOD B)"| MODB["R=A G=7 B=data"]
|
||||
DECODE -->|"10 (MOD R)"| MODR["R=data G=7 B=3"]
|
||||
DECODE -->|"11 (MOD G)"| MODG["R=A G=data B=3"]
|
||||
|
||||
PAL --> NEXT["Current pixel<br/>→ becomes 'previous'<br/>for next pixel"]
|
||||
MODB --> NEXT
|
||||
MODR --> NEXT
|
||||
MODG --> NEXT
|
||||
```
|
||||
|
||||
> [!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.
|
||||
|
||||
### Practical Example — Encoding a HAM6 Scanline
|
||||
|
||||
Suppose we want to display these colours 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 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
|
||||
|
||||
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"
|
||||
```
|
||||
|
||||
### The Fringing Problem
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Desired: sharp edge"
|
||||
direction LR
|
||||
R1["R:F,0,0"] --- R2["R:F,0,0"] --- R3["R:F,0,0"] --- G1["G:0,F,0"] --- G2["G:0,F,0"] --- G3["G:0,F,0"]
|
||||
end
|
||||
|
||||
subgraph "HAM6 reality: 2-pixel fringe"
|
||||
direction LR
|
||||
H1["SET red<br/>F,0,0"] --- H2["SET red<br/>F,0,0"] --- H3["MOD_R 0<br/>0,0,0"] --- H4["MOD_G F<br/>0,F,0"] --- H5["SET green<br/>0,F,0"] --- H6["SET green<br/>0,F,0"]
|
||||
end
|
||||
|
||||
style H3 fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
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.
|
||||
|
||||
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
|
||||
- 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"]
|
||||
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 -->|"Only 1 component differs"| MOD["MODIFY command<br/>(no fringing)"]
|
||||
DECIDE -->|"2-3 components differ"| FRINGE["2-3 MODIFY sequence<br/>(fringing visible)"]
|
||||
|
||||
style FRINGE fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
style SET fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
style MOD fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
### Programming HAM6 from C
|
||||
|
||||
```c
|
||||
/* Open a HAM6 screen: */
|
||||
struct Screen *scr = OpenScreenTags(NULL,
|
||||
SA_Width, 320,
|
||||
SA_Height, 256,
|
||||
SA_Depth, 6,
|
||||
SA_DisplayID, HAM_KEY,
|
||||
TAG_DONE);
|
||||
|
||||
/* Set the 16 base palette colours: */
|
||||
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) */
|
||||
hamPalette[1] = 0xA0000000; /* R */
|
||||
hamPalette[2] = 0x70000000; /* G */
|
||||
hamPalette[3] = 0x30000000; /* B */
|
||||
/* ... fill remaining 15 entries ... */
|
||||
hamPalette[16 * 3 + 1] = 0;
|
||||
LoadRGB32(&scr->ViewPort, hamPalette);
|
||||
|
||||
/* Write pixels directly to bitplane data: */
|
||||
/* Each pixel = 6 bits across 6 bitplanes */
|
||||
struct BitMap *bm = scr->RastPort.BitMap;
|
||||
UBYTE *plane[6];
|
||||
for (int p = 0; p < 6; p++)
|
||||
plane[p] = bm->Planes[p];
|
||||
|
||||
/* Encode pixel at position x on line y: */
|
||||
void SetHAMPixel(UBYTE *plane[], int x, int y, UBYTE cmd, UBYTE data)
|
||||
{
|
||||
int byteOffset = y * 40 + (x >> 3); /* 40 bytes/line for 320px */
|
||||
int bitPos = 7 - (x & 7);
|
||||
UBYTE val = (cmd << 4) | (data & 0x0F); /* 6-bit HAM value */
|
||||
|
||||
for (int p = 0; p < 6; p++)
|
||||
{
|
||||
if (val & (1 << p))
|
||||
plane[p][byteOffset] |= (1 << bitPos);
|
||||
else
|
||||
plane[p][byteOffset] &= ~(1 << bitPos);
|
||||
}
|
||||
}
|
||||
|
||||
/* Example: SET colour 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 */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HAM8 — AGA Enhanced HAM
|
||||
|
||||
### How It Works
|
||||
### Pixel Encoding
|
||||
|
||||
Uses **8 bitplanes**:
|
||||
Uses **8 bitplanes**. Same principle, wider data:
|
||||
|
||||
| Bits 7–6 | Meaning | Bits 5–0 |
|
||||
|---|---|---|
|
||||
| `00` | **SET** — index colour register | 6-bit palette index (0–63) |
|
||||
| `01` | **MODIFY BLUE** | 6-bit blue value |
|
||||
| `10` | **MODIFY RED** | 6-bit red value |
|
||||
| `11` | **MODIFY GREEN** | 6-bit green value |
|
||||
| `00` | **SET** — palette index | 6-bit index (0–63 of 256-entry palette) |
|
||||
| `01` | **MODIFY BLUE** | 6-bit blue value (0–63) |
|
||||
| `10` | **MODIFY RED** | 6-bit red value (0–63) |
|
||||
| `11` | **MODIFY GREEN** | 6-bit green value (0–63) |
|
||||
|
||||
### Effective Result
|
||||
### Improvements over HAM6
|
||||
|
||||
- 64 base colours from the 256-entry palette
|
||||
- 6-bit component modification → 64 levels per channel
|
||||
- Total colour space: **18-bit** (262,144 colours)
|
||||
- Significantly reduced fringing compared to HAM6
|
||||
| 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) |
|
||||
| Memory per 320×256 screen | 6 × 40 × 256 = 60 KB | 8 × 40 × 256 = 80 KB |
|
||||
|
||||
### HAM8 Memory Layout
|
||||
### HAM8 Palette Setup
|
||||
|
||||
```
|
||||
8 bitplanes × 320 pixels × 256 lines = 81,920 bytes per plane × 8
|
||||
= 655,360 bytes (640 KB) for a single HAM8 320×256 display
|
||||
```c
|
||||
/* HAM8 uses 64 of the 256 AGA palette entries as base colours: */
|
||||
struct Screen *scr = OpenScreenTags(NULL,
|
||||
SA_Width, 320,
|
||||
SA_Height, 256,
|
||||
SA_Depth, 8,
|
||||
SA_DisplayID, HAM_KEY, /* HAM flag + 8 planes = HAM8 on AGA */
|
||||
TAG_DONE);
|
||||
|
||||
/* Load all 256 palette entries (HAM8 uses entries 0–63 as base): */
|
||||
ULONG palette[256 * 3 + 2];
|
||||
palette[0] = 256 << 16; /* count=256, first=0 */
|
||||
/* AGA palette is 24-bit — each component is 8 bits stored in upper byte: */
|
||||
palette[1] = 0xFF000000; /* Entry 0 red = $FF */
|
||||
palette[2] = 0x00000000; /* Entry 0 green = $00 */
|
||||
palette[3] = 0x00000000; /* Entry 0 blue = $00 */
|
||||
/* ... fill 255 more entries ... */
|
||||
palette[256 * 3 + 1] = 0;
|
||||
LoadRGB32(&scr->ViewPort, palette);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Enabling HAM
|
||||
## DMA Timing — How Bitplane Data Reaches the Display
|
||||
|
||||
### Bitplane-to-Pixel Pipeline
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CHIP["Chip RAM<br/>(Bitplane data)"] -->|"DMA fetch"| SR["Shift Registers<br/>(6 or 8 parallel)"]
|
||||
SR -->|"1 bit per plane<br/>per pixel clock"| MUX["Bitplane MUX<br/>6/8-bit value"]
|
||||
MUX --> MODE{"Display Mode?"}
|
||||
MODE -->|"Normal"| PAL["Palette Lookup<br/>32/256 entries"]
|
||||
MODE -->|"EHB"| EHB["Palette + Halve"]
|
||||
MODE -->|"HAM"| HAM["HAM Decoder<br/>(cmd + prev pixel)"]
|
||||
PAL --> DAC["RAMDAC<br/>→ Video Out"]
|
||||
EHB --> DAC
|
||||
HAM --> DAC
|
||||
|
||||
style HAM fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style EHB fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
style PAL fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
### Scanline DMA Fetch Cycle
|
||||
|
||||
The display hardware fetches bitplane data in DMA slots during each scanline:
|
||||
|
||||
```
|
||||
One scanline (~64 µs PAL):
|
||||
┌────────┬──────────────────────────────────────────┬────────┐
|
||||
│ HBlank │ Active Display Area │ HBlank │
|
||||
│ │← DMA fetch window (variable width) → │ │
|
||||
└────────┴──────────────────────────────────────────┴────────┘
|
||||
|
||||
DMA slots consumed per bitplane per lowres line:
|
||||
1 bitplane = 8 DMA words (16 bytes)
|
||||
6 bitplanes = 48 DMA words (HAM6/EHB)
|
||||
8 bitplanes = 64 DMA words (HAM8)
|
||||
```
|
||||
|
||||
### HAM Decode Pipeline (Hardware)
|
||||
|
||||
The HAM decoder operates **one pixel clock behind** the bitplane data output:
|
||||
|
||||
```
|
||||
Bitplane DMA → Bitplane shift registers → HAM decoder → Colour register → DAC → Video out
|
||||
↑
|
||||
1-pixel delay
|
||||
(needs previous pixel's colour)
|
||||
```
|
||||
|
||||
For FPGA implementation, the HAM decoder is a simple combinational circuit:
|
||||
|
||||
```verilog
|
||||
// HAM6 decoder (simplified)
|
||||
always @(*) begin
|
||||
case (pixel_data[5:4])
|
||||
2'b00: begin // SET
|
||||
out_r = palette[pixel_data[3:0]][11:8];
|
||||
out_g = palette[pixel_data[3:0]][7:4];
|
||||
out_b = palette[pixel_data[3:0]][3:0];
|
||||
end
|
||||
2'b01: begin // MODIFY BLUE
|
||||
out_r = prev_r;
|
||||
out_g = prev_g;
|
||||
out_b = pixel_data[3:0];
|
||||
end
|
||||
2'b10: begin // MODIFY RED
|
||||
out_r = pixel_data[3:0];
|
||||
out_g = prev_g;
|
||||
out_b = prev_b;
|
||||
end
|
||||
2'b11: begin // MODIFY GREEN
|
||||
out_r = prev_r;
|
||||
out_g = pixel_data[3:0];
|
||||
out_b = prev_b;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Standard Palette Modes — For Comparison
|
||||
|
||||
### Setting Palette Colours (Non-HAM)
|
||||
|
||||
```c
|
||||
/* Via ViewPort: */
|
||||
vp->Modes |= HAM; /* HAMF flag in modes */
|
||||
/* OS 3.0+ — 24-bit precision (AGA): */
|
||||
ULONG colours[3 * 3 + 2]; /* 3 colours */
|
||||
colours[0] = 3 << 16; /* count=3, first entry=0 */
|
||||
/* Entry 0: black */
|
||||
colours[1] = 0x00000000; colours[2] = 0x00000000; colours[3] = 0x00000000;
|
||||
/* Entry 1: bright red */
|
||||
colours[4] = 0xFF000000; colours[5] = 0x00000000; colours[6] = 0x00000000;
|
||||
/* Entry 2: pure blue */
|
||||
colours[7] = 0x00000000; colours[8] = 0x00000000; colours[9] = 0xFF000000;
|
||||
colours[10] = 0; /* terminator */
|
||||
LoadRGB32(vp, colours);
|
||||
|
||||
/* Via SA_ tags (Intuition screen): */
|
||||
struct TagItem scrTags[] = {
|
||||
{ SA_Width, 320 },
|
||||
{ SA_Height, 256 },
|
||||
{ SA_Depth, 6 }, /* 6 for HAM6, 8 for HAM8 */
|
||||
{ SA_DisplayID, HAM_KEY }, /* or SUPER_KEY|HAM for Super-HiRes HAM */
|
||||
{ TAG_DONE, 0 }
|
||||
};
|
||||
/* OCS/ECS — 12-bit precision: */
|
||||
UWORD oldPalette[] = { 0x000, 0xF00, 0x00F }; /* 4 bits per channel */
|
||||
LoadRGB4(vp, oldPalette, 3);
|
||||
|
||||
/* Direct hardware (bypass OS — for demos/games): */
|
||||
custom->color[0] = 0x000; /* $DFF180: COLOR00 (background) */
|
||||
custom->color[1] = 0xF00; /* $DFF182: COLOR01 */
|
||||
custom->color[2] = 0x00F; /* $DFF184: COLOR02 */
|
||||
/* AGA: extra bits via BPLCON3 bank select */
|
||||
```
|
||||
|
||||
### Colour Cycling (Palette Animation)
|
||||
|
||||
```c
|
||||
/* Rotate palette entries for animation — common demo/game technique: */
|
||||
void CyclePalette(struct ViewPort *vp, int first, int last)
|
||||
{
|
||||
ULONG saved[3]; /* save last entry */
|
||||
GetRGB32(vp->ColorMap, last, 1, saved);
|
||||
|
||||
/* Shift all entries up by one: */
|
||||
for (int i = last; i > first; i--)
|
||||
{
|
||||
ULONG rgb[3];
|
||||
GetRGB32(vp->ColorMap, i - 1, 1, rgb);
|
||||
SetRGB32(vp, i, rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
|
||||
/* Wrap last to first: */
|
||||
SetRGB32(vp, first, saved[0], saved[1], saved[2]);
|
||||
|
||||
/* Force display update: */
|
||||
MakeVPort(GfxBase->ActiView, vp);
|
||||
MrgCop(GfxBase->ActiView);
|
||||
LoadView(GfxBase->ActiView);
|
||||
}
|
||||
```
|
||||
|
||||
> [!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.
|
||||
|
||||
---
|
||||
|
||||
## Comparison Table
|
||||
|
||||
| Feature | EHB | HAM6 | HAM8 |
|
||||
|---|---|---|---|
|
||||
| Bitplanes | 6 | 6 | 8 |
|
||||
| Chipset | OCS/ECS/AGA | OCS/ECS/AGA | AGA only |
|
||||
| Base palette | 32 | 16 | 64 |
|
||||
| Max on-screen colours | 64 | 4,096 | 262,144 |
|
||||
| Colour depth | 12-bit | 12-bit | 24-bit (via 18-bit HAM) |
|
||||
| Fringing | None | Significant | Mild |
|
||||
| Good for | GUI, sprites | Photos, static art | Photos, video stills |
|
||||
| Bad for | — | Animation, scrolling | Memory-hungry |
|
||||
| Feature | Normal (5-plane) | EHB | HAM6 | HAM8 |
|
||||
|---|---|---|---|---|
|
||||
| Bitplanes | 5 | 6 | 6 | 8 |
|
||||
| Chipset | OCS/ECS/AGA | OCS/ECS/AGA | OCS/ECS/AGA | AGA only |
|
||||
| Programmable colours | 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 |
|
||||
| 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 |
|
||||
| DMA words/line (lores) | 40 | 48 | 48 | 64 |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- HRM: *Display modes* chapter
|
||||
- HRM: *Display modes* chapter — HAM decode logic
|
||||
- NDK39: `graphics/displayinfo.h` — `HAM_KEY`, `EXTRAHALFBRITE_KEY`
|
||||
- See also: [display_modes.md](display_modes.md) — ModeID system and chipset comparison
|
||||
- See also: [copper_programming.md](copper_programming.md) — Copper-driven palette tricks
|
||||
- See also: [bitmap.md](bitmap.md) — bitplane memory layout
|
||||
|
|
|
|||
|
|
@ -4,7 +4,31 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`RastPort` is the primary drawing context in AmigaOS. All graphics primitives (pixel, line, rectangle, polygon, text) operate through a `RastPort`, which links a `BitMap`, drawing pen colours, pattern, font, and 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 colours, 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.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "RastPort — Drawing Context"
|
||||
RP["struct RastPort"]
|
||||
RP --> BM["BitMap<br/>(pixel storage)"]
|
||||
RP --> PEN["Pens<br/>FgPen, BgPen, AOlPen"]
|
||||
RP --> DM["DrawMode<br/>JAM1/JAM2/COMPLEMENT"]
|
||||
RP --> FONT["TextFont<br/>(current font)"]
|
||||
RP --> PAT["AreaPtrn<br/>(fill pattern)"]
|
||||
RP --> LAYER["Layer<br/>(clipping region)"]
|
||||
RP --> CP["cp_x, cp_y<br/>(cursor position)"]
|
||||
end
|
||||
|
||||
APP["Application Code"] -->|"SetAPen, Move,<br/>Draw, RectFill,<br/>Text, ClipBlit"| RP
|
||||
RP -->|"Drawing operations"| BM
|
||||
LAYER -->|"Clips to visible region"| BM
|
||||
|
||||
style RP fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style BM fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
style LAYER fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -49,64 +73,331 @@ struct RastPort {
|
|||
|
||||
## Draw Modes
|
||||
|
||||
```c
|
||||
#define JAM1 0 /* draw FgPen only; background transparent */
|
||||
#define JAM2 1 /* draw FgPen and BgPen (opaque) */
|
||||
#define COMPLEMENT 2 /* XOR with existing pixels */
|
||||
#define INVERSVID 4 /* invert video (swap fg/bg for text) */
|
||||
The draw mode controls how new pixels combine with existing content:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "JAM1 — Transparent"
|
||||
J1BG["Background<br/>unchanged"] --- J1FG["Foreground<br/>drawn with FgPen"]
|
||||
end
|
||||
|
||||
subgraph "JAM2 — Opaque"
|
||||
J2BG["Background<br/>drawn with BgPen"] --- J2FG["Foreground<br/>drawn with FgPen"]
|
||||
end
|
||||
|
||||
subgraph "COMPLEMENT — XOR"
|
||||
XBG["All pixels<br/>XOR with existing"]
|
||||
end
|
||||
```
|
||||
|
||||
```c
|
||||
#define JAM1 0 /* draw FgPen only; background pixels are NOT touched */
|
||||
#define JAM2 1 /* draw FgPen AND BgPen — fully opaque */
|
||||
#define COMPLEMENT 2 /* XOR all drawn pixels with existing content */
|
||||
#define INVERSVID 4 /* swap foreground/background (for text inverse) */
|
||||
```
|
||||
|
||||
| Mode | Text Example | Fill Example | Use Case |
|
||||
|---|---|---|---|
|
||||
| `JAM1` | Characters drawn, background shows through | Solid fill with FgPen | Transparent overlays, labels on images |
|
||||
| `JAM2` | Characters + background box drawn | Same as JAM1 for fills | Opaque text on busy backgrounds |
|
||||
| `COMPLEMENT` | XOR of text pixels with screen | XOR fill | Rubber-band selection, dragging cursors |
|
||||
| `JAM1 | INVERSVID` | Background drawn with FgPen, chars transparent | — | Highlighted/selected text |
|
||||
|
||||
---
|
||||
|
||||
## Drawing Primitives
|
||||
|
||||
```c
|
||||
/* Set pen colour: */
|
||||
SetAPen(rp, 1); /* foreground = colour 1 */
|
||||
SetBPen(rp, 0); /* background = colour 0 */
|
||||
SetDrMd(rp, JAM1); /* transparent background */
|
||||
### Pen and Position Setup
|
||||
|
||||
/* Move to position: */
|
||||
```c
|
||||
/* Set pen colours: */
|
||||
SetAPen(rp, 1); /* foreground = colour register 1 */
|
||||
SetBPen(rp, 0); /* background = colour register 0 */
|
||||
SetDrMd(rp, JAM1); /* transparent background mode */
|
||||
|
||||
/* OS 3.0+ — use named pen for correct Workbench colours: */
|
||||
SetAPen(rp, screen->RastPort.BitMap->Depth > 1 ?
|
||||
ObtainBestPen(screen->ViewPort.ColorMap,
|
||||
0xFF000000, 0x00000000, 0x00000000, /* red */
|
||||
OBP_Precision, PRECISION_GUI,
|
||||
TAG_DONE) : 1);
|
||||
```
|
||||
|
||||
### Lines and Pixels
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
M["Move(rp, 10, 20)"] -->|"sets cp_x, cp_y"| D1["Draw(rp, 100, 20)"]
|
||||
D1 -->|"draws line,<br/>updates cp"| D2["Draw(rp, 100, 80)"]
|
||||
D2 -->|"draws line,<br/>updates cp"| D3["Draw(rp, 10, 80)"]
|
||||
D3 -->|"draws line,<br/>updates cp"| D4["Draw(rp, 10, 20)"]
|
||||
|
||||
style M fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
```c
|
||||
/* Move cursor (no drawing): */
|
||||
Move(rp, 100, 50);
|
||||
|
||||
/* Draw line from current position to (x,y): */
|
||||
/* Draw line from current position to (x,y), update cp: */
|
||||
Draw(rp, 200, 100);
|
||||
/* cp is now (200, 100) — next Draw continues from here */
|
||||
|
||||
/* Write pixel: */
|
||||
/* Draw a connected polygon: */
|
||||
Move(rp, 10, 10);
|
||||
Draw(rp, 100, 10); /* top edge */
|
||||
Draw(rp, 100, 80); /* right edge */
|
||||
Draw(rp, 10, 80); /* bottom edge */
|
||||
Draw(rp, 10, 10); /* close: left edge */
|
||||
|
||||
/* Single pixel: */
|
||||
WritePixel(rp, 160, 120);
|
||||
|
||||
/* Read pixel: */
|
||||
LONG colour = ReadPixel(rp, 160, 120);
|
||||
|
||||
/* Filled rectangle: */
|
||||
RectFill(rp, 10, 10, 100, 50);
|
||||
/* Dashed lines: */
|
||||
SetDrPt(rp, 0xF0F0); /* 16-bit pattern: 1111000011110000 */
|
||||
Draw(rp, 200, 100); /* draws dashed line */
|
||||
SetDrPt(rp, 0xFFFF); /* restore solid */
|
||||
```
|
||||
|
||||
/* Draw text at current position: */
|
||||
Move(rp, 20, 30);
|
||||
Text(rp, "Hello", 5);
|
||||
### Rectangles and Fills
|
||||
|
||||
/* Blitter copy between RastPorts: */
|
||||
ClipBlit(srcRP, sx, sy, dstRP, dx, dy, w, h, minterm);
|
||||
```c
|
||||
/* Solid filled rectangle (uses FgPen + DrawMode): */
|
||||
SetAPen(rp, 3);
|
||||
RectFill(rp, 10, 10, 100, 50); /* x1,y1 to x2,y2 inclusive */
|
||||
|
||||
/* Erase to background (clear a region): */
|
||||
SetAPen(rp, 0);
|
||||
SetDrMd(rp, JAM1);
|
||||
RectFill(rp, 0, 0, 319, 255);
|
||||
|
||||
/* Scroll a region (with clear): */
|
||||
ScrollRaster(rp, dx, dy, x1, y1, x2, y2);
|
||||
/* Shifts content by (dx,dy); exposed area cleared to BgPen */
|
||||
```
|
||||
|
||||
### Text Rendering
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
ATTR["TextAttr<br/>(name, size)"] -->|"OpenFont /<br/>OpenDiskFont"| TF["TextFont"]
|
||||
TF -->|"SetFont(rp, font)"| RP["RastPort"]
|
||||
RP -->|"Move + Text"| OUT["Rendered text<br/>on bitmap"]
|
||||
```
|
||||
|
||||
```c
|
||||
/* Set font: */
|
||||
struct TextAttr ta = {"topaz.font", 8, 0, FPF_ROMFONT};
|
||||
struct TextFont *font = OpenFont(&ta);
|
||||
SetFont(rp, font);
|
||||
|
||||
/* Render text at position: */
|
||||
Move(rp, 20, 30); /* baseline position */
|
||||
Text(rp, "Hello Amiga", 11);
|
||||
|
||||
/* Measure text width before rendering (for alignment): */
|
||||
UWORD width = TextLength(rp, "Hello Amiga", 11);
|
||||
/* Right-align: */
|
||||
Move(rp, screenWidth - width - 10, 30);
|
||||
Text(rp, "Hello Amiga", 11);
|
||||
|
||||
/* Bold/italic (algorithmic): */
|
||||
UWORD supported = AskSoftStyle(rp);
|
||||
SetSoftStyle(rp, FSF_BOLD | FSF_ITALIC, supported);
|
||||
Text(rp, "Bold Italic", 11);
|
||||
SetSoftStyle(rp, 0, supported); /* restore */
|
||||
```
|
||||
|
||||
### Area Fills (Polygons)
|
||||
|
||||
```c
|
||||
/* Area fills require setup: TmpRas + AreaInfo */
|
||||
UBYTE areaBuffer[5 * 5]; /* 5 vertices × 5 bytes each */
|
||||
struct AreaInfo areaInfo;
|
||||
InitArea(&areaInfo, areaBuffer, 5);
|
||||
rp->AreaInfo = &areaInfo;
|
||||
|
||||
PLANEPTR tmpRasData = AllocRaster(320, 256);
|
||||
struct TmpRas tmpRas;
|
||||
InitTmpRas(&tmpRas, tmpRasData, RASSIZE(320, 256));
|
||||
rp->TmpRas = &tmpRas;
|
||||
|
||||
/* Draw a filled triangle: */
|
||||
AreaMove(rp, 100, 10); /* first vertex */
|
||||
AreaDraw(rp, 200, 180); /* second vertex */
|
||||
AreaDraw(rp, 20, 180); /* third vertex */
|
||||
AreaEnd(rp); /* fill and close */
|
||||
|
||||
/* Cleanup: */
|
||||
FreeRaster(tmpRasData, 320, 256);
|
||||
```
|
||||
|
||||
### Flood Fill
|
||||
|
||||
```c
|
||||
/* 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 */
|
||||
```
|
||||
|
||||
### Blitting (Block Transfer)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
SRC["Source RastPort<br/>(or BitMap)"] -->|"ClipBlit /<br/>BltBitMapRastPort"| DST["Destination RastPort"]
|
||||
|
||||
subgraph "Minterm Controls"
|
||||
MT["0xC0 = copy<br/>0x30 = invert copy<br/>0x50 = XOR<br/>0x00 = clear"]
|
||||
end
|
||||
MT --> DST
|
||||
```
|
||||
|
||||
```c
|
||||
/* Copy region between RastPorts (respects clipping): */
|
||||
ClipBlit(srcRP, sx, sy, /* source position */
|
||||
dstRP, dx, dy, /* destination position */
|
||||
width, height,
|
||||
0xC0); /* minterm: straight copy */
|
||||
|
||||
/* Copy from BitMap to RastPort: */
|
||||
BltBitMapRastPort(srcBM, sx, sy,
|
||||
dstRP, dx, dy,
|
||||
width, height,
|
||||
0xC0);
|
||||
|
||||
/* Common minterms: */
|
||||
/* 0xC0 = A (copy source) */
|
||||
/* 0x30 = NOT A (invert source) */
|
||||
/* 0x50 = A XOR B (toggle) */
|
||||
/* 0x00 = clear destination */
|
||||
/* 0xFF = set all bits */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layers
|
||||
## Layers — Window Clipping
|
||||
|
||||
When `rp->Layer != NULL`, all drawing is clipped to the layer's bounds and damage regions. Layers are managed by `layers.library`:
|
||||
When `rp->Layer != NULL`, all drawing is automatically clipped to the layer's visible region. Intuition creates layers for every window — this is how overlapping windows work without drawing over each other.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Screen"
|
||||
subgraph "Window A (front)"
|
||||
LA["Layer A<br/>(fully visible)"]
|
||||
end
|
||||
subgraph "Window B (behind)"
|
||||
LB["Layer B<br/>(partially obscured)"]
|
||||
end
|
||||
subgraph "Window C (behind both)"
|
||||
LC["Layer C<br/>(mostly obscured)"]
|
||||
end
|
||||
end
|
||||
|
||||
DRAW["Draw(windowB->RPort, ...)"] --> LB
|
||||
LB -->|"ClipRects exclude<br/>obscured regions"| BM["BitMap<br/>(only visible<br/>portions drawn)"]
|
||||
|
||||
style LA fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
style LB fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style LC fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
```
|
||||
|
||||
Each layer maintains a list of **ClipRects** — rectangles defining which parts of the layer are visible. When you draw to a partially obscured window, `layers.library` breaks your drawing operation into multiple clipped sub-draws, one per visible ClipRect.
|
||||
|
||||
```c
|
||||
/* Direct layer creation (Intuition does this for windows): */
|
||||
struct Layer_Info *li = NewLayerInfo();
|
||||
struct Layer *layer = CreateUpfrontLayer(li, bm, x1, y1, x2, y2, flags, NULL);
|
||||
rp = layer->rp; /* use this RastPort for drawing */
|
||||
/* ... draw ... */
|
||||
struct Layer *layer = CreateUpfrontLayer(li, bm,
|
||||
x1, y1, x2, y2,
|
||||
LAYERSIMPLE, /* or LAYERSMART, LAYERSUPER */
|
||||
NULL);
|
||||
struct RastPort *rp = layer->rp; /* use this for drawing */
|
||||
|
||||
/* LAYERSIMPLE — no backing store; app must redraw damaged areas */
|
||||
/* LAYERSMART — automatic backing store; OS handles redraw */
|
||||
/* LAYERSUPER — super bitmap; full offscreen buffer */
|
||||
|
||||
/* Layer clipping is automatic — Draw, RectFill, Text all respect it */
|
||||
Draw(rp, 200, 100); /* clipped to visible portion of layer */
|
||||
|
||||
/* Cleanup: */
|
||||
DeleteLayer(0, layer);
|
||||
DisposeLayerInfo(li);
|
||||
```
|
||||
|
||||
### Layer Types
|
||||
|
||||
| Type | Flag | Backing Store | Damage Handling | Memory |
|
||||
|---|---|---|---|---|
|
||||
| Simple | `LAYERSIMPLE` | None | App receives `IDCMP_REFRESHWINDOW` and must redraw | Minimal |
|
||||
| Smart | `LAYERSMART` | Auto-saved obscured regions | OS restores automatically | Moderate |
|
||||
| Super | `LAYERSUPER` | Full off-screen bitmap | Full bitmap always valid | High |
|
||||
|
||||
> [!TIP]
|
||||
> **`WFLG_SIMPLE_REFRESH`** windows use the least memory but require the most application code (you must handle `IDCMP_REFRESHWINDOW`). **`WFLG_SMART_REFRESH`** is the default for most applications — Intuition saves/restores obscured regions automatically.
|
||||
|
||||
---
|
||||
|
||||
## Patterns
|
||||
|
||||
### Line Patterns
|
||||
|
||||
```c
|
||||
/* 16-bit repeating pattern for dashed lines: */
|
||||
SetDrPt(rp, 0xFF00); /* ████████░░░░░░░░ — long dash */
|
||||
SetDrPt(rp, 0xF0F0); /* ████░░░░████░░░░ — medium dash */
|
||||
SetDrPt(rp, 0xAAAA); /* █░█░█░█░█░█░█░█░ — dotted */
|
||||
SetDrPt(rp, 0xFCFC); /* ██████░░██████░░ — dash-dot */
|
||||
```
|
||||
|
||||
### Area Fill Patterns
|
||||
|
||||
```c
|
||||
/* Area patterns are powers-of-2 height, 16 bits wide: */
|
||||
UWORD checkerPattern[] = { 0x5555, 0xAAAA }; /* 2-line checkerboard */
|
||||
rp->AreaPtrn = checkerPattern;
|
||||
rp->AreaPtSz = 1; /* log2(2) = 1 */
|
||||
|
||||
/* Now RectFill uses the pattern instead of solid fill: */
|
||||
RectFill(rp, 10, 10, 100, 80);
|
||||
|
||||
/* Multiplane patterns (one set per bitplane): */
|
||||
UWORD brickPattern[] = {
|
||||
/* plane 0: */ 0xFFFF, 0x8080, 0xFFFF, 0x0808,
|
||||
/* plane 1: */ 0xFFFF, 0x8080, 0xFFFF, 0x0808
|
||||
};
|
||||
rp->AreaPtrn = brickPattern;
|
||||
rp->AreaPtSz = 2; /* log2(4 lines) = 2 */
|
||||
rp->Mask |= 0x02; /* enable plane 1 pattern */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plane Mask — Selective Bitplane Drawing
|
||||
|
||||
```c
|
||||
/* Mask controls which bitplanes are affected by drawing: */
|
||||
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: */
|
||||
rp->Mask = 0x04; /* only plane 2 */
|
||||
SetAPen(rp, 4); /* colour index with bit 2 set */
|
||||
RectFill(rp, 0, 0, 319, 255);
|
||||
rp->Mask = 0xFF; /* restore */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `graphics/rastport.h`, `graphics/gfxmacros.h`
|
||||
- ADCD 2.1: `SetAPen`, `Move`, `Draw`, `Text`, `RectFill`, `ClipBlit`
|
||||
- See also: [bitmap.md](bitmap.md) — BitMap structure and allocation
|
||||
- See also: [layers.md](../../11_libraries/layers.md) — layers.library detailed reference
|
||||
- See also: [text_fonts.md](text_fonts.md) — font loading and rendering
|
||||
- See also: [blitter.md](blitter.md) — hardware Blitter used by BltBitMap
|
||||
|
|
|
|||
|
|
@ -1,102 +1,305 @@
|
|||
[← Home](../README.md) · [Graphics](README.md)
|
||||
|
||||
# Hardware Sprites — SimpleSprite, MoveSprite
|
||||
# Hardware Sprites — DMA Engine, Multiplexing, and Tricks
|
||||
|
||||
## Overview
|
||||
|
||||
The Amiga has **8 hardware sprites**, each 16 pixels wide with 3 colours + transparent. Sprites are DMA-driven — the Copper sets their pointers each frame and the display hardware renders them with zero CPU overhead.
|
||||
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.
|
||||
|
||||
Sprite 0 is reserved by Intuition for the **mouse pointer**. Sprites 1–7 are available for application use.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Chip RAM"
|
||||
SD0["Sprite 0 data<br/>(mouse pointer)"]
|
||||
SD1["Sprite 1 data"]
|
||||
SD2["Sprite 2-7 data"]
|
||||
end
|
||||
|
||||
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"]
|
||||
end
|
||||
|
||||
SD0 --> DMA
|
||||
SD1 --> DMA
|
||||
SD2 --> DMA
|
||||
|
||||
subgraph "Copper"
|
||||
COP["Copper List"] -->|"Set SPRxPTH/L<br/>every frame"| DMA
|
||||
end
|
||||
|
||||
style DMA fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style COP fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sprite DMA Data Format
|
||||
|
||||
Each sprite scanline is two words (4 bytes):
|
||||
Each sprite is stored as a contiguous block in Chip RAM:
|
||||
|
||||
```
|
||||
Word 0 (DATA): bits 15–0 = pixel colour bit 0 for this line
|
||||
Word 1 (DATB): bits 15–0 = pixel colour bit 1 for this line
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Header Word 0: VSTART/HSTART │ Position control
|
||||
│ Header Word 1: VSTOP/Control │
|
||||
├──────────────────────────────────────────┤
|
||||
│ Line 0: DATA word (bit 0 of each pixel) │ ← 16 pixels per line
|
||||
│ Line 0: DATB word (bit 1 of each pixel) │
|
||||
├──────────────────────────────────────────┤
|
||||
│ Line 1: DATA word │
|
||||
│ Line 1: DATB word │
|
||||
├──────────────────────────────────────────┤
|
||||
│ ...repeat for each line... │
|
||||
├──────────────────────────────────────────┤
|
||||
│ Terminator: 0x0000 │ End marker
|
||||
│ Terminator: 0x0000 │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Pixel Colour Encoding
|
||||
|
||||
```
|
||||
Pixel colour = (DATB_bit << 1) | DATA_bit
|
||||
00 = transparent
|
||||
01 = colour 1 (from sprite palette)
|
||||
10 = colour 2
|
||||
11 = colour 3
|
||||
|
||||
00 = transparent (playfield shows through)
|
||||
01 = sprite colour 1
|
||||
10 = sprite colour 2
|
||||
11 = sprite colour 3
|
||||
```
|
||||
|
||||
### Sprite Header
|
||||
### Header Bit Layout
|
||||
|
||||
```
|
||||
WORD 0: VSTART (vertical start position) + HSTART high bits
|
||||
WORD 1: VSTOP (vertical stop position) + control bits
|
||||
Word 0 (SPRxPOS):
|
||||
Bits 15–8: VSTART[7:0] (vertical start line, low 8 bits)
|
||||
Bits 7–0: HSTART[8:1] (horizontal start, in low-res pixels ÷ 2)
|
||||
|
||||
Word 1 (SPRxCTL):
|
||||
Bits 15–8: VSTOP[7:0] (vertical stop line, low 8 bits)
|
||||
Bit 7: unused
|
||||
Bit 6: unused
|
||||
Bit 5: unused
|
||||
Bit 4: unused
|
||||
Bit 3: VSTART[8] (bit 8 of start — for lines > 255)
|
||||
Bit 2: VSTOP[8] (bit 8 of stop)
|
||||
Bit 1: HSTART[0] (low bit of horizontal position)
|
||||
Bit 0: ATTACH (1 = attached to previous sprite)
|
||||
```
|
||||
|
||||
### End marker
|
||||
```
|
||||
WORD 0: 0x0000
|
||||
WORD 1: 0x0000
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> **Horizontal position is in low-res pixel units ÷ 2** — a sprite can only be positioned on even low-res pixel boundaries. In hires/superhires modes, this means sprites have coarser horizontal positioning than playfield pixels.
|
||||
|
||||
---
|
||||
|
||||
## Sprite Colour Palette
|
||||
|
||||
| Sprite pair | Colour registers | Custom addresses |
|
||||
|---|---|---|
|
||||
| 0–1 | `COLOR17`–`COLOR19` | `$DFF1A2`–`$DFF1A6` |
|
||||
| 2–3 | `COLOR21`–`COLOR23` | `$DFF1AA`–`$DFF1AE` |
|
||||
| 4–5 | `COLOR25`–`COLOR27` | `$DFF1B2`–`$DFF1B6` |
|
||||
| 6–7 | `COLOR29`–`COLOR31` | `$DFF1BA`–`$DFF1BE` |
|
||||
Each pair of sprites shares 3 colour registers (colour 0 = transparent for all):
|
||||
|
||||
Sprite pairs can be **attached** to form a single 15-colour sprite (using both sets of 2 bits = 4 bits per pixel).
|
||||
| Sprite Pair | Colour Registers | Custom Addresses | Notes |
|
||||
|---|---|---|---|
|
||||
| 0–1 | `COLOR17`–`COLOR19` | `$DFF1A2`–`$DFF1A6` | Pair with mouse pointer |
|
||||
| 2–3 | `COLOR21`–`COLOR23` | `$DFF1AA`–`$DFF1AE` | |
|
||||
| 4–5 | `COLOR25`–`COLOR27` | `$DFF1B2`–`$DFF1B6` | |
|
||||
| 6–7 | `COLOR29`–`COLOR31` | `$DFF1BA`–`$DFF1BE` | |
|
||||
|
||||
```c
|
||||
/* Set sprite 0-1 colours directly: */
|
||||
custom->color[17] = 0xF00; /* red */
|
||||
custom->color[18] = 0x0F0; /* green */
|
||||
custom->color[19] = 0xFFF; /* white */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## OS-Level Sprite API
|
||||
## Attached Sprites — 15 Colours
|
||||
|
||||
Two sprites from the same pair can be **attached** to form a single 15-colour (+ transparent) sprite:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Normal (2 independent sprites)"
|
||||
S0["Sprite 0<br/>3 colours"] --- S1["Sprite 1<br/>3 colours"]
|
||||
end
|
||||
|
||||
subgraph "Attached (1 wide-colour sprite)"
|
||||
SA["Sprites 0+1 attached<br/>4 bits per pixel<br/>15 colours + 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.
|
||||
|
||||
```c
|
||||
/* graphics.library */
|
||||
struct SimpleSprite ss;
|
||||
WORD sprnum;
|
||||
|
||||
/* Obtain a free sprite: */
|
||||
sprnum = GetSprite(&ss, -1); /* -1 = any available */
|
||||
if (sprnum >= 0) {
|
||||
ss.x = 100;
|
||||
ss.y = 50;
|
||||
ss.height = 16;
|
||||
|
||||
/* Move sprite to position: */
|
||||
MoveSprite(NULL, &ss, 100, 50);
|
||||
|
||||
/* Set sprite image data: */
|
||||
ChangeSprite(NULL, &ss, spriteData);
|
||||
|
||||
/* Release: */
|
||||
FreeSprite(sprnum);
|
||||
}
|
||||
/* Enable attachment: set bit 0 of odd sprite's CTL word */
|
||||
oddSpriteData[1] |= 0x0001; /* ATTACH bit */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sprite Multiplexing — More Than 8 Sprites
|
||||
|
||||
The hardware only supports 8 simultaneous sprites, but demos and games use **multiplexing** to display many more by reusing sprite channels on different scanlines:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Screen (top to bottom)"
|
||||
Z1["Lines 0-50:<br/>Sprite 2 = Enemy A"]
|
||||
Z2["Lines 60-110:<br/>Sprite 2 = Enemy B"]
|
||||
Z3["Lines 120-170:<br/>Sprite 2 = Enemy C"]
|
||||
Z4["Lines 180-230:<br/>Sprite 2 = Enemy D"]
|
||||
end
|
||||
|
||||
COP["Copper List"] -->|"WAIT line 0<br/>Set SPR2PT → Enemy A"| Z1
|
||||
COP -->|"WAIT line 55<br/>Set SPR2PT → Enemy B"| Z2
|
||||
COP -->|"WAIT line 115<br/>Set SPR2PT → Enemy C"| Z3
|
||||
COP -->|"WAIT line 175<br/>Set SPR2PT → Enemy D"| Z4
|
||||
|
||||
style COP fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
```
|
||||
|
||||
The Copper waits for a line after one sprite ends, then reprograms the sprite pointer register for the next object. This gives effectively **unlimited sprites** as long as they don't overlap vertically on the same channel.
|
||||
|
||||
### Multiplexing Rules
|
||||
|
||||
| Rule | Explanation |
|
||||
|---|---|
|
||||
| No vertical overlap on same channel | Two sprites using the same DMA channel cannot appear on the same scanline |
|
||||
| 1-line gap required | After a sprite ends (VSTOP), the DMA channel needs at least 1 blank line before starting the next |
|
||||
| Copper must be fast enough | Copper WAIT + MOVE takes 2 DMA cycles; pointer reload = 2 MOVEs (PTH+PTL) |
|
||||
| Max per scanline: 8 | Absolute hardware limit — 8 DMA channels, one fetch per channel per line |
|
||||
|
||||
---
|
||||
|
||||
## AGA Sprite Enhancements
|
||||
|
||||
| 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 |
|
||||
| Horizontal resolution | Low-res ÷ 2 | Same (unchanged) |
|
||||
|
||||
```c
|
||||
/* AGA: wider sprites via FMODE register */
|
||||
custom->fmode = 3; /* 4× fetch — sprites become 64 pixels wide */
|
||||
/* WARNING: this also affects bitplane fetch! */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sprite-Playfield Priority
|
||||
|
||||
Sprites interact with playfields via the **priority control register** (`BPLCON2`):
|
||||
|
||||
```c
|
||||
/* BPLCON2 ($DFF104) priority bits: */
|
||||
/* PF2P2-PF2P0: playfield 2 priority vs sprites */
|
||||
/* PF1P2-PF1P0: playfield 1 priority vs sprites */
|
||||
|
||||
/* Priority order (front to back): */
|
||||
/* SP01 > SP23 > SP45 > SP67 > PF1 > PF2 (default) */
|
||||
|
||||
/* Make sprites appear behind playfield 1: */
|
||||
custom->bplcon2 = 0x0024; /* PF1 in front of all sprites */
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Default Priority (front → back)"
|
||||
direction LR
|
||||
S01["Sprites 0-1"] --- S23["Sprites 2-3"] --- S45["Sprites 4-5"] --- S67["Sprites 6-7"] --- PF1["Playfield 1"] --- PF2["Playfield 2"]
|
||||
end
|
||||
|
||||
style S01 fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
style PF1 fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
style PF2 fill:#bbdefb,stroke:#1565c0,color:#333
|
||||
```
|
||||
|
||||
The priority can be configured so that sprites appear **between** playfield 1 and playfield 2 — creating the illusion of depth (e.g., a character walking behind foreground objects but in front of the background).
|
||||
|
||||
---
|
||||
|
||||
## Sprite Pointer Registers
|
||||
|
||||
| Register | Address | Sprite |
|
||||
|---|---|---|
|
||||
| `SPR0PTH/L` | `$DFF120–$DFF122` | Sprite 0 |
|
||||
| `SPR1PTH/L` | `$DFF124–$DFF126` | Sprite 1 |
|
||||
| `SPR2PTH/L` | `$DFF128–$DFF12A` | Sprite 2 |
|
||||
| `SPR3PTH/L` | `$DFF12C–$DFF12E` | Sprite 3 |
|
||||
| `SPR4PTH/L` | `$DFF130–$DFF132` | Sprite 4 |
|
||||
| `SPR5PTH/L` | `$DFF134–$DFF136` | Sprite 5 |
|
||||
| `SPR6PTH/L` | `$DFF138–$DFF13A` | Sprite 6 |
|
||||
| `SPR7PTH/L` | `$DFF13C–$DFF13E` | Sprite 7 |
|
||||
| `SPR0PTH/L` | `$DFF120–$DFF123` | Sprite 0 (mouse pointer) |
|
||||
| `SPR1PTH/L` | `$DFF124–$DFF127` | Sprite 1 |
|
||||
| `SPR2PTH/L` | `$DFF128–$DFF12B` | Sprite 2 |
|
||||
| `SPR3PTH/L` | `$DFF12C–$DFF12F` | Sprite 3 |
|
||||
| `SPR4PTH/L` | `$DFF130–$DFF133` | Sprite 4 |
|
||||
| `SPR5PTH/L` | `$DFF134–$DFF137` | Sprite 5 |
|
||||
| `SPR6PTH/L` | `$DFF138–$DFF13B` | Sprite 6 |
|
||||
| `SPR7PTH/L` | `$DFF13C–$DFF13F` | Sprite 7 |
|
||||
|
||||
These must be set every frame — typically via the Copper list.
|
||||
These must be set **every frame** — typically by the Copper list during vertical blank. If not set, the sprite DMA will fetch from wherever the pointer was left, producing garbage.
|
||||
|
||||
---
|
||||
|
||||
## OS-Level Sprite API
|
||||
|
||||
```c
|
||||
/* graphics.library — system-friendly sprite access */
|
||||
struct SimpleSprite ss;
|
||||
WORD sprnum;
|
||||
|
||||
/* Obtain a free sprite (Intuition reserves sprite 0 for the pointer): */
|
||||
sprnum = GetSprite(&ss, -1); /* -1 = any available */
|
||||
if (sprnum >= 0)
|
||||
{
|
||||
ss.x = 100;
|
||||
ss.y = 50;
|
||||
ss.height = 16;
|
||||
|
||||
/* Set sprite image data (must be in Chip RAM!): */
|
||||
ChangeSprite(NULL, &ss, spriteData);
|
||||
|
||||
/* Move sprite to screen position: */
|
||||
MoveSprite(NULL, &ss, 100, 50);
|
||||
|
||||
/* Release when done: */
|
||||
FreeSprite(sprnum);
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Mouse Pointer
|
||||
|
||||
```c
|
||||
/* Change the Intuition mouse pointer: */
|
||||
UWORD pointerData[] = {
|
||||
0x0000, 0x0000, /* reserved (position) */
|
||||
/* 16 lines of sprite data: */
|
||||
0x8000, 0xC000, /* line 0 */
|
||||
0xC000, 0xE000, /* line 1 */
|
||||
0xE000, 0xF000, /* line 2 */
|
||||
/* ... etc ... */
|
||||
0x0000, 0x0000 /* terminator */
|
||||
};
|
||||
SetPointer(window, pointerData, height, width, xOffset, yOffset);
|
||||
/* Restore default: */
|
||||
ClearPointer(window);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
| Pitfall | Consequence | Fix |
|
||||
|---|---|---|
|
||||
| Sprite data not in Chip RAM | DMA can't access it — sprite invisible or garbage | Use `AllocMem(MEMF_CHIP)` for sprite data |
|
||||
| Not setting pointer every frame | Sprite DMA reads stale pointer → random data displayed | Use Copper list to reload SPRxPT |
|
||||
| Forgetting `FreeSprite` | Sprite channel stays reserved → other apps can't use it | Always free in cleanup |
|
||||
| Using sprite 0 directly | Conflicts with Intuition's mouse pointer | Use `GetSprite(-1)` to get a free one |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- HRM: *Amiga Hardware Reference Manual* — Sprites chapter
|
||||
- NDK39: `graphics/sprite.h`
|
||||
- NDK39: `graphics/sprite.h`, `hardware/custom.h`
|
||||
- ADCD 2.1: `GetSprite`, `MoveSprite`, `ChangeSprite`, `FreeSprite`
|
||||
- See also: [copper_programming.md](copper_programming.md) — Copper-driven sprite multiplexing
|
||||
- See also: [rastport.md](rastport.md) — BOBs (software sprites via Blitter)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,26 @@
|
|||
[← Home](../README.md) · [Graphics](README.md)
|
||||
|
||||
# Text and Fonts — TextFont, TextAttr, OpenFont, Text
|
||||
# Text and Fonts — TextFont, TextAttr, Rendering
|
||||
|
||||
## Overview
|
||||
|
||||
AmigaOS uses bitmap fonts rendered through `graphics.library`. Fonts are described by `TextAttr` (request) and `TextFont` (loaded instance). Disk fonts require `diskfont.library`.
|
||||
AmigaOS uses **bitmap fonts** rendered through `graphics.library`. Each font character is stored as a strip of pixels in a single bitmap. Fonts can be ROM-resident (topaz — always available), loaded from disk via `diskfont.library`, or generated algorithmically (bold, italic, underline).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Font Sources"
|
||||
ROM["ROM Fonts<br/>(topaz 8, topaz 9)"]
|
||||
DISK["Disk Fonts<br/>(FONTS: directory)"]
|
||||
end
|
||||
|
||||
ROM -->|"OpenFont"| TF["struct TextFont"]
|
||||
DISK -->|"OpenDiskFont"| TF
|
||||
|
||||
TF -->|"SetFont(rp, font)"| RP["RastPort"]
|
||||
RP -->|"Text(rp, str, len)"| BM["BitMap<br/>(rendered text)"]
|
||||
|
||||
style TF fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -12,6 +28,8 @@ AmigaOS uses bitmap fonts rendered through `graphics.library`. Fonts are describ
|
|||
|
||||
```c
|
||||
/* graphics/text.h — NDK39 */
|
||||
|
||||
/* Font request — describes what you want: */
|
||||
struct TextAttr {
|
||||
STRPTR ta_Name; /* font name, e.g. "topaz.font" */
|
||||
UWORD ta_YSize; /* desired height in pixels */
|
||||
|
|
@ -19,40 +37,67 @@ struct TextAttr {
|
|||
UBYTE ta_Flags; /* FPF_ROMFONT, FPF_DISKFONT, etc. */
|
||||
};
|
||||
|
||||
/* Loaded font instance: */
|
||||
struct TextFont {
|
||||
struct Message tf_Message;
|
||||
UWORD tf_YSize; /* font height */
|
||||
UBYTE tf_Style;
|
||||
struct Message tf_Message; /* standard message header */
|
||||
UWORD tf_YSize; /* actual font height */
|
||||
UBYTE tf_Style; /* styles this font has built-in */
|
||||
UBYTE tf_Flags;
|
||||
UWORD tf_XSize; /* nominal width */
|
||||
UWORD tf_Baseline; /* baseline from top */
|
||||
UWORD tf_BoldSmear; /* bold smear width */
|
||||
UWORD tf_Accessors; /* open count */
|
||||
UBYTE tf_LoChar; /* first character code */
|
||||
UBYTE tf_HiChar; /* last character code */
|
||||
APTR tf_CharData; /* bitmap data */
|
||||
UWORD tf_XSize; /* nominal character width */
|
||||
UWORD tf_Baseline; /* pixels from top to baseline */
|
||||
UWORD tf_BoldSmear; /* extra pixels for algorithmic bold */
|
||||
UWORD tf_Accessors; /* current open count */
|
||||
UBYTE tf_LoChar; /* first character code (usually 32) */
|
||||
UBYTE tf_HiChar; /* last character code (usually 127 or 255) */
|
||||
APTR tf_CharData; /* bitmap strip containing all glyphs */
|
||||
UWORD tf_Modulo; /* bytes per row of font bitmap */
|
||||
APTR tf_CharLoc; /* character location table */
|
||||
APTR tf_CharSpace; /* proportional spacing table */
|
||||
APTR tf_CharKern; /* kerning table */
|
||||
APTR tf_CharLoc; /* location table: offset + width per char */
|
||||
APTR tf_CharSpace; /* proportional spacing table (NULL = fixed) */
|
||||
APTR tf_CharKern; /* kerning adjustment table (NULL = none) */
|
||||
};
|
||||
```
|
||||
|
||||
### Font Bitmap Layout
|
||||
|
||||
All characters are stored in a single bitmap strip. The `tf_CharLoc` table tells the renderer where each character starts:
|
||||
|
||||
```
|
||||
tf_CharData bitmap:
|
||||
┌──┬───┬──┬───┬──┬──┬───────────────────┐
|
||||
│A │ B │C │ D │E │F │ ... all chars ... │
|
||||
└──┴───┴──┴───┴──┴──┴───────────────────┘
|
||||
|
||||
tf_CharLoc[ch - tf_LoChar]:
|
||||
bits 31–16 = bit offset into tf_CharData
|
||||
bits 15–0 = character width in pixels
|
||||
|
||||
tf_CharSpace[ch - tf_LoChar]:
|
||||
spacing advance (proportional fonts)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Opening Fonts
|
||||
|
||||
```c
|
||||
/* ROM font (topaz, always available): */
|
||||
/* ROM font (topaz — always available, no disk access): */
|
||||
struct TextAttr ta = {"topaz.font", 8, 0, FPF_ROMFONT};
|
||||
struct TextFont *font = OpenFont(&ta);
|
||||
|
||||
/* Disk font (requires diskfont.library): */
|
||||
struct Library *DiskfontBase = OpenLibrary("diskfont.library", 0);
|
||||
struct TextAttr ta2 = {"helvetica.font", 24, 0, FPF_DISKFONT};
|
||||
struct TextFont *font2 = OpenDiskFont(&ta2);
|
||||
|
||||
/* Set in RastPort: */
|
||||
/* Request with style (may get algorithmically generated): */
|
||||
struct TextAttr ta3 = {"topaz.font", 8, FSF_BOLD, FPF_ROMFONT};
|
||||
struct TextFont *bold = OpenFont(&ta3);
|
||||
|
||||
/* Assign to RastPort: */
|
||||
SetFont(rp, font);
|
||||
|
||||
/* Close when done: */
|
||||
CloseFont(font);
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -60,31 +105,111 @@ SetFont(rp, font);
|
|||
## Rendering Text
|
||||
|
||||
```c
|
||||
Move(rp, 10, 20); /* position cursor */
|
||||
Text(rp, "Hello Amiga", 11); /* render 11 characters */
|
||||
/* Position cursor then render: */
|
||||
Move(rp, 10, 20 + rp->Font->tf_Baseline); /* baseline-relative! */
|
||||
Text(rp, "Hello Amiga", 11);
|
||||
|
||||
/* Get pixel width of a string: */
|
||||
UWORD width = TextLength(rp, "Hello", 5);
|
||||
/* Measure width before rendering (for centering/alignment): */
|
||||
UWORD width = TextLength(rp, "Hello Amiga", 11);
|
||||
|
||||
/* Centre text: */
|
||||
WORD centreX = (screenWidth - width) / 2;
|
||||
Move(rp, centreX, 100);
|
||||
Text(rp, "Hello Amiga", 11);
|
||||
|
||||
/* Pixel-perfect extent info: */
|
||||
struct TextExtent te;
|
||||
TextExtent(rp, "Hello", 5, &te);
|
||||
/* te.te_Width = total pixel width */
|
||||
/* te.te_Height = total pixel height */
|
||||
/* te.te_Extent = bounding rectangle */
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `Text()` renders at the **current pen position**, which should be at the font's **baseline** — not the top of the character. The baseline offset is `font->tf_Baseline` pixels below the top.
|
||||
|
||||
---
|
||||
|
||||
## Style Flags
|
||||
## Algorithmic Styles
|
||||
|
||||
```c
|
||||
/* Style flags: */
|
||||
#define FSF_UNDERLINED 0x01
|
||||
#define FSF_BOLD 0x02
|
||||
#define FSF_ITALIC 0x04
|
||||
#define FSF_EXTENDED 0x08
|
||||
|
||||
/* Set algorithmic style: */
|
||||
SetSoftStyle(rp, FSF_BOLD | FSF_ITALIC,
|
||||
AskSoftStyle(rp)); /* ask which styles the font supports */
|
||||
/* Ask which styles this font supports algorithmically: */
|
||||
UWORD supported = AskSoftStyle(rp);
|
||||
|
||||
/* Apply bold + italic: */
|
||||
SetSoftStyle(rp, FSF_BOLD | FSF_ITALIC, supported);
|
||||
Text(rp, "Bold Italic Text", 16);
|
||||
|
||||
/* Reset to normal: */
|
||||
SetSoftStyle(rp, 0, supported);
|
||||
```
|
||||
|
||||
| Style | Method | Notes |
|
||||
|---|---|---|
|
||||
| Bold | Smear right by `tf_BoldSmear` | Characters become slightly wider |
|
||||
| Italic | Shear top scanlines right | Fixed-angle slant |
|
||||
| Underline | Draw line at descender level | 1-pixel line below baseline |
|
||||
| Extended | Widen each character | Rarely used |
|
||||
|
||||
---
|
||||
|
||||
## Available Font Lists
|
||||
|
||||
```c
|
||||
/* List all fonts available on FONTS: */
|
||||
struct AvailFontsHeader *afh;
|
||||
LONG bufSize = 4096;
|
||||
do {
|
||||
afh = AllocMem(bufSize, MEMF_ANY);
|
||||
LONG shortBy = AvailFonts((STRPTR)afh, bufSize,
|
||||
AFF_DISK | AFF_MEMORY | AFF_SCALED);
|
||||
if (shortBy > 0) {
|
||||
FreeMem(afh, bufSize);
|
||||
bufSize += shortBy;
|
||||
afh = NULL;
|
||||
}
|
||||
} while (!afh);
|
||||
|
||||
struct AvailFonts *af = &afh->afh_AF;
|
||||
for (int i = 0; i < afh->afh_NumEntries; i++)
|
||||
{
|
||||
Printf("Font: %s, size %ld, type %s\n",
|
||||
af[i].af_Attr.ta_Name,
|
||||
af[i].af_Attr.ta_YSize,
|
||||
(af[i].af_Type & AFF_DISK) ? "disk" : "ROM");
|
||||
}
|
||||
FreeMem(afh, bufSize);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Font Preferences
|
||||
|
||||
The system font can be changed via Preferences. Applications should respect the user's choice:
|
||||
|
||||
```c
|
||||
/* Get the current screen font (user's preference): */
|
||||
struct TextAttr *screenFont;
|
||||
struct Preferences prefs;
|
||||
GetPrefs(&prefs, sizeof(prefs));
|
||||
/* prefs contains font info */
|
||||
|
||||
/* Better: use the screen's font directly: */
|
||||
struct TextFont *scrFont = screen->RastPort.Font;
|
||||
SetFont(myRastPort, scrFont);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `graphics/text.h`
|
||||
- NDK39: `graphics/text.h`, `graphics/rastport.h`
|
||||
- ADCD 2.1: `OpenFont`, `OpenDiskFont`, `SetFont`, `Text`, `TextLength`
|
||||
- See also: [diskfont.md](../11_libraries/diskfont.md) — disk font loading
|
||||
- See also: [rastport.md](rastport.md) — RastPort text rendering context
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue