mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-12 16:16: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
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ Amiga devices are shared libraries with an exec I/O request interface. They prov
|
|||
| File | Description |
|
||||
|---|---|
|
||||
| [trackdisk.md](trackdisk.md) | Floppy disk DMA: MFM encoding, track format, disk geometry, track caching, direct HW access |
|
||||
| [scsi.md](scsi.md) | scsi.device / 2nd.scsi.device — hard disk I/O |
|
||||
| [scsi.md](scsi.md) | Hard disk/CD-ROM I/O: per-model interfaces, Gayle bandwidth limits, native vs vendor drivers, HD_SCSICMD, CD-ROM commands, TD64/NSD 64-bit |
|
||||
| [serial.md](serial.md) | UART/RS-232: CIA registers, baud rate calculation, serial debugging (KPrintF) |
|
||||
| [parallel.md](parallel.md) | parallel.device — Centronics parallel port |
|
||||
| [timer.md](timer.md) | CIA timers, E-clock, VBlank: delays, ReadEClock, periodic patterns, signal-based waiting |
|
||||
| [parallel.md](parallel.md) | Centronics parallel port: CIA-A Port B mapping, hardware pinout, direct register access |
|
||||
| [timer.md](timer.md) | CIA timers, E-clock, VBlank: delays, ReadEClock, periodic patterns, signal multiplexing, resource exhaustion |
|
||||
| [audio.md](audio.md) | 4-channel DMA audio: hardware registers, priority allocation, double-buffering, modulation |
|
||||
| [keyboard.md](keyboard.md) | CIA-A serial handshake, raw key codes, key matrix, reset sequence, FPGA protocol notes |
|
||||
| [gameport.md](gameport.md) | gameport.device — joystick/mouse port reading |
|
||||
| [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) | console.device — text terminal I/O |
|
||||
| [console.md](console.md) | Text terminal I/O: ANSI escape sequences, cursor/colour control, raw key events, CON:/RAW: handlers |
|
||||
|
|
|
|||
|
|
@ -4,44 +4,241 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`console.device` provides ANSI-compatible text rendering into Intuition windows. It translates raw keycodes to ASCII and supports a rich set of escape sequences for cursor positioning, colour, and text formatting.
|
||||
`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
|
||||
- **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.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
KB["Keyboard<br/>(raw keycodes)"] --> INPUT["input.device"]
|
||||
INPUT --> CON["console.device"]
|
||||
CON -->|"Read: ASCII chars"| APP["Application"]
|
||||
APP -->|"Write: text + ESC sequences"| CON
|
||||
CON -->|"Renders text via<br/>RastPort drawing"| WIN["Intuition Window"]
|
||||
|
||||
style CON fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style WIN fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Opening
|
||||
|
||||
```c
|
||||
struct IOStdReq *con = CreateStdIO(port);
|
||||
con->io_Data = (APTR)win; /* the Intuition Window */
|
||||
struct MsgPort *conPort = CreateMsgPort();
|
||||
struct IOStdReq *con = (struct IOStdReq *)
|
||||
CreateIORequest(conPort, sizeof(struct IOStdReq));
|
||||
|
||||
/* Attach to an Intuition window: */
|
||||
con->io_Data = (APTR)window;
|
||||
con->io_Length = sizeof(struct Window);
|
||||
OpenDevice("console.device", 0, (struct IORequest *)con, 0);
|
||||
|
||||
if (OpenDevice("console.device", CONU_STANDARD, (struct IORequest *)con, 0))
|
||||
{
|
||||
/* error — can't open console */
|
||||
}
|
||||
```
|
||||
|
||||
### Unit Types
|
||||
|
||||
| Unit | Constant | Description |
|
||||
|---|---|---|
|
||||
| 0 | `CONU_STANDARD` | Full-feature console with cursor and scrolling |
|
||||
| 1 | `CONU_CHARMAP` | Character-mapped console (OS 3.0+) — faster for full-screen updates |
|
||||
| 3 | `CONU_SNIPMAP` | Snip-mapped: supports clipboard cut/paste (OS 3.0+) |
|
||||
| -1 | `CONU_LIBRARY` | Library mode — no window, just keymap translation |
|
||||
|
||||
---
|
||||
|
||||
## Writing Text and Escape Sequences
|
||||
|
||||
```c
|
||||
/* Write text to the console window: */
|
||||
void ConPuts(struct IOStdReq *con, char *str)
|
||||
{
|
||||
con->io_Command = CMD_WRITE;
|
||||
con->io_Data = (APTR)str;
|
||||
con->io_Length = -1; /* -1 = null-terminated */
|
||||
DoIO((struct IORequest *)con);
|
||||
}
|
||||
|
||||
/* 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[10;20HText at row 10 col 20"); /* absolute position */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Escape Sequences
|
||||
## Reading Input
|
||||
|
||||
```c
|
||||
/* Read characters (blocking): */
|
||||
char buffer[256];
|
||||
con->io_Command = CMD_READ;
|
||||
con->io_Data = (APTR)buffer;
|
||||
con->io_Length = sizeof(buffer);
|
||||
DoIO((struct IORequest *)con);
|
||||
/* con->io_Actual = number of bytes read */
|
||||
|
||||
/* Non-blocking read via SendIO + WaitPort: */
|
||||
con->io_Command = CMD_READ;
|
||||
con->io_Data = (APTR)buffer;
|
||||
con->io_Length = 1; /* read 1 char at a time */
|
||||
SendIO((struct IORequest *)con);
|
||||
|
||||
/* Wait for input alongside other events: */
|
||||
ULONG consoleSig = 1 << conPort->mp_SigBit;
|
||||
ULONG windowSig = 1 << window->UserPort->mp_SigBit;
|
||||
|
||||
ULONG sigs = Wait(consoleSig | windowSig);
|
||||
if (sigs & consoleSig)
|
||||
{
|
||||
WaitIO((struct IORequest *)con);
|
||||
char ch = buffer[0];
|
||||
/* process character... */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ANSI Escape Sequences
|
||||
|
||||
Console.device supports a rich subset of ANSI/VT100 escape sequences (CSI = `\033[` = ESC + `[`):
|
||||
|
||||
### Cursor Movement
|
||||
|
||||
| Sequence | Description | Example |
|
||||
|---|---|---|
|
||||
| `\033[nA` | Cursor up n lines | `\033[5A` = up 5 |
|
||||
| `\033[nB` | Cursor down n lines | |
|
||||
| `\033[nC` | Cursor right n columns | |
|
||||
| `\033[nD` | Cursor left n columns | |
|
||||
| `\033[y;xH` | Move to row y, column x (1-based) | `\033[1;1H` = home |
|
||||
| `\033[H` | Home cursor (top-left) | |
|
||||
| `\033[6n` | Report cursor position → replies `\033[y;xR` | |
|
||||
| `\033[s` | Save cursor position | |
|
||||
| `\033[u` | Restore cursor position | |
|
||||
|
||||
### Erasing
|
||||
|
||||
| Sequence | Description |
|
||||
|---|---|
|
||||
| `\033[H` | Home cursor |
|
||||
| `\033[nA` | Cursor up n lines |
|
||||
| `\033[nB` | Cursor down n lines |
|
||||
| `\033[nC` | Cursor right n columns |
|
||||
| `\033[nD` | Cursor left n columns |
|
||||
| `\033[y;xH` | Move to row y, column x |
|
||||
| `\033[J` | Clear to end of screen |
|
||||
| `\033[K` | Clear to end of line |
|
||||
| `\033[nm` | Set graphics rendition (colour/style) |
|
||||
| `\033[0m` | Reset attributes |
|
||||
| `\033[1m` | Bold |
|
||||
| `\033[3m` | Italic |
|
||||
| `\033[4m` | Underline |
|
||||
| `\033[30–37m` | Foreground colour |
|
||||
| `\033[40–47m` | Background colour |
|
||||
| `\033[J` | Clear from cursor to end of screen |
|
||||
| `\033[1J` | Clear from start of screen to cursor |
|
||||
| `\033[2J` | Clear entire screen |
|
||||
| `\033[K` | Clear from cursor to end of line |
|
||||
| `\033[1K` | Clear from start of line to cursor |
|
||||
| `\033[2K` | Clear entire line |
|
||||
|
||||
### Text Attributes (SGR)
|
||||
|
||||
| Sequence | Effect |
|
||||
|---|---|
|
||||
| `\033[0m` | Reset all attributes |
|
||||
| `\033[1m` | **Bold** (high intensity) |
|
||||
| `\033[3m` | *Italic* |
|
||||
| `\033[4m` | <u>Underline</u> |
|
||||
| `\033[7m` | Inverse video (swap fg/bg) |
|
||||
| `\033[22m` | Normal intensity (cancel bold) |
|
||||
| `\033[23m` | Cancel italic |
|
||||
| `\033[24m` | Cancel underline |
|
||||
|
||||
### Colours
|
||||
|
||||
| Sequence | Foreground | Background |
|
||||
|---|---|---|
|
||||
| `\033[30m` / `\033[40m` | Black | Black |
|
||||
| `\033[31m` / `\033[41m` | Red | Red |
|
||||
| `\033[32m` / `\033[42m` | Green | Green |
|
||||
| `\033[33m` / `\033[43m` | Yellow/Brown | Yellow/Brown |
|
||||
| `\033[34m` / `\033[44m` | Blue | Blue |
|
||||
| `\033[35m` / `\033[45m` | Magenta | Magenta |
|
||||
| `\033[36m` / `\033[46m` | Cyan | Cyan |
|
||||
| `\033[37m` / `\033[47m` | White | White |
|
||||
| `\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.
|
||||
|
||||
### Amiga-Specific Extensions
|
||||
|
||||
| Sequence | Description |
|
||||
|---|---|
|
||||
| `\033[>1h` | Enable auto-scroll |
|
||||
| `\033[>1l` | Disable auto-scroll |
|
||||
| `\033[ p` | Enable cursor |
|
||||
| `\033[0 p` | Disable cursor |
|
||||
| `\033[t` / `\033[b` | Set top/bottom scroll margins |
|
||||
| `\033[20h` | Linefeed mode (LF = CR+LF) |
|
||||
|
||||
---
|
||||
|
||||
## Raw Key Events
|
||||
|
||||
In addition to ASCII, console.device reports **special keys** as multi-byte escape sequences:
|
||||
|
||||
| Key | Sequence Received |
|
||||
|---|---|
|
||||
| Cursor Up | `\033[A` |
|
||||
| Cursor Down | `\033[B` |
|
||||
| Cursor Right | `\033[C` |
|
||||
| Cursor Left | `\033[D` |
|
||||
| Shift+Up | `\033[T` |
|
||||
| Shift+Down | `\033[S` |
|
||||
| F1–F10 | `\033[0~` – `\033[9~` |
|
||||
| Shift+F1–F10 | `\033[10~` – `\033[19~` |
|
||||
| Help | `\033[?~` |
|
||||
|
||||
---
|
||||
|
||||
## Proper Shutdown
|
||||
|
||||
```c
|
||||
/* Must abort any pending read before closing: */
|
||||
if (!CheckIO((struct IORequest *)con))
|
||||
{
|
||||
AbortIO((struct IORequest *)con);
|
||||
WaitIO((struct IORequest *)con);
|
||||
}
|
||||
CloseDevice((struct IORequest *)con);
|
||||
DeleteIORequest((struct IORequest *)con);
|
||||
DeleteMsgPort(conPort);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CON: and RAW: Handlers
|
||||
|
||||
The AmigaDOS file handlers `CON:` and `RAW:` are wrappers around console.device:
|
||||
|
||||
| Handler | Description |
|
||||
|---|---|
|
||||
| `CON:` | Line-buffered console — input is buffered until Enter is pressed. Supports line editing. |
|
||||
| `RAW:` | Raw console — each keypress is delivered immediately. No line editing. |
|
||||
|
||||
```c
|
||||
/* Open a CON: window from DOS: */
|
||||
BPTR fh = Open("CON:0/0/640/200/My Window/CLOSE", MODE_OLDFILE);
|
||||
FPuts(fh, "Type something: ");
|
||||
char buf[80];
|
||||
FGets(fh, buf, sizeof(buf));
|
||||
Close(fh);
|
||||
|
||||
/* RAW: for unbuffered key-by-key input: */
|
||||
BPTR raw = Open("RAW:0/0/640/200/Raw Input", MODE_OLDFILE);
|
||||
/* Each Read returns immediately with 1 char */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `devices/conunit.h`
|
||||
- NDK39: `devices/conunit.h`, `devices/console.h`
|
||||
- ADCD 2.1: console.device autodocs
|
||||
- See also: [keyboard.md](keyboard.md) — raw keycode to console.device pipeline
|
||||
- See also: [input.md](input.md) — input handler chain
|
||||
|
|
|
|||
|
|
@ -1,64 +1,130 @@
|
|||
[← Home](../README.md) · [Devices](README.md)
|
||||
|
||||
# gameport.device — Joystick and Mouse
|
||||
# gameport.device — Joystick and Mouse Ports
|
||||
|
||||
## Overview
|
||||
|
||||
`gameport.device` reads joystick and mouse ports (active on port 1 for joystick, port 0 for mouse). Uses CIA and custom chip registers.
|
||||
`gameport.device` provides access to the Amiga's two **controller ports** (mouse/joystick). The hardware reads controller state through custom chip registers `JOY0DAT`/`JOY1DAT` and the CIA-A port for fire buttons. Understanding the hardware protocol is critical for FPGA cores.
|
||||
|
||||
---
|
||||
|
||||
## Hardware Registers
|
||||
|
||||
| Register | Address | Controller | Function |
|
||||
|---|---|---|---|
|
||||
| `JOY0DAT` | `$DFF00A` | Port 1 (mouse) | Quadrature-encoded position data |
|
||||
| `JOY1DAT` | `$DFF00C` | Port 2 (joystick) | Direction bits or quadrature data |
|
||||
| `JOYTEST` | `$DFF036` | Both | Write to set counter test value |
|
||||
| `POTGO` | `$DFF034` | Both | Proportional/paddle port control |
|
||||
| `POTGOR` | `$DFF016` | Both | Read paddle/proportional values |
|
||||
| CIA-A PRA | `$BFE001` | Both | Fire buttons (active low) |
|
||||
|
||||
### JOYxDAT Bit Layout
|
||||
|
||||
```
|
||||
Bits 15–8: Y counter (or vertical quadrature)
|
||||
Bits 7–0: X counter (or horizontal quadrature)
|
||||
```
|
||||
|
||||
### Mouse Quadrature Decoding
|
||||
|
||||
For mouse input, the X/Y values are **quadrature counters** — they increment/decrement as the mouse moves:
|
||||
|
||||
```c
|
||||
/* Read mouse delta: */
|
||||
UWORD joy = custom->joy0dat; /* current counter */
|
||||
WORD dx = (WORD)(joy & 0xFF) - (WORD)(prev_joy & 0xFF);
|
||||
WORD dy = (WORD)(joy >> 8) - (WORD)(prev_joy >> 8);
|
||||
/* dx/dy = movement since last read (signed, wraps at 255→0) */
|
||||
prev_joy = joy;
|
||||
```
|
||||
|
||||
### Joystick Digital Decoding
|
||||
|
||||
For digital joysticks, the direction bits are encoded:
|
||||
|
||||
```c
|
||||
UWORD joy = custom->joy1dat;
|
||||
BOOL right = joy & 0x0002;
|
||||
BOOL left = (joy >> 1) ^ (joy & 0x0001); /* XOR of bit 1 and bit 0 */
|
||||
BOOL down = joy & 0x0200;
|
||||
BOOL up = (joy >> 9) ^ ((joy >> 8) & 0x0001);
|
||||
|
||||
/* Fire button — from CIA-A: */
|
||||
BOOL fire = !(ciaa->ciapra & 0x80); /* port 2 button, active low */
|
||||
/* Port 1 fire = bit 6 of CIAA PRA */
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The joystick directional decoding uses **XOR** between adjacent bits — not direct reads. This is because the hardware shares the same quadrature interface used for mice. This is a common source of bugs in FPGA implementations.
|
||||
|
||||
### Fire Buttons
|
||||
|
||||
| Button | Register | Bit | Port |
|
||||
|---|---|---|---|
|
||||
| Port 1 fire (left mouse) | CIA-A PRA `$BFE001` | 6 | Mouse port |
|
||||
| Port 2 fire (joystick) | CIA-A PRA `$BFE001` | 7 | Joystick port |
|
||||
| Port 1 middle/right | POTGOR `$DFF016` | 8, 10 | Mouse port |
|
||||
| Port 2 second button | POTGOR `$DFF016` | 12, 14 | Joystick port |
|
||||
|
||||
---
|
||||
|
||||
## Using gameport.device (OS Level)
|
||||
|
||||
```c
|
||||
struct MsgPort *gpPort = CreateMsgPort();
|
||||
struct IOStdReq *gpReq = (struct IOStdReq *)
|
||||
CreateIORequest(gpPort, sizeof(struct IOStdReq));
|
||||
|
||||
/* Open port 1 (joystick port): */
|
||||
OpenDevice("gameport.device", 1, (struct IORequest *)gpReq, 0);
|
||||
|
||||
/* Set controller type: */
|
||||
UBYTE type = GPCT_ABSJOYSTICK;
|
||||
gpReq->io_Command = GPD_SETCTYPE;
|
||||
gpReq->io_Data = (APTR)&type;
|
||||
gpReq->io_Length = 1;
|
||||
DoIO((struct IORequest *)gpReq);
|
||||
|
||||
/* Set trigger conditions: */
|
||||
struct GamePortTrigger trigger;
|
||||
trigger.gpt_Keys = GPTF_UPKEYS | GPTF_DOWNKEYS;
|
||||
trigger.gpt_Timeout = 0; /* no timeout */
|
||||
trigger.gpt_XDelta = 1; /* report on any movement */
|
||||
trigger.gpt_YDelta = 1;
|
||||
gpReq->io_Command = GPD_SETTRIGGER;
|
||||
gpReq->io_Data = (APTR)&trigger;
|
||||
gpReq->io_Length = sizeof(trigger);
|
||||
DoIO((struct IORequest *)gpReq);
|
||||
|
||||
/* Read events: */
|
||||
struct InputEvent ie;
|
||||
gpReq->io_Command = GPD_READEVENT;
|
||||
gpReq->io_Data = (APTR)&ie;
|
||||
gpReq->io_Length = sizeof(ie);
|
||||
SendIO((struct IORequest *)gpReq);
|
||||
|
||||
/* Wait for joystick event: */
|
||||
Wait(1L << gpPort->mp_SigBit);
|
||||
WaitIO((struct IORequest *)gpReq);
|
||||
/* ie now contains joystick movement/button data */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Controller Types
|
||||
|
||||
```c
|
||||
/* devices/gameport.h */
|
||||
#define GPCT_ALLOCATED -1 /* port is allocated */
|
||||
#define GPCT_NOCONTROLLER 0 /* nothing connected */
|
||||
#define GPCT_MOUSE 1 /* mouse */
|
||||
#define GPCT_RELJOYSTICK 2 /* relative joystick (proportional) */
|
||||
#define GPCT_ABSJOYSTICK 3 /* absolute joystick */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reading a Joystick
|
||||
|
||||
```c
|
||||
struct IOStdReq *gp = CreateStdIO(port);
|
||||
OpenDevice("gameport.device", 1, (struct IORequest *)gp, 0);
|
||||
|
||||
/* Set controller type: */
|
||||
UBYTE type = GPCT_ABSJOYSTICK;
|
||||
gp->io_Command = GPD_SETCTYPE;
|
||||
gp->io_Data = &type;
|
||||
gp->io_Length = 1;
|
||||
DoIO((struct IORequest *)gp);
|
||||
|
||||
/* Set trigger conditions: */
|
||||
struct GamePortTrigger trigger = {
|
||||
GPTF_UPKEYS | GPTF_DOWNKEYS, /* report buttons */
|
||||
10000, /* X delta timeout */
|
||||
10000, /* Y delta timeout */
|
||||
1, 1 /* X/Y delta threshold */
|
||||
};
|
||||
gp->io_Command = GPD_SETTRIGGER;
|
||||
gp->io_Data = &trigger;
|
||||
gp->io_Length = sizeof(trigger);
|
||||
DoIO((struct IORequest *)gp);
|
||||
|
||||
/* Read events: */
|
||||
struct InputEvent ie;
|
||||
gp->io_Command = GPD_READEVENT;
|
||||
gp->io_Data = &ie;
|
||||
gp->io_Length = sizeof(ie);
|
||||
DoIO((struct IORequest *)gp);
|
||||
/* ie.ie_Code, ie.ie_position.ie_xy give button/direction */
|
||||
|
||||
CloseDevice((struct IORequest *)gp);
|
||||
```
|
||||
| Constant | Value | Device |
|
||||
|---|---|---|
|
||||
| `GPCT_MOUSE` | 1 | Standard Amiga mouse |
|
||||
| `GPCT_RELJOYSTICK` | 2 | Relative joystick (proportional) |
|
||||
| `GPCT_ABSJOYSTICK` | 3 | Absolute/digital joystick |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `devices/gameport.h`
|
||||
- NDK39: `devices/gameport.h`, `hardware/custom.h`, `hardware/cia.h`
|
||||
- HRM: *Amiga Hardware Reference Manual* — Controller Ports chapter
|
||||
- See also: [input.md](input.md) — input handler chain that receives gameport events
|
||||
- See also: [keyboard.md](keyboard.md) — keyboard shares CIA-A interrupt infrastructure
|
||||
|
|
|
|||
|
|
@ -4,33 +4,146 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`parallel.device` provides I/O for the Amiga's Centronics-compatible parallel port, used primarily for printers.
|
||||
`parallel.device` provides I/O for the Amiga's Centronics-compatible 25-pin parallel port. Primarily used for printers, it also serves as a general-purpose 8-bit data port for hardware projects, dongles, MIDI interfaces, and inter-machine transfers (Laplink-style).
|
||||
|
||||
The parallel port is directly connected to **CIA-A** Port B data register (`$BFE101`) with handshake lines on CIA-A and CIA-B.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
APP["Application"] -->|"CMD_WRITE<br/>CMD_READ"| PAR["parallel.device"]
|
||||
PAR --> CIA["CIA-A Port B<br/>($BFE101)<br/>8 data bits"]
|
||||
CIA --> PORT["DB-25 Connector<br/>(pins 2-9 = data)"]
|
||||
PAR --> CIAB["CIA-B<br/>Handshake lines"]
|
||||
|
||||
style PAR fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style CIA fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hardware Pinout
|
||||
|
||||
| Pin | Signal | Direction | Description |
|
||||
|---|---|---|---|
|
||||
| 1 | /STROBE | Output | Data strobe (active low) |
|
||||
| 2–9 | D0–D7 | I/O | 8-bit bidirectional data |
|
||||
| 10 | /ACK | Input | Acknowledge from printer |
|
||||
| 11 | BUSY | Input | Printer busy |
|
||||
| 12 | POUT | Input | Paper out |
|
||||
| 13 | SELECT | Input | Printer selected |
|
||||
| 14–17 | — | — | Reserved / unused |
|
||||
| 18–25 | GND | — | Ground |
|
||||
|
||||
---
|
||||
|
||||
## Opening
|
||||
|
||||
```c
|
||||
struct MsgPort *parPort = CreateMsgPort();
|
||||
struct IOExtPar *par = (struct IOExtPar *)
|
||||
CreateIORequest(port, sizeof(struct IOExtPar));
|
||||
OpenDevice("parallel.device", 0, (struct IORequest *)par, 0);
|
||||
CreateIORequest(parPort, sizeof(struct IOExtPar));
|
||||
|
||||
if (OpenDevice("parallel.device", 0, (struct IORequest *)par, 0))
|
||||
{
|
||||
Printf("Cannot open parallel.device\n");
|
||||
/* Port may be in use by another application (printer, etc.) */
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Only **one** application can have the parallel port open at a time. If the port is busy (e.g., printer spooler has it), `OpenDevice` will fail. This is unlike serial.device which supports shared modes.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
| Code | Constant | Description |
|
||||
|---|---|---|
|
||||
| 2 | `CMD_READ` | Read bytes (if bidirectional) |
|
||||
| 3 | `CMD_WRITE` | Write bytes |
|
||||
| 5 | `CMD_CLEAR` | Clear buffers |
|
||||
| 8 | `CMD_FLUSH` | Abort pending |
|
||||
| 9 | `PDCMD_QUERY` | Get status |
|
||||
| 10 | `PDCMD_SETPARAMS` | Set parameters |
|
||||
| 2 | `CMD_READ` | Read bytes (bidirectional mode) |
|
||||
| 3 | `CMD_WRITE` | Write bytes to the port |
|
||||
| 5 | `CMD_CLEAR` | Clear internal buffers |
|
||||
| 7 | `CMD_RESET` | Reset the device |
|
||||
| 8 | `CMD_FLUSH` | Abort all pending I/O |
|
||||
| 9 | `PDCMD_QUERY` | Get port status (busy, paper out, etc.) |
|
||||
| 10 | `PDCMD_SETPARAMS` | Configure port parameters |
|
||||
|
||||
### Writing Data
|
||||
|
||||
```c
|
||||
/* Send data to the parallel port: */
|
||||
char printData[] = "Hello, Printer!\r\n";
|
||||
par->IOPar.io_Command = CMD_WRITE;
|
||||
par->IOPar.io_Data = printData;
|
||||
par->IOPar.io_Length = sizeof(printData) - 1;
|
||||
DoIO((struct IORequest *)par);
|
||||
|
||||
if (par->IOPar.io_Error)
|
||||
Printf("Write error: %ld\n", par->IOPar.io_Error);
|
||||
```
|
||||
|
||||
### Querying Status
|
||||
|
||||
```c
|
||||
par->IOPar.io_Command = PDCMD_QUERY;
|
||||
DoIO((struct IORequest *)par);
|
||||
|
||||
UBYTE status = par->io_Status;
|
||||
if (status & IOPTF_PARBUSY) Printf("Printer busy\n");
|
||||
if (status & IOPTF_PAPEROUT) Printf("Paper out\n");
|
||||
if (status & IOPTF_PARSEL) Printf("Printer selected\n");
|
||||
```
|
||||
|
||||
### Setting Parameters
|
||||
|
||||
```c
|
||||
/* Configure for custom use (e.g., no handshaking): */
|
||||
par->IOPar.io_Command = PDCMD_SETPARAMS;
|
||||
par->io_ParFlags = PARF_SHARED; /* shared access (OS 2.0+) */
|
||||
DoIO((struct IORequest *)par);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Direct Hardware Access
|
||||
|
||||
For hardware projects that need precise control (bypassing the device driver):
|
||||
|
||||
```c
|
||||
/* Direct CIA-A Port B access: */
|
||||
volatile UBYTE *ciaaPrb = (UBYTE *)0xBFE101; /* data register */
|
||||
volatile UBYTE *ciaaDdrb = (UBYTE *)0xBFE301; /* data direction */
|
||||
|
||||
/* Set all 8 bits as output: */
|
||||
*ciaaDdrb = 0xFF;
|
||||
|
||||
/* Write a byte: */
|
||||
*ciaaPrb = 0x42;
|
||||
|
||||
/* Set as input: */
|
||||
*ciaaDdrb = 0x00;
|
||||
UBYTE value = *ciaaPrb;
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> Direct hardware access bypasses the OS completely. Do not mix direct register access with parallel.device operations — close the device first or don't open it at all.
|
||||
|
||||
---
|
||||
|
||||
## Common Uses
|
||||
|
||||
| Use Case | Method |
|
||||
|---|---|
|
||||
| Printer output | `CMD_WRITE` through printer.device (which opens parallel.device) |
|
||||
| Hardware dongle | Direct CIA-A Port B register read |
|
||||
| MIDI interface | Parallel-to-MIDI adapter with custom timing |
|
||||
| Amiga-to-PC transfer | Laplink cable with ParNet or ParBench software |
|
||||
| Sampling hardware | ADC/DAC connected to data lines |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `devices/parallel.h`
|
||||
- ADCD 2.1: parallel.device autodocs
|
||||
- See also: [serial.md](serial.md) — serial port I/O
|
||||
- See also: [cia_chips.md](../01_hardware/common/cia_chips.md) — CIA register details
|
||||
|
|
|
|||
|
|
@ -1,66 +1,283 @@
|
|||
[← Home](../README.md) · [Devices](README.md)
|
||||
|
||||
# scsi.device — Hard Disk I/O
|
||||
# scsi.device — Hard Disk, CD-ROM, and Mass Storage I/O
|
||||
|
||||
## Overview
|
||||
|
||||
SCSI and IDE hard disks are accessed via `scsi.device` (A3000/A4000 built-in) or `2nd.scsi.device` / `ide.device` (A1200/A600). The API is the same as trackdisk for basic read/write, with additional SCSI direct commands.
|
||||
SCSI and IDE hard disks on the Amiga are accessed through `scsi.device` or compatible device drivers. The API is consistent across implementations — the same IORequest structure and commands work regardless of whether the underlying hardware is Commodore's native Gayle IDE, a Zorro SCSI card, or an accelerator-integrated controller.
|
||||
|
||||
---
|
||||
|
||||
## Disk Interfaces by Amiga Model
|
||||
|
||||
| Model | Interface | Controller | Device Name | Bandwidth Limit |
|
||||
|---|---|---|---|---|
|
||||
| A500 | None (stock) | — | — | Requires external SCSI card |
|
||||
| A500+ | None (stock) | — | — | Requires external SCSI card |
|
||||
| A600 | IDE (44-pin) | **Gayle** | `scsi.device` | ~1.5 MB/s (PIO, 16-bit CIA) |
|
||||
| A1000 | None (stock) | — | — | Requires SCSI sidecar |
|
||||
| A1200 | IDE (44-pin) | **Gayle** | `scsi.device` | ~1.5 MB/s (PIO) |
|
||||
| A2000 | None (stock) | — | — | Zorro II SCSI cards |
|
||||
| A3000 | SCSI (50-pin) | **WD33C93** | `scsi.device` | ~3 MB/s (DMA) |
|
||||
| A4000 | IDE (40-pin) | **A4000 IDE** | `scsi.device` | ~2 MB/s (PIO) |
|
||||
| A4000T | SCSI (50-pin) + IDE | **NCR 53C710** + IDE | `2nd.scsi.device` / `scsi.device` | ~10 MB/s (SCSI DMA) |
|
||||
| CD32 | IDE (internal CD) | **Akiko** | `scsi.device` | ~1.5 MB/s |
|
||||
| CDTV | SCSI (internal CD) | Custom | `scsi.device` | Slow |
|
||||
|
||||
### Why Native Bandwidth Is Limited
|
||||
|
||||
The A600/A1200 **Gayle** IDE interface is the most common and the most constrained:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CPU["68020/68030<br/>(fast bus)"] -->|"PIO transfer<br/>WORD at a time"| GAYLE["Gayle IDE<br/>(no DMA!)"]
|
||||
GAYLE -->|"16-bit ATA"| DRIVE["IDE Drive"]
|
||||
|
||||
style GAYLE fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
```
|
||||
|
||||
| Bottleneck | Explanation |
|
||||
|---|---|
|
||||
| **No DMA** | Gayle has no DMA engine — every word must be moved by the CPU (`MOVE.W` loop) |
|
||||
| **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 |
|
||||
|
||||
### 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 |
|
||||
| **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 |
|
||||
| **CF adapters** | CompactFlash in IDE socket | Same speed, but no mechanical seek latency |
|
||||
| **SD/microSD** | Adapter on clock port | Varies, typically slow but no noise |
|
||||
|
||||
---
|
||||
|
||||
## Native vs. Vendor Drivers — Software Differences
|
||||
|
||||
All drivers expose the same `CMD_READ`/`CMD_WRITE`/`HD_SCSICMD` API, but internal differences matter:
|
||||
|
||||
| Aspect | Commodore `scsi.device` | Vendor Drivers (GVP, A4091, etc.) |
|
||||
|---|---|---|
|
||||
| **Source** | Commodore ROM or Devs: | Third-party (ships with hardware) |
|
||||
| **DMA support** | None (Gayle PIO) | Often DMA-capable |
|
||||
| **Interrupt handling** | Level 2 interrupt (CIA) | Card-specific interrupt level |
|
||||
| **TD64 / NSD** | Added via patches | Often built-in from factory |
|
||||
| **Multi-LUN** | Usually ignored | Proper LUN scanning on SCSI |
|
||||
| **Disconnect/reselect** | Not supported (Gayle) | SCSI disconnect on good controllers |
|
||||
| **Tagged queuing** | No | Some advanced SCSI controllers |
|
||||
| **CD-ROM support** | Basic (atapi.device) | Full ATAPI/SCSI CD support |
|
||||
| **Removable media** | Basic change detection | Proper unit attention handling |
|
||||
|
||||
> [!IMPORTANT]
|
||||
> When writing software that accesses disks, **always use the device name from the mountlist** — don't hardcode `scsi.device`. Different systems use different device names for the same physical drive.
|
||||
|
||||
---
|
||||
|
||||
## Opening
|
||||
|
||||
```c
|
||||
struct IOStdReq *scsi = CreateStdIO(port);
|
||||
OpenDevice("scsi.device", 0, (struct IORequest *)scsi, 0);
|
||||
/* unit 0 = first SCSI/IDE device */
|
||||
struct MsgPort *diskPort = CreateMsgPort();
|
||||
struct IOStdReq *diskReq = (struct IOStdReq *)
|
||||
CreateIORequest(diskPort, sizeof(struct IOStdReq));
|
||||
|
||||
/* Unit 0 = first device on the bus (master/ID 0): */
|
||||
if (OpenDevice("scsi.device", 0, (struct IORequest *)diskReq, 0))
|
||||
{
|
||||
Printf("Cannot open scsi.device unit 0\n");
|
||||
}
|
||||
```
|
||||
|
||||
### Units
|
||||
|
||||
| Unit | SCSI Meaning | IDE Meaning |
|
||||
|---|---|---|
|
||||
| 0 | SCSI ID 0 | Master |
|
||||
| 1 | SCSI ID 1 | Slave |
|
||||
| 2–6 | SCSI ID 2–6 | N/A |
|
||||
| 7 | Host adapter (reserved) | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Standard Commands
|
||||
|
||||
Same as trackdisk: `CMD_READ`, `CMD_WRITE`, `CMD_UPDATE`, `TD_CHANGENUM`, etc.
|
||||
```c
|
||||
/* Read 512 bytes from byte offset on disk: */
|
||||
diskReq->io_Command = CMD_READ;
|
||||
diskReq->io_Data = buffer;
|
||||
diskReq->io_Length = 512;
|
||||
diskReq->io_Offset = 0; /* byte offset */
|
||||
DoIO((struct IORequest *)diskReq);
|
||||
|
||||
/* Write: */
|
||||
diskReq->io_Command = CMD_WRITE;
|
||||
diskReq->io_Data = buffer;
|
||||
diskReq->io_Length = 512;
|
||||
diskReq->io_Offset = 512;
|
||||
DoIO((struct IORequest *)diskReq);
|
||||
|
||||
/* Flush write cache: */
|
||||
diskReq->io_Command = CMD_UPDATE;
|
||||
DoIO((struct IORequest *)diskReq);
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The `io_Offset` field is a `ULONG` (32-bit), limiting addressable space to **4 GB**. For larger drives, use TD64/NSD 64-bit commands.
|
||||
|
||||
---
|
||||
|
||||
## Direct SCSI Commands (HD_SCSICMD)
|
||||
|
||||
For operations beyond read/write — device identification, mode pages, CD-ROM commands:
|
||||
|
||||
```c
|
||||
#include <devices/scsidisk.h>
|
||||
|
||||
struct SCSICmd scsicmd;
|
||||
UBYTE cdb[10]; /* SCSI CDB */
|
||||
UBYTE sense[20]; /* sense data buffer */
|
||||
UBYTE data[512]; /* data buffer */
|
||||
UBYTE cdb[10], sense[20], data[512];
|
||||
|
||||
/* Read 1 sector at LBA 0: */
|
||||
cdb[0] = 0x28; /* READ(10) */
|
||||
cdb[1] = 0;
|
||||
cdb[2] = 0; cdb[3] = 0; cdb[4] = 0; cdb[5] = 0; /* LBA = 0 */
|
||||
cdb[6] = 0;
|
||||
cdb[7] = 0; cdb[8] = 1; /* transfer length = 1 sector */
|
||||
cdb[9] = 0;
|
||||
/* READ(10) — read 1 sector at LBA 0: */
|
||||
memset(cdb, 0, sizeof(cdb));
|
||||
cdb[0] = 0x28; /* READ(10) opcode */
|
||||
cdb[7] = 0; cdb[8] = 1; /* transfer length = 1 sector */
|
||||
|
||||
scsicmd.scsi_Data = (UWORD *)data;
|
||||
scsicmd.scsi_Length = 512;
|
||||
scsicmd.scsi_Command = cdb;
|
||||
scsicmd.scsi_CmdLength = 10;
|
||||
scsicmd.scsi_Flags = SCSIF_READ;
|
||||
scsicmd.scsi_SenseData = sense;
|
||||
scsicmd.scsi_Data = (UWORD *)data;
|
||||
scsicmd.scsi_Length = 512;
|
||||
scsicmd.scsi_Command = cdb;
|
||||
scsicmd.scsi_CmdLength = 10;
|
||||
scsicmd.scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE;
|
||||
scsicmd.scsi_SenseData = sense;
|
||||
scsicmd.scsi_SenseLength = sizeof(sense);
|
||||
|
||||
scsi->io_Command = HD_SCSICMD;
|
||||
scsi->io_Data = &scsicmd;
|
||||
scsi->io_Length = sizeof(scsicmd);
|
||||
DoIO((struct IORequest *)scsi);
|
||||
diskReq->io_Command = HD_SCSICMD;
|
||||
diskReq->io_Data = &scsicmd;
|
||||
diskReq->io_Length = sizeof(scsicmd);
|
||||
DoIO((struct IORequest *)diskReq);
|
||||
|
||||
if (scsicmd.scsi_Status != 0) {
|
||||
/* SCSI error — check sense data */
|
||||
}
|
||||
if (scsicmd.scsi_Status != 0)
|
||||
Printf("SCSI error: status=%ld, sense key=%ld\n",
|
||||
scsicmd.scsi_Status, sense[2] & 0x0F);
|
||||
```
|
||||
|
||||
### Common SCSI Commands
|
||||
|
||||
| Opcode | Name | CDB | Description |
|
||||
|---|---|---|---|
|
||||
| `0x00` | TEST UNIT READY | 6 | Check device readiness |
|
||||
| `0x03` | REQUEST SENSE | 6 | Get error details |
|
||||
| `0x12` | INQUIRY | 6 | Device identification |
|
||||
| `0x1A` | MODE SENSE(6) | 6 | Read device parameters |
|
||||
| `0x25` | READ CAPACITY | 10 | Get total sectors and sector size |
|
||||
| `0x28` | READ(10) | 10 | Read sectors (32-bit LBA) |
|
||||
| `0x2A` | WRITE(10) | 10 | Write sectors |
|
||||
| `0x35` | SYNCHRONIZE CACHE | 10 | Flush write cache |
|
||||
| `0x43` | READ TOC | 10 | Read CD table of contents |
|
||||
| `0xBE` | READ CD | 12 | Read CD sector (any format) |
|
||||
|
||||
---
|
||||
|
||||
## CD-ROM Specifics
|
||||
|
||||
CD-ROM drives require special handling — they use ATAPI (IDE) or SCSI commands but with CD-specific extensions:
|
||||
|
||||
### Opening a CD-ROM
|
||||
|
||||
```c
|
||||
/* CD-ROM is typically on a different device or unit: */
|
||||
/* A1200 + ATAPI CD: */
|
||||
OpenDevice("scsi.device", 2, req, 0); /* unit 2 = slave on second channel */
|
||||
|
||||
/* External SCSI CD: */
|
||||
OpenDevice("2nd.scsi.device", 3, req, 0); /* SCSI ID 3 */
|
||||
|
||||
/* AmiCDFS or CacheCDFS handles mounting automatically via:
|
||||
DEVS:DOSDrivers/CD0 mountlist entry */
|
||||
```
|
||||
|
||||
### Reading the Table of Contents
|
||||
|
||||
```c
|
||||
UBYTE tocData[804]; /* max TOC size */
|
||||
memset(cdb, 0, 10);
|
||||
cdb[0] = 0x43; /* READ TOC */
|
||||
cdb[6] = 1; /* starting track */
|
||||
cdb[7] = (sizeof(tocData) >> 8) & 0xFF;
|
||||
cdb[8] = sizeof(tocData) & 0xFF;
|
||||
|
||||
scsicmd.scsi_Data = (UWORD *)tocData;
|
||||
scsicmd.scsi_Length = sizeof(tocData);
|
||||
scsicmd.scsi_Command = cdb;
|
||||
scsicmd.scsi_CmdLength = 10;
|
||||
scsicmd.scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE;
|
||||
DoIO((struct IORequest *)diskReq);
|
||||
```
|
||||
|
||||
### Audio CD Playback
|
||||
|
||||
```c
|
||||
/* PLAY AUDIO MSF — play from start to end: */
|
||||
memset(cdb, 0, 10);
|
||||
cdb[0] = 0x47; /* PLAY AUDIO MSF */
|
||||
cdb[3] = 0; /* start minute */
|
||||
cdb[4] = 2; /* start second */
|
||||
cdb[5] = 0; /* start frame */
|
||||
cdb[6] = 60; /* end minute */
|
||||
cdb[7] = 0; /* end second */
|
||||
cdb[8] = 0; /* end frame */
|
||||
|
||||
scsicmd.scsi_Flags = SCSIF_READ;
|
||||
DoIO((struct IORequest *)diskReq);
|
||||
```
|
||||
|
||||
### CD Filesystem Drivers
|
||||
|
||||
| Driver | Type | Features |
|
||||
|---|---|---|
|
||||
| **AmiCDFS** | Commodore (stock) | Basic ISO 9660, slow |
|
||||
| **CacheCDFS** | Third-party (popular) | ISO 9660 + Joliet + RockRidge, caching, fast |
|
||||
| **AsimCDFS** | Third-party | Similar to CacheCDFS, commercial |
|
||||
|
||||
---
|
||||
|
||||
## 64-Bit Addressing (TD64 / NSD)
|
||||
|
||||
For drives larger than 4 GB:
|
||||
|
||||
```c
|
||||
/* TD64 — uses io_Actual for high 32 bits: */
|
||||
diskReq->io_Command = NSCMD_TD_READ64;
|
||||
diskReq->io_Data = buffer;
|
||||
diskReq->io_Length = 512;
|
||||
diskReq->io_Offset = lowOffset; /* low 32 bits */
|
||||
diskReq->io_Actual = highOffset; /* high 32 bits */
|
||||
DoIO((struct IORequest *)diskReq);
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **New Style Device (NSD)** provides a standard way to query 64-bit support: send `NSCMD_DEVICEQUERY` and check the returned command list.
|
||||
|
||||
---
|
||||
|
||||
## MiSTer / FPGA Notes
|
||||
|
||||
| Aspect | Implementation |
|
||||
|---|---|
|
||||
| IDE emulation | MiSTer emulates Gayle IDE — presents virtual ATA backed by SD card `.hdf` |
|
||||
| SCSI emulation | A2091/A3000 SCSI cores map targets to `.hdf` files |
|
||||
| Sector size | Must be 512 bytes — all Amiga drivers assume this |
|
||||
| RDB | Rigid Disk Block at sectors 0–15 — must be present for HDToolBox |
|
||||
| Performance | Virtual IDE is faster than real Gayle (no PIO bottleneck) |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `devices/scsidisk.h`
|
||||
- NDK39: `devices/scsidisk.h`, `devices/trackdisk.h`
|
||||
- ADCD 2.1: scsi.device autodocs
|
||||
- SCSI-2 standard: ANSI X3.131-1994
|
||||
- See also: [trackdisk.md](trackdisk.md) — floppy I/O (shares the same API model)
|
||||
|
|
|
|||
|
|
@ -4,63 +4,132 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`timer.device` provides all timing services on AmigaOS: delays, system clock queries, and high-resolution timestamps. It interfaces with two independent hardware sources — the **CIA timers** (microsecond resolution) and the **vertical blank interrupt** (frame-rate resolution).
|
||||
`timer.device` is the system's central timing service — every delay, timeout, periodic callback, and timestamp on AmigaOS flows through it. Unlike most devices that map to one piece of hardware, timer.device **virtualises** two physical clock sources ([CIA timers](../01_hardware/common/cia_chips.md) and the vertical blank interrupt) into an unlimited number of independent timer requests.
|
||||
|
||||
> For low-level CIA register programming and hardware timer theory, see [CIA Chips — Hardware Reference](../01_hardware/common/cia_chips.md). This article covers the OS-level `timer.device` API that sits on top of that hardware.
|
||||
|
||||
**Key insight**: timer.device is **fully multiplexed**. Any number of tasks can have active timer requests simultaneously — the device maintains a sorted queue of pending requests and satisfies them from the same hardware clocks. There is no "one subscriber per timer" limit.
|
||||
|
||||
---
|
||||
|
||||
## Units
|
||||
|
||||
| Unit | Constant | Resolution | Clock Source | Use Case |
|
||||
|---|---|---|---|---|
|
||||
| 0 | `UNIT_MICROHZ` | ~1.4 µs (E-clock tick) | CIA-A Timer A | Short, precise delays |
|
||||
| 1 | `UNIT_VBLANK` | ~20 ms (PAL) / ~16.7 ms (NTSC) | VBlank interrupt | Long delays, low CPU overhead |
|
||||
| 2 | `UNIT_ECLOCK` | ~1.4 µs | CIA-B Timer A | Highest resolution timing (OS 2.0+) |
|
||||
| 3 | `UNIT_WAITUNTIL` | absolute time | System clock | Wait until specific wall-clock time |
|
||||
| 4 | `UNIT_WAITECLOCK` | E-clock absolute | CIA | Wait until specific E-clock value |
|
||||
|
||||
### Which Unit to Use?
|
||||
## System Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Q["How long is the delay?"] --> SHORT["< 100 ms"]
|
||||
Q --> LONG["> 100 ms"]
|
||||
SHORT --> MICRO["UNIT_MICROHZ<br/>or UNIT_ECLOCK"]
|
||||
LONG --> VBLANK["UNIT_VBLANK<br/>(lower CPU overhead)"]
|
||||
Q --> MEASURE["Need to measure<br/>elapsed time?"]
|
||||
MEASURE --> ECLOCK["ReadEClock()"]
|
||||
subgraph "Hardware"
|
||||
CIAA["CIA-A Timer A/B<br/>709,379 Hz (PAL)<br/>715,909 Hz (NTSC)"]
|
||||
CIAB["CIA-B Timer A/B"]
|
||||
VBLANK["VBlank Interrupt<br/>50 Hz (PAL) / 60 Hz (NTSC)"]
|
||||
end
|
||||
|
||||
subgraph "timer.device (kernel)"
|
||||
UM["UNIT_MICROHZ<br/>queue"] --> CIAA
|
||||
UE["UNIT_ECLOCK<br/>queue"] --> CIAB
|
||||
UV["UNIT_VBLANK<br/>queue"] --> VBLANK
|
||||
UW["UNIT_WAITUNTIL"] --> CIAA
|
||||
UWE["UNIT_WAITECLOCK"] --> CIAB
|
||||
|
||||
CLOCK["System Clock<br/>(seconds since 1978-01-01)"]
|
||||
CIAA --> CLOCK
|
||||
VBLANK --> CLOCK
|
||||
end
|
||||
|
||||
subgraph "User Tasks (unlimited)"
|
||||
T1["App 1: 100ms delay"]
|
||||
T2["App 2: 50ms delay"]
|
||||
T3["App 3: 2s delay"]
|
||||
T4["Game: 20ms periodic"]
|
||||
T5["Audio: 5ms refill"]
|
||||
end
|
||||
|
||||
T1 --> UM
|
||||
T2 --> UM
|
||||
T3 --> UV
|
||||
T4 --> UV
|
||||
T5 --> UE
|
||||
|
||||
style UM fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style UV fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style UE fill:#fff3e0,stroke:#ff9800,color:#333
|
||||
```
|
||||
|
||||
### How Multiplexing Works
|
||||
|
||||
timer.device keeps a **sorted linked list** of pending requests per unit. When a `TR_ADDREQUEST` arrives:
|
||||
|
||||
1. The device calculates the absolute expiry time (current time + requested delay)
|
||||
2. Inserts the request into the sorted queue
|
||||
3. Programs the hardware timer to fire at the **soonest** expiry time
|
||||
4. When the timer interrupt fires, the device scans the queue, replies all expired requests, and reprograms for the next one
|
||||
|
||||
This means 1000 applications can each have an active timer — the device just has one hardware timer firing at the nearest deadline. The overhead is the sorted insertion (O(n) in the worst case, but n is rarely large).
|
||||
|
||||
### Can You Run Out of Timer Resources?
|
||||
|
||||
**Short answer**: timer.device itself never runs out — you can queue an unlimited number of requests. But the **surrounding resources** can be exhausted:
|
||||
|
||||
| Resource | Limit | What Happens When Exhausted |
|
||||
|---|---|---|
|
||||
| **Signal bits** | **32 per task** (and ~16 are reserved by the system) | `CreateMsgPort()` returns NULL because `AllocSignal()` can't find a free bit. You can't create a new MsgPort — but you can share one port across multiple timers (see "Multiple Timers Per Task" below). |
|
||||
| **Memory for IORequests** | Available RAM | `CreateIORequest()` returns NULL. Each `timerequest` is 40 bytes — you'd need thousands to notice. |
|
||||
| **Queue depth (device internal)** | Unbounded linked list | Theoretically infinite. In practice, if you queue 10,000+ pending requests, the O(n) sorted insertion becomes noticeable — each new request must walk the list to find its insertion point. At ~50,000+ the system may feel sluggish. |
|
||||
| **Hardware timers (CIA)** | 4 total (2 per CIA) | **Not your problem.** timer.device owns the hardware timers and virtualises them. You never directly compete for CIA timer resources — the device multiplexes everything internally. |
|
||||
| **VBlank slots** | 1 per frame (50/60 Hz) | Not a resource limit — VBlank fires once per frame regardless of how many UNIT_VBLANK requests are queued. All expired requests are serviced in the same interrupt. |
|
||||
|
||||
**The real bottleneck is signal bits.** A task only has 32, and each `CreateMsgPort` consumes one. If your application already uses signals for windows, ARexx, commodities, and other devices, you may only have 5–10 left. The solution is **sharing a single MsgPort** across multiple timer requests and using `GetMsg()` to distinguish which timer fired (compare the reply message pointer to each `timerequest`).
|
||||
|
||||
> [!TIP]
|
||||
> `OpenDevice("timer.device", ...)` itself never fails — timer.device is always available and has no "maximum open count". You can open it thousands of times. It's `CreateMsgPort` (signal bits) and `CreateIORequest` (memory) that can fail.
|
||||
|
||||
---
|
||||
|
||||
## Units — When to Use What
|
||||
|
||||
| Unit | Constant | Resolution | Clock Source | Best For |
|
||||
|---|---|---|---|---|
|
||||
| 0 | `UNIT_MICROHZ` | ~1.4 µs | CIA-A Timer A | Sub-millisecond delays, benchmarking |
|
||||
| 1 | `UNIT_VBLANK` | 20 ms (PAL) / 16.7 ms (NTSC) | VBlank IRQ | Game loops, UI timeouts, long waits |
|
||||
| 2 | `UNIT_ECLOCK` | ~1.4 µs | CIA-B Timer A | Highest resolution measurement (OS 2.0+) |
|
||||
| 3 | `UNIT_WAITUNTIL` | absolute | System clock | Wake at specific wall-clock time |
|
||||
| 4 | `UNIT_WAITECLOCK` | E-clock ticks | CIA | Wait until specific E-clock count |
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Q["What do you need?"] --> |"< 20ms delay"| MICRO["UNIT_MICROHZ"]
|
||||
Q --> |"> 100ms delay"| VBLANK["UNIT_VBLANK<br/>(lower CPU cost)"]
|
||||
Q --> |"Measure elapsed time"| ECLOCK["ReadEClock()<br/>via UNIT_ECLOCK"]
|
||||
Q --> |"Wake at specific time"| WAITUNTIL["UNIT_WAITUNTIL"]
|
||||
Q --> |"Frame-rate sync"| VBLANK2["UNIT_VBLANK<br/>(natural frame sync)"]
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **UNIT_VBLANK has 20ms granularity** — requesting a 5ms delay will actually wait 0–20ms. For anything shorter than one frame, use UNIT_MICROHZ or UNIT_ECLOCK.
|
||||
|
||||
---
|
||||
|
||||
## Hardware Foundation
|
||||
|
||||
### CIA Timer Internals
|
||||
|
||||
The timing hardware lives in the two CIA (Complex Interface Adapter) chips:
|
||||
|
||||
| CIA | Base | Timer | E-Clock Frequency |
|
||||
| CIA | Base Address | Timer | E-Clock Frequency |
|
||||
|---|---|---|---|
|
||||
| CIA-A | `$BFE001` | Timer A, Timer B | 709,379 Hz (PAL) / 715,909 Hz (NTSC) |
|
||||
| CIA-B | `$BFD000` | Timer A, Timer B | Same |
|
||||
|
||||
The **E-clock** is derived from the system clock ÷ 10 (PAL: 7,093,790 / 10 = 709,379 Hz). Each tick is ~1.4 µs.
|
||||
|
||||
```c
|
||||
/* E-clock ticks per second: */
|
||||
#define ECLOCK_PAL 709379
|
||||
#define ECLOCK_NTSC 715909
|
||||
|
||||
/* Example: 100 ms delay = 70,938 ticks (PAL) */
|
||||
```
|
||||
The **E-clock** is derived from the system clock ÷ 10:
|
||||
- PAL: 7,093,790 Hz / 10 = **709,379 Hz** → tick = **1.410 µs**
|
||||
- NTSC: 7,159,090 Hz / 10 = **715,909 Hz** → tick = **1.397 µs**
|
||||
|
||||
### VBlank Timing
|
||||
|
||||
UNIT_VBLANK piggybacks on the vertical blank interrupt — one tick per video frame:
|
||||
UNIT_VBLANK is synchronised to the display's vertical blank interrupt:
|
||||
|
||||
| Standard | VBlank Rate | Resolution |
|
||||
|---|---|---|
|
||||
| PAL | 50 Hz | 20.0 ms |
|
||||
| NTSC | 60 Hz | 16.7 ms |
|
||||
| Standard | VBlank Rate | Period | Use |
|
||||
|---|---|---|---|
|
||||
| PAL | 50 Hz | 20.0 ms | European systems |
|
||||
| NTSC | 60 Hz | 16.7 ms | American/Japanese systems |
|
||||
|
||||
VBlank is the natural heartbeat of the system — graphics updates, animation, and game logic are traditionally synced to it.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -69,170 +138,186 @@ UNIT_VBLANK piggybacks on the vertical blank interrupt — one tick per video fr
|
|||
```c
|
||||
/* devices/timer.h — NDK39 */
|
||||
struct timeval {
|
||||
ULONG tv_secs; /* seconds */
|
||||
ULONG tv_secs; /* seconds (since midnight 1 Jan 1978) */
|
||||
ULONG tv_micro; /* microseconds (0–999999) */
|
||||
};
|
||||
|
||||
struct timerequest {
|
||||
struct IORequest tr_node;
|
||||
struct timeval tr_time;
|
||||
struct IORequest tr_node; /* standard I/O request header */
|
||||
struct timeval tr_time; /* delay/time value */
|
||||
};
|
||||
/* sizeof(timerequest) = sizeof(IORequest) + 8 */
|
||||
```
|
||||
|
||||
### EClockVal (OS 2.0+)
|
||||
|
||||
```c
|
||||
struct EClockVal {
|
||||
ULONG ev_hi; /* high 32 bits of 64-bit tick counter */
|
||||
ULONG ev_lo; /* low 32 bits */
|
||||
struct EClockVal { /* OS 2.0+ */
|
||||
ULONG ev_hi; /* high 32 bits of 64-bit tick counter */
|
||||
ULONG ev_lo; /* low 32 bits */
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Opening timer.device
|
||||
## Proper Initialisation 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.
|
||||
|
||||
### Opening (The Right Way)
|
||||
|
||||
The correct sequence is: create a MsgPort (for reply signals) → create an IORequest (the "ticket" for timer operations) → open the device (binds the IORequest to the hardware). Each step depends on the previous one, so errors must unwind in reverse order:
|
||||
|
||||
```c
|
||||
struct MsgPort *timerPort = CreateMsgPort();
|
||||
if (!timerPort) { /* handle error */ }
|
||||
|
||||
struct timerequest *tr = (struct timerequest *)
|
||||
CreateIORequest(timerPort, sizeof(struct timerequest));
|
||||
if (!tr) { DeleteMsgPort(timerPort); /* handle error */ }
|
||||
|
||||
BYTE err = OpenDevice("timer.device", UNIT_MICROHZ,
|
||||
(struct IORequest *)tr, 0);
|
||||
if (err != 0) { /* handle error */ }
|
||||
if (err != 0)
|
||||
{
|
||||
DeleteIORequest((struct IORequest *)tr);
|
||||
DeleteMsgPort(timerPort);
|
||||
/* handle error — timer.device should always be available */
|
||||
}
|
||||
|
||||
/* IMPORTANT: after opening, you can get TimerBase for direct calls: */
|
||||
/* After opening, get TimerBase for utility functions: */
|
||||
struct Library *TimerBase = (struct Library *)tr->tr_node.io_Device;
|
||||
/* Now you can call AddTime(), SubTime(), CmpTime(), ReadEClock() */
|
||||
/* Now AddTime(), SubTime(), CmpTime(), ReadEClock() are available */
|
||||
```
|
||||
|
||||
**Why each step is mandatory:**
|
||||
|
||||
| Shortcut | Consequence |
|
||||
|---|---|
|
||||
| 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. |
|
||||
| Not saving `TimerBase` | Can't call `AddTime`/`SubTime`/`CmpTime`/`ReadEClock` — they require the device's library base in A6. |
|
||||
|
||||
### Shutdown (The Right Way)
|
||||
|
||||
Shutdown must drain all pending I/O before freeing anything. The device's internal request queue holds a pointer to your IORequest — if you free it while it's still queued, the next timer interrupt dereferences freed memory.
|
||||
|
||||
```c
|
||||
/* CRITICAL: abort any pending request before closing! */
|
||||
if (!CheckIO((struct IORequest *)tr))
|
||||
{
|
||||
AbortIO((struct IORequest *)tr);
|
||||
WaitIO((struct IORequest *)tr); /* MUST wait even after abort */
|
||||
}
|
||||
|
||||
CloseDevice((struct IORequest *)tr);
|
||||
DeleteIORequest((struct IORequest *)tr);
|
||||
DeleteMsgPort(timerPort);
|
||||
```
|
||||
|
||||
**Why `AbortIO` + `WaitIO` — not just one or the other:**
|
||||
|
||||
- **`AbortIO` alone is not enough**: `AbortIO` marks the request for cancellation, but the device may be in the middle of processing it (e.g., inside the timer interrupt handler). The request isn't truly "done" until the device replies it back to your MsgPort. `WaitIO` collects that reply.
|
||||
- **`WaitIO` alone is not enough**: If you `WaitIO` a request that has 5 minutes left, your task blocks for 5 minutes. `AbortIO` tells the device "cancel this immediately" so `WaitIO` returns right away.
|
||||
- **Skipping both**: The IORequest stays in the device's sorted queue. When the timer fires later, the device writes to your (now freed) IORequest → memory corruption → delayed Guru Meditation, often in an unrelated task.
|
||||
|
||||
> [!WARNING]
|
||||
> **Never call `CloseDevice` with a pending timer request.** This corrupts the device's internal queue and will eventually Guru Meditation. Always `AbortIO` + `WaitIO` first. This is the single most common timer.device bug in Amiga software.
|
||||
|
||||
---
|
||||
|
||||
## Simple Delay
|
||||
## Use Case 1: Simple Blocking Delay
|
||||
|
||||
```c
|
||||
/* Block the current task for exactly 2.5 seconds: */
|
||||
tr->tr_node.io_Command = TR_ADDREQUEST;
|
||||
tr->tr_time.tv_secs = 2;
|
||||
tr->tr_time.tv_micro = 500000; /* 0.5 sec */
|
||||
DoIO((struct IORequest *)tr); /* blocks until done */
|
||||
tr->tr_time.tv_micro = 500000; /* 500ms */
|
||||
DoIO((struct IORequest *)tr);
|
||||
/* Task is now blocked — other tasks run during this time */
|
||||
```
|
||||
|
||||
### Non-Blocking Delay
|
||||
---
|
||||
|
||||
## Use Case 2: Non-Blocking Timeout (UI Pattern)
|
||||
|
||||
The standard Intuition event loop with a timeout — essential for UI applications that need to update periodically:
|
||||
|
||||
```c
|
||||
/* Start delay, continue doing work, then wait: */
|
||||
tr->tr_node.io_Command = TR_ADDREQUEST;
|
||||
tr->tr_time.tv_secs = 0;
|
||||
tr->tr_time.tv_micro = 100000; /* 100 ms */
|
||||
SendIO((struct IORequest *)tr); /* non-blocking */
|
||||
|
||||
/* ... do other work ... */
|
||||
|
||||
/* Check if timer expired: */
|
||||
if (CheckIO((struct IORequest *)tr))
|
||||
{
|
||||
WaitIO((struct IORequest *)tr); /* collect result */
|
||||
/* timer expired */
|
||||
}
|
||||
```
|
||||
|
||||
### Signal-Based Waiting
|
||||
|
||||
```c
|
||||
/* Wait for timer OR user input: */
|
||||
ULONG timerSig = 1L << timerPort->mp_SigBit;
|
||||
/* Classic Intuition event loop with timer: */
|
||||
ULONG timerSig = 1L << timerPort->mp_SigBit;
|
||||
ULONG windowSig = 1L << window->UserPort->mp_SigBit;
|
||||
BOOL timerPending = FALSE;
|
||||
|
||||
/* Start a 1-second timeout: */
|
||||
tr->tr_node.io_Command = TR_ADDREQUEST;
|
||||
tr->tr_time.tv_secs = 1;
|
||||
tr->tr_time.tv_micro = 0;
|
||||
SendIO((struct IORequest *)tr);
|
||||
timerPending = TRUE;
|
||||
|
||||
ULONG sigs = Wait(timerSig | windowSig);
|
||||
if (sigs & timerSig) {
|
||||
WaitIO((struct IORequest *)tr);
|
||||
/* handle timeout */
|
||||
BOOL running = TRUE;
|
||||
while (running)
|
||||
{
|
||||
ULONG sigs = Wait(timerSig | windowSig | SIGBREAKF_CTRL_C);
|
||||
|
||||
/* Handle window events: */
|
||||
if (sigs & windowSig)
|
||||
{
|
||||
struct IntuiMessage *msg;
|
||||
while ((msg = (struct IntuiMessage *)GetMsg(window->UserPort)))
|
||||
{
|
||||
switch (msg->Class)
|
||||
{
|
||||
case IDCMP_CLOSEWINDOW:
|
||||
running = FALSE;
|
||||
break;
|
||||
case IDCMP_GADGETUP:
|
||||
HandleGadget(msg);
|
||||
break;
|
||||
}
|
||||
ReplyMsg((struct Message *)msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle timer expiry: */
|
||||
if (sigs & timerSig)
|
||||
{
|
||||
WaitIO((struct IORequest *)tr);
|
||||
timerPending = FALSE;
|
||||
|
||||
/* --- Update UI clock, animation, status bar, etc. --- */
|
||||
UpdateStatusBar();
|
||||
|
||||
/* Re-arm timer for next second: */
|
||||
tr->tr_node.io_Command = TR_ADDREQUEST;
|
||||
tr->tr_time.tv_secs = 1;
|
||||
tr->tr_time.tv_micro = 0;
|
||||
SendIO((struct IORequest *)tr);
|
||||
timerPending = TRUE;
|
||||
}
|
||||
|
||||
if (sigs & SIGBREAKF_CTRL_C)
|
||||
running = FALSE;
|
||||
}
|
||||
if (sigs & windowSig) {
|
||||
|
||||
/* Clean shutdown: */
|
||||
if (timerPending)
|
||||
{
|
||||
AbortIO((struct IORequest *)tr);
|
||||
WaitIO((struct IORequest *)tr);
|
||||
/* handle window event */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Current Time
|
||||
## Use Case 3: Game/Demo Frame Sync (Periodic Timer)
|
||||
|
||||
```c
|
||||
/* Get system time (wall clock since midnight Jan 1, 1978): */
|
||||
tr->tr_node.io_Command = TR_GETSYSTIME;
|
||||
DoIO((struct IORequest *)tr);
|
||||
Printf("Time: %lu.%06lu seconds since epoch\n",
|
||||
tr->tr_time.tv_secs, tr->tr_time.tv_micro);
|
||||
```
|
||||
|
||||
### Time Arithmetic
|
||||
|
||||
```c
|
||||
/* After opening timer.device and getting TimerBase: */
|
||||
struct timeval t1, t2, diff;
|
||||
|
||||
/* Measure elapsed time: */
|
||||
tr->tr_node.io_Command = TR_GETSYSTIME;
|
||||
DoIO((struct IORequest *)tr);
|
||||
t1 = tr->tr_time;
|
||||
|
||||
/* ... do work ... */
|
||||
|
||||
DoIO((struct IORequest *)tr);
|
||||
t2 = tr->tr_time;
|
||||
|
||||
/* Compute difference: */
|
||||
diff = t2;
|
||||
SubTime(&diff, &t1);
|
||||
Printf("Elapsed: %lu.%06lu s\n", diff.tv_secs, diff.tv_micro);
|
||||
|
||||
/* Compare times: */
|
||||
LONG cmp = CmpTime(&t1, &t2); /* <0: t1<t2, 0: equal, >0: t1>t2 */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## High-Resolution Timing (ReadEClock)
|
||||
|
||||
```c
|
||||
/* Most precise timing available — E-clock resolution: */
|
||||
struct EClockVal start, end;
|
||||
ULONG efreq = ReadEClock(&start); /* returns ticks/second */
|
||||
|
||||
/* ... code to benchmark ... */
|
||||
|
||||
ReadEClock(&end);
|
||||
|
||||
/* Compute elapsed microseconds: */
|
||||
ULONG ticks = end.ev_lo - start.ev_lo; /* assumes <4 billion ticks */
|
||||
ULONG usecs = ticks * 1000000 / efreq;
|
||||
Printf("Elapsed: %lu µs (E-clock freq: %lu Hz)\n", usecs, efreq);
|
||||
```
|
||||
|
||||
| Standard | E-clock Freq | Tick Resolution |
|
||||
|---|---|---|
|
||||
| PAL | 709,379 Hz | ~1.410 µs |
|
||||
| NTSC | 715,909 Hz | ~1.397 µs |
|
||||
|
||||
---
|
||||
|
||||
## Periodic Timer (Game Loop / Audio Refill)
|
||||
|
||||
```c
|
||||
/* Classic pattern: periodic callback using timer.device */
|
||||
#define FRAME_USEC 20000 /* 50 Hz (PAL frame rate) */
|
||||
/* 50 Hz game loop synchronised to PAL frame rate: */
|
||||
#define FRAME_USEC 20000 /* 1/50th second = 20ms */
|
||||
|
||||
void GameLoop(void)
|
||||
{
|
||||
ULONG timerSig = 1L << timerPort->mp_SigBit;
|
||||
|
||||
/* Kick off first timer request: */
|
||||
tr->tr_node.io_Command = TR_ADDREQUEST;
|
||||
tr->tr_time.tv_secs = 0;
|
||||
tr->tr_time.tv_micro = FRAME_USEC;
|
||||
|
|
@ -247,11 +332,13 @@ void GameLoop(void)
|
|||
{
|
||||
WaitIO((struct IORequest *)tr);
|
||||
|
||||
/* --- Game logic here --- */
|
||||
UpdateGame();
|
||||
/* === Frame logic === */
|
||||
ReadInput();
|
||||
UpdatePhysics();
|
||||
RenderFrame();
|
||||
SwapBuffers();
|
||||
|
||||
/* Re-arm timer: */
|
||||
/* Re-arm for next frame: */
|
||||
tr->tr_time.tv_secs = 0;
|
||||
tr->tr_time.tv_micro = FRAME_USEC;
|
||||
SendIO((struct IORequest *)tr);
|
||||
|
|
@ -266,17 +353,169 @@ void GameLoop(void)
|
|||
}
|
||||
```
|
||||
|
||||
> **Demo effects**: For smooth copper-style effects at higher rates (100+ Hz), demos typically bypass timer.device entirely and use direct CIA timer interrupts or copper waits. timer.device is better suited for system-friendly applications.
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
## Use Case 4: Audio Buffer Refill
|
||||
|
||||
```c
|
||||
/* Double-buffered audio playback with timer-driven refill: */
|
||||
#define AUDIO_BUFFER_MS 10 /* refill every 10ms */
|
||||
|
||||
void AudioRefillLoop(void)
|
||||
{
|
||||
ULONG timerSig = 1L << timerPort->mp_SigBit;
|
||||
|
||||
/* Use UNIT_MICROHZ for sub-frame precision: */
|
||||
tr->tr_node.io_Command = TR_ADDREQUEST;
|
||||
tr->tr_time.tv_secs = 0;
|
||||
tr->tr_time.tv_micro = AUDIO_BUFFER_MS * 1000;
|
||||
SendIO((struct IORequest *)tr);
|
||||
|
||||
while (!quit)
|
||||
{
|
||||
Wait(timerSig);
|
||||
WaitIO((struct IORequest *)tr);
|
||||
|
||||
/* Fill the next audio DMA buffer: */
|
||||
FillAudioBuffer(currentBuffer);
|
||||
SwapAudioBuffers();
|
||||
|
||||
/* Re-arm: */
|
||||
tr->tr_time.tv_secs = 0;
|
||||
tr->tr_time.tv_micro = AUDIO_BUFFER_MS * 1000;
|
||||
SendIO((struct IORequest *)tr);
|
||||
}
|
||||
|
||||
AbortIO((struct IORequest *)tr);
|
||||
WaitIO((struct IORequest *)tr);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Case 5: Benchmarking with ReadEClock
|
||||
|
||||
```c
|
||||
/* Precise code benchmarking using E-clock: */
|
||||
struct EClockVal start, end;
|
||||
ULONG efreq = ReadEClock(&start);
|
||||
|
||||
/* --- Code to benchmark --- */
|
||||
SortLargeArray(data, count);
|
||||
/* --- End benchmark --- */
|
||||
|
||||
ReadEClock(&end);
|
||||
|
||||
/* Calculate elapsed microseconds: */
|
||||
ULONG ticks = end.ev_lo - start.ev_lo;
|
||||
ULONG usecs = (ULONG)((UQUAD)ticks * 1000000ULL / efreq);
|
||||
Printf("Elapsed: %lu µs (%lu E-clock ticks at %lu Hz)\n",
|
||||
usecs, ticks, efreq);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Case 6: Getting System Time
|
||||
|
||||
```c
|
||||
/* Read wall-clock time: */
|
||||
tr->tr_node.io_Command = TR_GETSYSTIME;
|
||||
DoIO((struct IORequest *)tr);
|
||||
Printf("Seconds since 1978-01-01: %lu.%06lu\n",
|
||||
tr->tr_time.tv_secs, tr->tr_time.tv_micro);
|
||||
|
||||
/* Time arithmetic: */
|
||||
struct timeval t1, t2, elapsed;
|
||||
/* ... get t1, do work, get t2 ... */
|
||||
elapsed = t2;
|
||||
SubTime(&elapsed, &t1);
|
||||
Printf("Operation took: %lu.%06lu s\n",
|
||||
elapsed.tv_secs, elapsed.tv_micro);
|
||||
|
||||
/* Compare times: */
|
||||
LONG cmp = CmpTime(&t1, &t2);
|
||||
/* Returns: -1 if t1 > t2, 0 if equal, +1 if t1 < t2 */
|
||||
/* WARNING: return values are opposite to strcmp convention! */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multiple Timers Per Task
|
||||
|
||||
A single task can have **multiple simultaneous timer requests** — just use separate `timerequest` structures sharing the same MsgPort:
|
||||
|
||||
```c
|
||||
/* Two independent timers on one port: */
|
||||
struct timerequest *tr_fast = CreateIORequest(port, sizeof(*tr_fast));
|
||||
struct timerequest *tr_slow = CreateIORequest(port, sizeof(*tr_slow));
|
||||
|
||||
OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest *)tr_fast, 0);
|
||||
|
||||
/* Clone the device for the second request: */
|
||||
*tr_slow = *tr_fast; /* copy device/unit/port */
|
||||
|
||||
/* Start both timers: */
|
||||
tr_fast->tr_time.tv_micro = 50000; /* 50ms — UI animation */
|
||||
SendIO((struct IORequest *)tr_fast);
|
||||
|
||||
tr_slow->tr_time.tv_secs = 5; /* 5s — autosave */
|
||||
SendIO((struct IORequest *)tr_slow);
|
||||
|
||||
/* Wait for either: */
|
||||
while (running)
|
||||
{
|
||||
ULONG sigs = Wait(1L << port->mp_SigBit);
|
||||
struct Message *msg;
|
||||
while ((msg = GetMsg(port)))
|
||||
{
|
||||
if (msg == (struct Message *)tr_fast)
|
||||
{
|
||||
/* Fast timer expired — animate */
|
||||
WaitIO((struct IORequest *)tr_fast);
|
||||
AnimateUI();
|
||||
tr_fast->tr_time.tv_micro = 50000;
|
||||
SendIO((struct IORequest *)tr_fast);
|
||||
}
|
||||
else if (msg == (struct Message *)tr_slow)
|
||||
{
|
||||
/* Slow timer expired — autosave */
|
||||
WaitIO((struct IORequest *)tr_slow);
|
||||
AutoSave();
|
||||
tr_slow->tr_time.tv_secs = 5;
|
||||
SendIO((struct IORequest *)tr_slow);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls and Anti-Patterns
|
||||
|
||||
| Pitfall | Problem | Solution |
|
||||
|---|---|---|
|
||||
| Reusing active IORequest | Sending a `TR_ADDREQUEST` while previous is pending | Use two timerequest structs, or `WaitIO` first |
|
||||
| Forgetting `WaitIO` after `AbortIO` | Leaves IORequest in limbo — crash on next use | Always `WaitIO` after `AbortIO`, even if aborted |
|
||||
| Using `UNIT_VBLANK` for short delays | 20 ms granularity — actual delay is 0 to 20 ms | Use `UNIT_MICROHZ` for sub-20ms precision |
|
||||
| Not opening timer for `ReadEClock` | `TimerBase` is NULL — crash | Must `OpenDevice` first to get `TimerBase` |
|
||||
| Ignoring PAL/NTSC differences | Hardcoded periods wrong on other standard | Use `ReadEClock()` frequency for calculations |
|
||||
| **Reusing active IORequest** | `SendIO` while previous is pending → queue corruption | Use two `timerequest` structs, or `WaitIO` first |
|
||||
| **Missing `WaitIO` after `AbortIO`** | IORequest in limbo — crash on next use | **Always** `WaitIO` after `AbortIO`, even if aborted |
|
||||
| **Using `UNIT_VBLANK` for short delays** | 20ms granularity — actual delay is 0–20ms | Use `UNIT_MICROHZ` for sub-20ms precision |
|
||||
| **Not opening timer for `ReadEClock`** | `TimerBase` is NULL — immediate crash | Must `OpenDevice` first to get `TimerBase` |
|
||||
| **Hardcoded PAL/NTSC values** | Wrong timing on the other standard | Use `ReadEClock()` frequency for calculations |
|
||||
| **Polling in a loop instead of `Wait()`** | Burns 100% CPU for no benefit | Use signal-based `Wait()` — CPU sleeps until timer fires |
|
||||
| **Forgetting to abort on shutdown** | Device queue contains pointer to freed memory | Always `AbortIO`+`WaitIO` before `CloseDevice` |
|
||||
| **Using Delay() for everything** | `Delay()` has 20ms granularity and blocks the process | Use `SendIO` + signals for responsive apps |
|
||||
|
||||
### The `Delay()` Trap
|
||||
|
||||
`dos.library/Delay()` uses timer.device internally, but:
|
||||
- Fixed to UNIT_VBLANK (20ms granularity)
|
||||
- Blocks the entire process (no signal checking possible)
|
||||
- Takes ticks, not milliseconds: `Delay(50)` = 1 second (at 50 Hz)
|
||||
|
||||
```c
|
||||
/* DON'T use Delay() in event loops — use timer.device directly */
|
||||
Delay(25); /* blocks for 0.5s — can't handle window events during this! */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -286,3 +525,5 @@ void GameLoop(void)
|
|||
- ADCD 2.1: timer.device autodocs
|
||||
- HRM: CIA timer chapter
|
||||
- See also: [interrupts.md](../06_exec_os/interrupts.md) — VBlank interrupt chain
|
||||
- See also: [multitasking.md](../06_exec_os/multitasking.md) — task scheduling and signals
|
||||
- See also: [audio.md](audio.md) — audio buffer timing
|
||||
|
|
|
|||
|
|
@ -2,18 +2,20 @@
|
|||
|
||||
# Common Libraries — Overview
|
||||
|
||||
Shared libraries beyond the core exec/dos/graphics/intuition subsystems. These provide utility functions, file format parsing, hardware expansion support, and Workbench integration.
|
||||
|
||||
## Section Index
|
||||
|
||||
| File | Description |
|
||||
|---|---|
|
||||
| [utility.md](utility.md) | utility.library — TagItem, hooks, date |
|
||||
| [expansion.md](expansion.md) | expansion.library — Zorro/AutoConfig |
|
||||
| [icon.md](icon.md) | icon.library — Workbench icons (.info) |
|
||||
| [workbench.md](workbench.md) | workbench.library — Workbench integration |
|
||||
| [iffparse.md](iffparse.md) | iffparse.library — IFF file parsing |
|
||||
| [locale.md](locale.md) | locale.library — internationalisation |
|
||||
| [keymap.md](keymap.md) | keymap.library — keyboard mapping |
|
||||
| [rexxsyslib.md](rexxsyslib.md) | rexxsyslib.library — ARexx interface |
|
||||
| [mathffp.md](mathffp.md) | mathffp/mathieeesingbas — floating point |
|
||||
| [layers.md](layers.md) | layers.library — window clipping layers |
|
||||
| [diskfont.md](diskfont.md) | diskfont.library — disk-based fonts |
|
||||
| [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 |
|
||||
| [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 |
|
||||
| [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+) |
|
||||
|
|
|
|||
|
|
@ -1,10 +1,48 @@
|
|||
[← Home](../README.md) · [Libraries](README.md)
|
||||
|
||||
# diskfont.library — Disk-Based Fonts
|
||||
# diskfont.library — Disk-Based Font Loading
|
||||
|
||||
## Overview
|
||||
|
||||
`diskfont.library` loads bitmap fonts from disk (the `FONTS:` assign). ROM fonts (topaz 8, topaz 9) are always available; all others require this library.
|
||||
`diskfont.library` loads bitmap fonts from disk (the `FONTS:` assign). Only two fonts are built into ROM — **topaz 8** and **topaz 9**. All other fonts (helvetica, times, courier, etc.) must be loaded from disk via this library.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
APP["Application"] -->|"OpenDiskFont(&ta)"| DFL["diskfont.library"]
|
||||
DFL -->|"Scans FONTS: directory"| FONTS["FONTS:<br/>helvetica.font<br/>helvetica/24"]
|
||||
DFL -->|"Returns loaded font"| TF["struct TextFont"]
|
||||
TF -->|"SetFont(rp, font)"| RP["RastPort"]
|
||||
|
||||
ROM["ROM"] -->|"OpenFont(&ta)"| TOPAZ["topaz 8/9<br/>(always available)"]
|
||||
|
||||
style DFL fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style FONTS fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Font Directory Structure
|
||||
|
||||
Amiga bitmap fonts are stored as a descriptor file plus per-size data files:
|
||||
|
||||
```
|
||||
FONTS:
|
||||
helvetica.font ← font descriptor (FontContents header)
|
||||
helvetica/
|
||||
9 ← bitmap data for 9-pixel height
|
||||
11 ← bitmap data for 11-pixel height
|
||||
13 ← bitmap data for 13-pixel height
|
||||
18 ← bitmap data for 18-pixel height
|
||||
24 ← bitmap data for 24-pixel height
|
||||
times.font
|
||||
times/
|
||||
11
|
||||
13
|
||||
18
|
||||
24
|
||||
```
|
||||
|
||||
The `.font` descriptor file contains a `FontContentsHeader` listing all available sizes, styles, and flags.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -13,45 +51,106 @@
|
|||
```c
|
||||
struct Library *DiskfontBase = OpenLibrary("diskfont.library", 0);
|
||||
|
||||
/* Request a specific font and size: */
|
||||
struct TextAttr ta = {"helvetica.font", 24, 0, 0};
|
||||
struct TextFont *font = OpenDiskFont(&ta);
|
||||
if (font) {
|
||||
|
||||
if (font)
|
||||
{
|
||||
SetFont(rp, font);
|
||||
/* ... render ... */
|
||||
Move(rp, 10, 30);
|
||||
Text(rp, "Disk Font Text", 14);
|
||||
|
||||
/* When done with the font: */
|
||||
CloseFont(font);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Font not found — fall back to ROM font: */
|
||||
struct TextAttr fallback = {"topaz.font", 8, 0, FPF_ROMFONT};
|
||||
struct TextFont *topaz = OpenFont(&fallback);
|
||||
SetFont(rp, topaz);
|
||||
}
|
||||
|
||||
CloseLibrary(DiskfontBase);
|
||||
```
|
||||
|
||||
---
|
||||
### Size Matching
|
||||
|
||||
## Font Directory Structure
|
||||
|
||||
```
|
||||
FONTS:
|
||||
helvetica.font ← font descriptor file
|
||||
helvetica/
|
||||
24 ← bitmap data for size 24
|
||||
11 ← bitmap data for size 11
|
||||
```
|
||||
If the exact requested size is not available, `OpenDiskFont` returns NULL. To find the nearest available size, use `AvailFonts` to enumerate, then request the closest match.
|
||||
|
||||
---
|
||||
|
||||
## Listing Available Fonts
|
||||
## Enumerating Available Fonts
|
||||
|
||||
```c
|
||||
struct List *fontList = NULL;
|
||||
LONG count = AvailFonts(buf, bufsize, AFF_DISK | AFF_MEMORY);
|
||||
/* AvailFonts returns all fonts in ROM and on disk: */
|
||||
LONG bufSize = 4096;
|
||||
APTR buf = AllocMem(bufSize, MEMF_ANY | MEMF_CLEAR);
|
||||
|
||||
LONG shortfall = AvailFonts(buf, bufSize, AFF_DISK | AFF_MEMORY);
|
||||
if (shortfall > 0)
|
||||
{
|
||||
/* Buffer too small — reallocate and retry: */
|
||||
FreeMem(buf, bufSize);
|
||||
bufSize += shortfall;
|
||||
buf = AllocMem(bufSize, MEMF_ANY | MEMF_CLEAR);
|
||||
AvailFonts(buf, bufSize, AFF_DISK | AFF_MEMORY);
|
||||
}
|
||||
|
||||
struct AvailFontsHeader *afh = (struct AvailFontsHeader *)buf;
|
||||
struct AvailFonts *af = (struct AvailFonts *)&afh[1];
|
||||
for (int i = 0; i < afh->afh_NumEntries; i++) {
|
||||
Printf("%s %ld\n", af[i].af_Attr.ta_Name, af[i].af_Attr.ta_YSize);
|
||||
|
||||
for (int i = 0; i < afh->afh_NumEntries; i++)
|
||||
{
|
||||
Printf("%s size %-3ld %s\n",
|
||||
af[i].af_Attr.ta_Name,
|
||||
af[i].af_Attr.ta_YSize,
|
||||
(af[i].af_Type & AFF_DISK) ? "disk" :
|
||||
(af[i].af_Type & AFF_MEMORY) ? "ROM" : "scaled");
|
||||
}
|
||||
|
||||
FreeMem(buf, bufSize);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Font Types
|
||||
|
||||
| Flag | Source | Notes |
|
||||
|---|---|---|
|
||||
| `AFF_MEMORY` | ROM or already loaded in memory | topaz, or previously opened disk fonts |
|
||||
| `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 |
|
||||
|
||||
---
|
||||
|
||||
## Colour Fonts (OS 3.0+)
|
||||
|
||||
OS 3.0 introduced **colour bitmap fonts** — each glyph can have multiple bitplanes:
|
||||
|
||||
```c
|
||||
/* Colour 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 */
|
||||
APTR ctf_PlanePick; /* plane selection */
|
||||
APTR ctf_PlaneOnOff; /* plane on/off defaults */
|
||||
struct ColorFontColors *ctf_ColorTable;
|
||||
APTR ctf_CharData[8]; /* per-plane glyph data */
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `diskfont/diskfont.h`
|
||||
- NDK39: `diskfont/diskfont.h`, `graphics/text.h`
|
||||
- ADCD 2.1: diskfont.library autodocs
|
||||
- See also: [text_fonts.md](../08_graphics/text_fonts.md) — TextFont structure and rendering
|
||||
|
|
|
|||
|
|
@ -4,18 +4,93 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`expansion.library` handles automatic configuration of Zorro II/III expansion boards. At boot, the OS scans the expansion bus and assigns base addresses to each board based on its AutoConfig ROM.
|
||||
`expansion.library` handles automatic configuration of Zorro II/III expansion boards. At boot, the OS scans the expansion bus and assigns base addresses to each board based on its **AutoConfig ROM** — a 256-byte structure that identifies the board's manufacturer, product, memory requirements, and bus type.
|
||||
|
||||
Understanding AutoConfig is essential for FPGA core development — MiSTer cores that emulate expansion hardware (RAM boards, accelerators, RTG cards) must present valid AutoConfig data to the boot ROM.
|
||||
|
||||
---
|
||||
|
||||
## Zorro Bus Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Zorro II Address Space (8 MB)"
|
||||
Z2["$200000–$9FFFFF"]
|
||||
Z2A["Board A: $200000 (2 MB)"]
|
||||
Z2B["Board B: $400000 (512 KB)"]
|
||||
Z2C["Board C: $480000 (64 KB)"]
|
||||
end
|
||||
|
||||
subgraph "Zorro III Address Space (1.75 GB)"
|
||||
Z3["$10000000–$7FFFFFFF"]
|
||||
Z3A["Board D: $40000000 (16 MB)"]
|
||||
end
|
||||
|
||||
CPU["68020/030/040"] --> Z2
|
||||
CPU --> Z3
|
||||
```
|
||||
|
||||
| Feature | Zorro II | Zorro III |
|
||||
|---|---|---|
|
||||
| Bus width | 16-bit | 32-bit |
|
||||
| Address space | $200000–$9FFFFF (8 MB) | $10000000–$7FFFFFFF |
|
||||
| Max board size | 8 MB | 1 GB |
|
||||
| Burst transfer | No | Yes (37 MB/s peak) |
|
||||
| Auto-sizing | No | Yes (dynamic) |
|
||||
| DMA capable | Yes | Yes |
|
||||
| Systems | A2000, A500, A1200 | A3000, A4000 |
|
||||
|
||||
---
|
||||
|
||||
## AutoConfig Sequence
|
||||
|
||||
1. Board asserts `CFGIN` to request configuration
|
||||
2. CPU reads 256-byte config area at `$E80000`
|
||||
3. Board reports: manufacturer ID, product ID, board size, flags
|
||||
4. OS assigns a base address via `WriteExpansionByte`
|
||||
5. Board relocates to assigned address
|
||||
6. Next board in chain is configured
|
||||
The AutoConfig mechanism runs during early boot, before DOS is loaded:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ROM as Kickstart ROM
|
||||
participant BUS as Zorro Bus
|
||||
participant BOARD as Expansion Board
|
||||
|
||||
ROM->>BUS: Assert CFGOUT chain
|
||||
BOARD->>BUS: Assert CFGIN (ready to configure)
|
||||
ROM->>BOARD: Read config area at $E80000
|
||||
Note over ROM: Parse manufacturer, product, size, type
|
||||
ROM->>BOARD: Write assigned base address
|
||||
Note over BOARD: Board relocates to assigned address
|
||||
ROM->>BUS: Pass CFGIN to next board
|
||||
Note over ROM: Repeat until no more boards respond
|
||||
```
|
||||
|
||||
### AutoConfig ROM Layout ($E80000)
|
||||
|
||||
The board presents its identity at the configuration address. Each byte is read from **even** addresses only ($E80000, $E80002, $E80004...):
|
||||
|
||||
| Offset | Register | Bits | Description |
|
||||
|---|---|---|---|
|
||||
| $00 | `er_Type` | 7:6 | Board type: 11=Zorro III, 10=Zorro II |
|
||||
| $00 | `er_Type` | 5 | Memory board (1) or I/O board (0) |
|
||||
| $00 | `er_Type` | 4 | Chain bit — more boards follow |
|
||||
| $00 | `er_Type` | 3:0 | Size code (see table below) |
|
||||
| $02 | `er_Product` | 7:0 | Product number (0–255) |
|
||||
| $04 | `er_Flags` | 7 | Can be shut up (mapped out) |
|
||||
| $04 | `er_Flags` | 5 | Board's memory is free (add to system pool) |
|
||||
| $08/$0A | `er_Manufacturer` | 15:0 | Manufacturer ID (IANA-like registry) |
|
||||
| $0C–$12 | `er_SerialNumber` | 31:0 | Serial number (unique per board) |
|
||||
| $20/$22 | `er_InitDiagVec` | 15:0 | Offset to optional boot ROM (DiagArea) |
|
||||
|
||||
### Size Code
|
||||
|
||||
| Code | Zorro II Size | Zorro III Size |
|
||||
|---|---|---|
|
||||
| $0 | 8 MB | 16 MB |
|
||||
| $1 | 64 KB | 32 MB |
|
||||
| $2 | 128 KB | 64 MB |
|
||||
| $3 | 256 KB | 128 MB |
|
||||
| $4 | 512 KB | 256 MB |
|
||||
| $5 | 1 MB | 512 MB |
|
||||
| $6 | 2 MB | 1 GB |
|
||||
| $7 | 4 MB | — |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -25,27 +100,27 @@
|
|||
/* libraries/configvars.h — NDK39 */
|
||||
struct ConfigDev {
|
||||
struct Node cd_Node;
|
||||
UBYTE cd_Flags; /* CDF_* flags */
|
||||
UBYTE cd_Flags; /* CDF_CONFIGME, CDF_BADMEMORY, etc. */
|
||||
UBYTE cd_Pad;
|
||||
struct ExpansionRom cd_Rom; /* AutoConfig ROM data */
|
||||
struct ExpansionRom cd_Rom; /* AutoConfig ROM data (copied) */
|
||||
APTR cd_BoardAddr; /* assigned base address */
|
||||
ULONG cd_BoardSize; /* board size in bytes */
|
||||
UWORD cd_SlotAddr; /* slot address */
|
||||
UWORD cd_SlotSize;
|
||||
APTR cd_Driver; /* driver bound to this board */
|
||||
struct ConfigDev *cd_NextCD; /* next in chain */
|
||||
/* ... */
|
||||
};
|
||||
|
||||
struct ExpansionRom {
|
||||
UBYTE er_Type; /* board type + size code */
|
||||
UBYTE er_Product; /* product number */
|
||||
UBYTE er_Flags;
|
||||
UBYTE er_Product; /* product number (0–255) */
|
||||
UBYTE er_Flags; /* can shut up, has memory, etc. */
|
||||
UBYTE er_Reserved03;
|
||||
UWORD er_Manufacturer; /* manufacturer ID */
|
||||
ULONG er_SerialNumber;
|
||||
UWORD er_InitDiagVec; /* boot ROM offset */
|
||||
/* ... */
|
||||
UWORD er_Manufacturer; /* manufacturer ID (16-bit) */
|
||||
ULONG er_SerialNumber; /* board serial number */
|
||||
UWORD er_InitDiagVec; /* offset to DiagArea boot ROM */
|
||||
APTR er_Reserved0c;
|
||||
APTR er_Reserved10;
|
||||
};
|
||||
```
|
||||
|
||||
|
|
@ -57,9 +132,24 @@ struct ExpansionRom {
|
|||
struct Library *ExpansionBase = OpenLibrary("expansion.library", 0);
|
||||
struct ConfigDev *cd = NULL;
|
||||
|
||||
while ((cd = FindConfigDev(cd, 2167, 11))) {
|
||||
/* manufacturer=2167 (Individual Computers), product=11 (ACA500plus) */
|
||||
Printf("Board at $%08lx, size %lu\n", cd->cd_BoardAddr, cd->cd_BoardSize);
|
||||
/* Find all boards from a specific manufacturer+product: */
|
||||
while ((cd = FindConfigDev(cd, 2167, 11)))
|
||||
{
|
||||
Printf("Board at $%08lx, size %lu bytes\n",
|
||||
cd->cd_BoardAddr, cd->cd_BoardSize);
|
||||
Printf(" Manufacturer: %u, Product: %u\n",
|
||||
cd->cd_Rom.er_Manufacturer, cd->cd_Rom.er_Product);
|
||||
}
|
||||
|
||||
/* Find ANY board: use -1 for wildcard */
|
||||
cd = NULL;
|
||||
while ((cd = FindConfigDev(cd, -1, -1)))
|
||||
{
|
||||
Printf("Mfr=%u Prod=%u at $%08lx (%lu bytes)\n",
|
||||
cd->cd_Rom.er_Manufacturer,
|
||||
cd->cd_Rom.er_Product,
|
||||
cd->cd_BoardAddr,
|
||||
cd->cd_BoardSize);
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -67,12 +157,54 @@ while ((cd = FindConfigDev(cd, 2167, 11))) {
|
|||
|
||||
## Common Manufacturer IDs
|
||||
|
||||
| ID | Manufacturer |
|
||||
| ID | Manufacturer | Notable Products |
|
||||
|---|---|---|
|
||||
| 514 | Commodore | A2091 SCSI, A2065 Ethernet, A2232 serial |
|
||||
| 1030 | Supra | SupraRAM, SupraDrive |
|
||||
| 2017 | GVP | Impact A2000, Series II SCSI+RAM |
|
||||
| 2167 | Individual Computers | Buddha, ACA500+, ACA1233 |
|
||||
| 2168 | Kupke | Golem RAM |
|
||||
| 4096 | University of Lowell | — |
|
||||
| 4626 | ACT | Apollo accelerators |
|
||||
| 4754 | MacroSystem | Retina, Warp Engine |
|
||||
| 8512 | Phase5 | CyberStorm, Blizzard, CyberVision |
|
||||
| 12802 | Village Tronic | Picasso II, Picasso IV |
|
||||
|
||||
---
|
||||
|
||||
## DiagArea — Boot ROMs on Expansion Boards
|
||||
|
||||
If `er_InitDiagVec` is non-zero, the board has a boot ROM that runs during expansion configuration:
|
||||
|
||||
```c
|
||||
struct DiagArea {
|
||||
UBYTE da_Config; /* DAC_WORDWIDE, DAC_BYTEWIDE, etc. */
|
||||
UBYTE da_Flags; /* DAC_CONFIGTIME or DAC_BINDTIME */
|
||||
UWORD da_Size; /* total size of DiagArea */
|
||||
UWORD da_DiagPoint; /* offset to diagnostic routine */
|
||||
UWORD da_BootPoint; /* offset to boot code */
|
||||
char da_Name[]; /* optional handler name */
|
||||
};
|
||||
```
|
||||
|
||||
Boot ROMs are used by:
|
||||
- SCSI controllers (to boot from hard disk)
|
||||
- Network cards (to boot via TFTP)
|
||||
- Accelerator boards (to patch CPU-specific features)
|
||||
|
||||
---
|
||||
|
||||
## FPGA Implementation Notes
|
||||
|
||||
For MiSTer or other FPGA cores emulating Zorro expansion:
|
||||
|
||||
| Aspect | Requirement |
|
||||
|---|---|
|
||||
| 514 | Commodore |
|
||||
| 2017 | GVP |
|
||||
| 2167 | Individual Computers |
|
||||
| 8512 | Phase5 |
|
||||
| AutoConfig ROM | Must present valid er_Type, er_Product, er_Manufacturer at $E80000 |
|
||||
| Address assignment | Must accept base address write and relocate accordingly |
|
||||
| CFGIN/CFGOUT chain | Must implement daisy-chain protocol for multi-board configs |
|
||||
| Memory type flag | Set `er_Flags` bit 5 if board memory should be added to system pool |
|
||||
| Shut-up | Board must go silent after configuration (stop responding at $E80000) |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -80,3 +212,6 @@ while ((cd = FindConfigDev(cd, 2167, 11))) {
|
|||
|
||||
- NDK39: `libraries/configvars.h`, `libraries/expansion.h`
|
||||
- ADCD 2.1: expansion.library autodocs
|
||||
- Dave Haynie: *"The Amiga Zorro III Bus Specification"* — definitive bus reference
|
||||
- See also: [rtg_driver.md](../16_driver_development/rtg_driver.md) — RTG cards use Zorro AutoConfig
|
||||
- See also: [device_driver_basics.md](../16_driver_development/device_driver_basics.md) — driver binding to ConfigDev
|
||||
|
|
|
|||
|
|
@ -4,7 +4,28 @@
|
|||
|
||||
## Overview
|
||||
|
||||
Every Workbench-visible file has a companion `.info` file containing its icon imagery, tooltypes, and default tool. `icon.library` provides reading/writing these structures.
|
||||
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.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Disk"
|
||||
FILE["myapp"]
|
||||
INFO["myapp.info"]
|
||||
end
|
||||
|
||||
subgraph "icon.library"
|
||||
GET["GetDiskObject"] --> DO["struct DiskObject"]
|
||||
DO --> IMG["Icon imagery<br/>(selected + unselected)"]
|
||||
DO --> TT["ToolTypes<br/>(key=value strings)"]
|
||||
DO --> DT["DefaultTool<br/>(path to handler)"]
|
||||
DO --> POS["Position<br/>(x, y on Workbench)"]
|
||||
end
|
||||
|
||||
INFO --> GET
|
||||
style DO fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -14,16 +35,16 @@ Every Workbench-visible file has a companion `.info` file containing its icon im
|
|||
/* workbench/workbench.h — NDK39 */
|
||||
struct DiskObject {
|
||||
UWORD do_Magic; /* $E310 = WB_DISKMAGIC */
|
||||
UWORD do_Version; /* WB_DISKVERSION */
|
||||
struct Gadget do_Gadget; /* the icon gadget */
|
||||
UBYTE do_Type; /* WBDISK, WBTOOL, WBPROJECT, etc. */
|
||||
UWORD do_Version; /* WB_DISKVERSION (1) */
|
||||
struct Gadget do_Gadget; /* the icon gadget (contains imagery) */
|
||||
UBYTE do_Type; /* icon type: WBDISK, WBTOOL, etc. */
|
||||
char *do_DefaultTool; /* default tool path */
|
||||
char **do_ToolTypes; /* NULL-terminated string array */
|
||||
LONG do_CurrentX; /* icon X position */
|
||||
LONG do_CurrentX; /* icon X position on Workbench */
|
||||
LONG do_CurrentY; /* icon Y position */
|
||||
BPTR do_DrawerData; /* drawer window position (BPTR) */
|
||||
char *do_ToolWindow; /* tool window spec */
|
||||
LONG do_StackSize; /* stack size for tool */
|
||||
char *do_ToolWindow; /* tool window spec (CON: string) */
|
||||
LONG do_StackSize; /* stack size for tool launch */
|
||||
};
|
||||
```
|
||||
|
||||
|
|
@ -31,36 +52,137 @@ struct DiskObject {
|
|||
|
||||
## Icon Types
|
||||
|
||||
| Constant | Value | Description |
|
||||
|---|---|---|
|
||||
| `WBDISK` | 1 | Disk/volume icon |
|
||||
| `WBDRAWER` | 2 | Drawer (directory) |
|
||||
| `WBTOOL` | 3 | Executable tool |
|
||||
| `WBPROJECT` | 4 | Project (document) |
|
||||
| `WBGARBAGE` | 5 | Trashcan |
|
||||
| `WBDEVICE` | 6 | Device |
|
||||
| `WBKICK` | 7 | Kickstart disk |
|
||||
| `WBAPPICON` | 8 | AppIcon (OS 2.0+) |
|
||||
| Constant | Value | Description | Workbench Behaviour |
|
||||
|---|---|---|---|
|
||||
| `WBDISK` | 1 | Disk/volume icon | Opens drawer showing disk contents |
|
||||
| `WBDRAWER` | 2 | Drawer (directory) | Opens drawer window |
|
||||
| `WBTOOL` | 3 | Executable tool | Launches the program |
|
||||
| `WBPROJECT` | 4 | Project (document) | Launches `do_DefaultTool` with this file as argument |
|
||||
| `WBGARBAGE` | 5 | Trashcan | Special drawer for deleted files |
|
||||
| `WBDEVICE` | 6 | Device | Shown in Workbench root |
|
||||
| `WBKICK` | 7 | Kickstart disk | ROM update disk |
|
||||
| `WBAPPICON` | 8 | AppIcon (OS 2.0+) | Application-registered dynamic icon |
|
||||
|
||||
---
|
||||
|
||||
## ToolTypes
|
||||
|
||||
ToolTypes are key=value strings stored in `do_ToolTypes`:
|
||||
## Reading Icons
|
||||
|
||||
```c
|
||||
struct Library *IconBase = OpenLibrary("icon.library", 0);
|
||||
|
||||
/* Load a DiskObject from disk: */
|
||||
struct DiskObject *dobj = GetDiskObject("SYS:Utilities/MultiView");
|
||||
if (dobj)
|
||||
{
|
||||
Printf("Type: %ld\n", dobj->do_Type);
|
||||
Printf("Default tool: %s\n", dobj->do_DefaultTool ?
|
||||
dobj->do_DefaultTool : "(none)");
|
||||
Printf("Stack: %ld bytes\n", dobj->do_StackSize);
|
||||
|
||||
FreeDiskObject(dobj);
|
||||
}
|
||||
|
||||
/* Get the default icon for a file type: */
|
||||
struct DiskObject *defIcon = GetDefDiskObject(WBPROJECT);
|
||||
/* ... use defIcon ... */
|
||||
FreeDiskObject(defIcon);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ToolTypes — Key/Value Metadata
|
||||
|
||||
ToolTypes are the Amiga's equivalent of application-specific metadata. They're stored as a NULL-terminated array of strings in the `.info` file.
|
||||
|
||||
```c
|
||||
/* Example .info file ToolTypes:
|
||||
PUBSCREEN=Workbench
|
||||
NOBLITTER=YES
|
||||
DONOTWAIT
|
||||
CX_PRIORITY=0
|
||||
(DISABLED_OPTION)
|
||||
*/
|
||||
|
||||
struct DiskObject *dobj = GetDiskObject("myapp");
|
||||
if (dobj) {
|
||||
char *val = FindToolType(dobj->do_ToolTypes, "PUBSCREEN");
|
||||
if (val) Printf("Screen: %s\n", val);
|
||||
if (MatchToolValue(FindToolType(dobj->do_ToolTypes, "FLAGS"), "DEBUG"))
|
||||
Printf("Debug mode\n");
|
||||
if (dobj)
|
||||
{
|
||||
/* Find a specific ToolType: */
|
||||
char *screen = FindToolType(dobj->do_ToolTypes, "PUBSCREEN");
|
||||
if (screen)
|
||||
Printf("Public screen: %s\n", screen); /* "Workbench" */
|
||||
|
||||
/* Check if a ToolType exists (no value): */
|
||||
if (FindToolType(dobj->do_ToolTypes, "DONOTWAIT"))
|
||||
Printf("DONOTWAIT is set\n");
|
||||
|
||||
/* Check for a specific value within a multi-value ToolType: */
|
||||
char *flags = FindToolType(dobj->do_ToolTypes, "FLAGS");
|
||||
if (MatchToolValue(flags, "DEBUG"))
|
||||
Printf("Debug mode enabled\n");
|
||||
|
||||
/* Enumerate all ToolTypes: */
|
||||
char **tt = dobj->do_ToolTypes;
|
||||
while (*tt)
|
||||
{
|
||||
Printf(" ToolType: %s\n", *tt);
|
||||
tt++;
|
||||
}
|
||||
|
||||
FreeDiskObject(dobj);
|
||||
}
|
||||
```
|
||||
|
||||
### ToolType Conventions
|
||||
|
||||
| Convention | Meaning | Example |
|
||||
|---|---|---|
|
||||
| `KEY=VALUE` | Named setting with value | `PUBSCREEN=Workbench` |
|
||||
| `KEY` (no value) | Boolean flag — presence = true | `DONOTWAIT` |
|
||||
| `(KEY)` | Disabled/commented out | `(CX_POPUP=NO)` |
|
||||
| `KEY=val1|val2` | Multi-value (check with `MatchToolValue`) | `FLAGS=DEBUG|VERBOSE` |
|
||||
|
||||
---
|
||||
|
||||
## Writing / Modifying Icons
|
||||
|
||||
```c
|
||||
/* Write a DiskObject to disk: */
|
||||
PutDiskObject("myfile", dobj);
|
||||
|
||||
/* Create a new icon programmatically: */
|
||||
struct DiskObject *newIcon = GetDefDiskObject(WBPROJECT);
|
||||
newIcon->do_DefaultTool = "SYS:Utilities/MultiView";
|
||||
newIcon->do_StackSize = 8192;
|
||||
|
||||
char *toolTypes[] = {
|
||||
"PUBSCREEN=Workbench",
|
||||
"DONOTWAIT",
|
||||
NULL
|
||||
};
|
||||
newIcon->do_ToolTypes = toolTypes;
|
||||
|
||||
PutDiskObject("myfile", newIcon);
|
||||
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.
|
||||
|
||||
| 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 |
|
||||
| Size | Fixed (standard sizes) | Scalable |
|
||||
| Transparency | 1-bit mask | 8-bit alpha channel |
|
||||
| Storage | `do_Gadget.GadgetRender` | Extended chunks in `.info` |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `workbench/workbench.h`, `workbench/icon.h`
|
||||
- ADCD 2.1: icon.library autodocs
|
||||
- See also: [workbench.md](workbench.md) — Workbench integration (AppIcon, AppWindow)
|
||||
|
|
|
|||
|
|
@ -4,53 +4,124 @@
|
|||
|
||||
## Overview
|
||||
|
||||
IFF (Interchange File Format) is EA/Commodore's universal container format. `iffparse.library` provides stream-oriented parsing/writing of IFF files. Common IFF types: ILBM (images), 8SVX (audio), ANIM (animation), FTXT (formatted text).
|
||||
IFF (Interchange File Format) is EA/Commodore's universal container format used throughout the Amiga ecosystem. `iffparse.library` provides stream-oriented parsing and writing of IFF files, handling the nested chunk structure, byte ordering, and padding automatically.
|
||||
|
||||
Common IFF types:
|
||||
- **ILBM** — Interleaved Bitmap (images)
|
||||
- **8SVX** — 8-bit sampled voice (audio)
|
||||
- **ANIM** — Animation sequences
|
||||
- **FTXT** — Formatted text
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "IFF ILBM File"
|
||||
FORM["FORM ILBM"] --> BMHD["BMHD<br/>(bitmap header)"]
|
||||
FORM --> CMAP["CMAP<br/>(colour palette)"]
|
||||
FORM --> CAMG["CAMG<br/>(Amiga view mode)"]
|
||||
FORM --> BODY["BODY<br/>(pixel data)"]
|
||||
end
|
||||
|
||||
subgraph "iffparse.library"
|
||||
IH["AllocIFF"] --> INIT["InitIFFasDOS"]
|
||||
INIT --> OPEN["OpenIFF(READ)"]
|
||||
OPEN --> PARSE["ParseIFF(SCAN)"]
|
||||
PARSE --> READ["ReadChunkBytes"]
|
||||
READ --> CLOSE["CloseIFF"]
|
||||
end
|
||||
|
||||
FORM -.-> PARSE
|
||||
style FORM fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## IFF Structure
|
||||
|
||||
All IFF files follow a nested chunk structure. Everything is **big-endian** (network byte order):
|
||||
|
||||
```
|
||||
FORM <size> <type>
|
||||
<chunk_id> <size> <data...>
|
||||
<chunk_id> <size> <data...>
|
||||
...
|
||||
FORM <size:LONG> <type:4 chars>
|
||||
<chunk_id:4 chars> <size:LONG> <data...> [pad byte if odd size]
|
||||
<chunk_id:4 chars> <size:LONG> <data...> [pad byte]
|
||||
...
|
||||
|
||||
Nested FORMs:
|
||||
LIST <size> <type>
|
||||
FORM <size> <type>
|
||||
...
|
||||
FORM <size> <type>
|
||||
...
|
||||
```
|
||||
|
||||
All values are big-endian. Chunks are padded to even byte boundaries.
|
||||
| Container | Purpose |
|
||||
|---|---|
|
||||
| `FORM` | A single structured data object |
|
||||
| `LIST` | Ordered collection of FORMs |
|
||||
| `CAT ` | Unordered concatenation of FORMs |
|
||||
| `PROP` | Default property block (within LIST) |
|
||||
|
||||
---
|
||||
|
||||
## Key Functions
|
||||
## Reading an IFF File
|
||||
|
||||
```c
|
||||
struct Library *IFFParseBase = OpenLibrary("iffparse.library", 0);
|
||||
struct IFFHandle *iff = AllocIFF();
|
||||
|
||||
/* Open from file: */
|
||||
/* Open from AmigaDOS file: */
|
||||
iff->iff_Stream = (ULONG)Open("image.iff", MODE_OLDFILE);
|
||||
InitIFFasDOS(iff);
|
||||
OpenIFF(iff, IFFF_READ);
|
||||
if (!iff->iff_Stream) { /* error */ }
|
||||
|
||||
InitIFFasDOS(iff); /* use DOS Read/Write/Seek hooks */
|
||||
|
||||
if (OpenIFF(iff, IFFF_READ)) { /* error */ }
|
||||
|
||||
/* Register chunks we want to stop at: */
|
||||
StopChunk(iff, ID_ILBM, ID_BMHD);
|
||||
StopChunk(iff, ID_ILBM, ID_BODY);
|
||||
StopChunk(iff, ID_ILBM, ID_CMAP);
|
||||
StopChunk(iff, ID_ILBM, ID_CAMG);
|
||||
StopChunk(iff, ID_ILBM, ID_BODY);
|
||||
|
||||
/* Parse: */
|
||||
/* Parse — stops at each registered chunk: */
|
||||
LONG error;
|
||||
while ((error = ParseIFF(iff, IFFPARSE_SCAN)) == 0) {
|
||||
while ((error = ParseIFF(iff, IFFPARSE_SCAN)) == 0)
|
||||
{
|
||||
struct ContextNode *cn = CurrentChunk(iff);
|
||||
switch (cn->cn_ID) {
|
||||
|
||||
switch (cn->cn_ID)
|
||||
{
|
||||
case ID_BMHD:
|
||||
{
|
||||
struct BitMapHeader bmhd;
|
||||
ReadChunkBytes(iff, &bmhd, sizeof(bmhd));
|
||||
Printf("Image: %ldx%ld, %ld planes\n",
|
||||
bmhd.bmh_Width, bmhd.bmh_Height, bmhd.bmh_Depth);
|
||||
break;
|
||||
}
|
||||
case ID_CMAP:
|
||||
ReadChunkBytes(iff, palette, cn->cn_Size);
|
||||
{
|
||||
UBYTE palette[256 * 3];
|
||||
LONG palSize = ReadChunkBytes(iff, palette, cn->cn_Size);
|
||||
LONG numColours = palSize / 3;
|
||||
Printf("Palette: %ld colours\n", numColours);
|
||||
break;
|
||||
}
|
||||
case ID_CAMG:
|
||||
{
|
||||
ULONG viewMode;
|
||||
ReadChunkBytes(iff, &viewMode, 4);
|
||||
if (viewMode & HAM) Printf("HAM mode\n");
|
||||
break;
|
||||
}
|
||||
case ID_BODY:
|
||||
ReadChunkBytes(iff, bodydata, cn->cn_Size);
|
||||
{
|
||||
/* Read pixel data (may be compressed) */
|
||||
UBYTE *bodyData = AllocMem(cn->cn_Size, MEMF_ANY);
|
||||
ReadChunkBytes(iff, bodyData, cn->cn_Size);
|
||||
/* ... decompress if bmhd.bmh_Compression == 1 (ByteRun1) ... */
|
||||
FreeMem(bodyData, cn->cn_Size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,21 +132,140 @@ FreeIFF(iff);
|
|||
|
||||
---
|
||||
|
||||
## Writing an IFF File
|
||||
|
||||
```c
|
||||
struct IFFHandle *iff = AllocIFF();
|
||||
iff->iff_Stream = (ULONG)Open("output.iff", MODE_NEWFILE);
|
||||
InitIFFasDOS(iff);
|
||||
OpenIFF(iff, IFFF_WRITE);
|
||||
|
||||
/* Start the FORM: */
|
||||
PushChunk(iff, ID_ILBM, ID_FORM, IFFSIZE_UNKNOWN);
|
||||
|
||||
/* Write BMHD chunk: */
|
||||
PushChunk(iff, 0, ID_BMHD, sizeof(struct BitMapHeader));
|
||||
WriteChunkBytes(iff, &bmhd, sizeof(bmhd));
|
||||
PopChunk(iff);
|
||||
|
||||
/* Write CMAP chunk: */
|
||||
PushChunk(iff, 0, ID_CMAP, numColours * 3);
|
||||
WriteChunkBytes(iff, palette, numColours * 3);
|
||||
PopChunk(iff);
|
||||
|
||||
/* Write BODY chunk: */
|
||||
PushChunk(iff, 0, ID_BODY, IFFSIZE_UNKNOWN);
|
||||
WriteChunkBytes(iff, bodyData, bodySize);
|
||||
PopChunk(iff);
|
||||
|
||||
/* Close the FORM: */
|
||||
PopChunk(iff);
|
||||
|
||||
CloseIFF(iff);
|
||||
Close((BPTR)iff->iff_Stream);
|
||||
FreeIFF(iff);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ILBM BitMapHeader
|
||||
|
||||
```c
|
||||
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 */
|
||||
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 */
|
||||
UBYTE bmh_XAspect; /* pixel aspect ratio */
|
||||
UBYTE bmh_YAspect;
|
||||
WORD bmh_PageWidth; /* source page width */
|
||||
WORD bmh_PageHeight; /* source page height */
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ByteRun1 Compression
|
||||
|
||||
ILBM BODY data is typically compressed with **ByteRun1** (a simple RLE):
|
||||
|
||||
```
|
||||
For each byte n:
|
||||
0..127: copy next n+1 bytes literally
|
||||
-1..-127: repeat next byte (-n+1) times
|
||||
-128: no-op (skip)
|
||||
```
|
||||
|
||||
```c
|
||||
/* Decompress ByteRun1: */
|
||||
void DecompressByteRun1(UBYTE *src, UBYTE *dst, LONG dstSize)
|
||||
{
|
||||
UBYTE *end = dst + dstSize;
|
||||
while (dst < end)
|
||||
{
|
||||
BYTE n = *src++;
|
||||
if (n >= 0)
|
||||
{
|
||||
LONG count = n + 1;
|
||||
memcpy(dst, src, count);
|
||||
src += count;
|
||||
dst += count;
|
||||
}
|
||||
else if (n != -128)
|
||||
{
|
||||
LONG count = -n + 1;
|
||||
memset(dst, *src++, count);
|
||||
dst += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Chunk IDs
|
||||
|
||||
| FORM Type | Chunk | Description |
|
||||
|---|---|---|
|
||||
| `ILBM` | `BMHD` | Bitmap header (width, height, depth) |
|
||||
| `ILBM` | `CMAP` | Colour map (R,G,B triples) |
|
||||
| `ILBM` | `BODY` | Image body data |
|
||||
| `ILBM` | `CAMG` | Amiga display mode |
|
||||
| `8SVX` | `VHDR` | Voice header |
|
||||
| `8SVX` | `BODY` | Audio sample data |
|
||||
| `ANIM` | `ANHD` | Animation header |
|
||||
| `ANIM` | `DLTA` | Delta frame data |
|
||||
| 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` | `CAMG` | 4 | Amiga display mode (ModeID for ViewPort) |
|
||||
| `ILBM` | `BODY` | varies | Pixel data (interleaved bitplanes) |
|
||||
| `ILBM` | `CRNG` | 8 | Colour 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) |
|
||||
| `ANIM` | `ANHD` | 24 | Animation frame header |
|
||||
| `ANIM` | `DLTA` | varies | Delta-compressed frame data |
|
||||
| `FTXT` | `CHRS` | varies | Character string data |
|
||||
|
||||
---
|
||||
|
||||
## Using IFF with Clipboard
|
||||
|
||||
```c
|
||||
/* Read from clipboard instead of file: */
|
||||
struct IFFHandle *iff = AllocIFF();
|
||||
struct ClipboardHandle *ch = OpenClipboard(PRIMARY_CLIP);
|
||||
iff->iff_Stream = (ULONG)ch;
|
||||
InitIFFasClip(iff); /* use clipboard hooks instead of DOS */
|
||||
OpenIFF(iff, IFFF_READ);
|
||||
/* ... parse as normal ... */
|
||||
CloseIFF(iff);
|
||||
CloseClipboard(ch);
|
||||
FreeIFF(iff);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `libraries/iffparse.h`, `datatypes/pictureclass.h`
|
||||
- EA IFF-85 specification: the original format definition
|
||||
- ADCD 2.1: iffparse.library autodocs
|
||||
- See also: [ham_ehb_modes.md](../08_graphics/ham_ehb_modes.md) — HAM-encoded ILBM files
|
||||
|
|
|
|||
|
|
@ -4,21 +4,41 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`keymap.library` translates raw keycodes from `keyboard.device` into character codes using the active keymap. Each keymap defines the mapping from physical keys to characters, including dead keys and string sequences.
|
||||
`keymap.library` translates **raw keycodes** from `keyboard.device` into character codes using the active keymap. The Amiga keyboard generates hardware scancodes (0x00–0x77); the keymap defines how each physical key maps to characters, including shifted variants, dead keys (accented characters), and string sequences (function keys).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
KB["Physical Keyboard"] -->|"Raw scancode<br/>(0x00-0x77)"| KBD["keyboard.device"]
|
||||
KBD -->|"InputEvent<br/>(rawkey + qualifiers)"| KM["keymap.library<br/>MapRawKey"]
|
||||
KM -->|"ASCII / ANSI<br/>character(s)"| APP["Application<br/>or console.device"]
|
||||
|
||||
style KM fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Functions
|
||||
|
||||
```c
|
||||
/* Map a raw keycode + qualifiers to ASCII: */
|
||||
LONG actual = MapRawKey(&inputEvent, buffer, bufsize, NULL);
|
||||
/* Returns number of characters, -1 if buffer too small */
|
||||
#include <devices/inputevent.h>
|
||||
#include <libraries/keymap.h>
|
||||
|
||||
/* Map ANSI code back to raw key: */
|
||||
WORD MapANSI(STRPTR string, WORD count,
|
||||
STRPTR buffer, WORD length,
|
||||
struct KeyMap *keyMap);
|
||||
/* Map a raw keycode + qualifiers to characters: */
|
||||
struct InputEvent ie;
|
||||
ie.ie_Class = IECLASS_RAWKEY;
|
||||
ie.ie_Code = rawKeyCode; /* 0x00–0x77 */
|
||||
ie.ie_Qualifier = qualifiers; /* IEQUALIFIER_LSHIFT, etc. */
|
||||
ie.ie_EventAddress = NULL;
|
||||
|
||||
char buffer[16];
|
||||
LONG numChars = MapRawKey(&ie, buffer, sizeof(buffer), NULL);
|
||||
/* Returns number of characters, or -1 if buffer too small */
|
||||
/* numChars=0 means the key doesn't produce a character (e.g., Shift alone) */
|
||||
|
||||
/* Map ANSI characters back to raw key events: */
|
||||
WORD result = MapANSI(string, numChars,
|
||||
outBuffer, outLength,
|
||||
NULL); /* NULL = use default keymap */
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -28,19 +48,115 @@ WORD MapANSI(STRPTR string, WORD count,
|
|||
```c
|
||||
/* devices/keymap.h — NDK39 */
|
||||
struct KeyMap {
|
||||
UBYTE *km_LoKeyMapTypes; /* type array for keys 0x00–0x3F */
|
||||
ULONG *km_LoKeyMap; /* mapping array for keys 0x00–0x3F */
|
||||
UBYTE *km_LoCapsable; /* caps-lock bitmap */
|
||||
UBYTE *km_LoKeyMapTypes; /* type byte per key, 0x00–0x3F */
|
||||
ULONG *km_LoKeyMap; /* mapping data per key, 0x00–0x3F */
|
||||
UBYTE *km_LoCapsable; /* caps-lock bitmap (1 bit per key) */
|
||||
UBYTE *km_LoRepeatable; /* auto-repeat bitmap */
|
||||
UBYTE *km_HiKeyMapTypes; /* type array for keys 0x40–0x77 */
|
||||
ULONG *km_HiKeyMap;
|
||||
UBYTE *km_HiKeyMapTypes; /* type byte per key, 0x40–0x77 */
|
||||
ULONG *km_HiKeyMap; /* mapping data per key, 0x40–0x77 */
|
||||
UBYTE *km_HiCapsable;
|
||||
UBYTE *km_HiRepeatable;
|
||||
};
|
||||
```
|
||||
|
||||
The keymap is split into two halves:
|
||||
- **Lo keys** (0x00–0x3F): main keyboard area (letters, numbers, symbols)
|
||||
- **Hi keys** (0x40–0x77): function keys, cursor keys, keypad, special keys
|
||||
|
||||
### Key Type Flags
|
||||
|
||||
| Flag | Meaning |
|
||||
|---|---|
|
||||
| `KCF_SHIFT` | Key has shifted variant |
|
||||
| `KCF_ALT` | Key has Alt variant |
|
||||
| `KCF_CONTROL` | Key has Control variant |
|
||||
| `KCF_DEAD` | Dead key (accent prefix — combines with next keypress) |
|
||||
| `KCF_STRING` | Key produces a multi-byte string (e.g., function keys → escape sequences) |
|
||||
| `KCF_NOP` | Key produces no character |
|
||||
|
||||
---
|
||||
|
||||
## Raw Keycodes
|
||||
|
||||
Key physical positions are fixed across all Amiga keyboards:
|
||||
|
||||
| Rawkey | Key | Rawkey | Key |
|
||||
|---|---|---|---|
|
||||
| `0x00` | \` (backtick) | `0x40` | Space |
|
||||
| `0x01` | 1 | `0x41` | Backspace |
|
||||
| `0x02` | 2 | `0x42` | Tab |
|
||||
| `0x03`–`0x0A` | 3–0 | `0x43` | Enter (keypad) |
|
||||
| `0x10`–`0x19` | Q–P row | `0x44` | Return |
|
||||
| `0x20`–`0x28` | A–L row | `0x45` | Escape |
|
||||
| `0x31`–`0x39` | Z–/ row | `0x46` | Delete |
|
||||
| `0x30` | — | `0x4C` | Cursor Up |
|
||||
| | | `0x4D` | Cursor Down |
|
||||
| | | `0x4E` | Cursor Right |
|
||||
| | | `0x4F` | Cursor Left |
|
||||
| `0x50`–`0x59` | F1–F10 | `0x60` | Left Shift |
|
||||
| | | `0x61` | Right Shift |
|
||||
| | | `0x63` | Control |
|
||||
| | | `0x64` | Left Alt |
|
||||
| | | `0x65` | Right Alt |
|
||||
| | | `0x66` | Left Amiga |
|
||||
| | | `0x67` | Right Amiga |
|
||||
|
||||
> [!NOTE]
|
||||
> **Key-up events** have bit 7 set: rawkey `0x80 | keycode`. So rawkey `0xC5` = Escape key released.
|
||||
|
||||
---
|
||||
|
||||
## Dead Keys (Accented Characters)
|
||||
|
||||
Dead keys work in two presses:
|
||||
1. Press the dead key (e.g., Alt+H = acute accent ´)
|
||||
2. Press the base letter (e.g., e)
|
||||
3. Result: é
|
||||
|
||||
```c
|
||||
/* Dead key handling is automatic in MapRawKey if you pass
|
||||
the previous dead key's InputEvent via ie_EventAddress: */
|
||||
struct InputEvent deadEvent;
|
||||
/* ... first keypress (dead key) stored here ... */
|
||||
|
||||
ie.ie_EventAddress = (APTR)&deadEvent; /* chain to dead key */
|
||||
numChars = MapRawKey(&ie, buffer, sizeof(buffer), NULL);
|
||||
/* buffer now contains the composed character */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changing Keymaps
|
||||
|
||||
```c
|
||||
/* Set a different keymap: */
|
||||
struct KeyMap *germanMap;
|
||||
/* Load from DEVS:Keymaps/d (German layout) */
|
||||
|
||||
/* System-wide change via Preferences: */
|
||||
/* Use the Input prefs editor, or: */
|
||||
SetKeyMapDefault(newKeyMap);
|
||||
```
|
||||
|
||||
Available keymaps in `DEVS:Keymaps/`:
|
||||
|
||||
| File | Layout |
|
||||
|---|---|
|
||||
| `usa` | US English (QWERTY) — default |
|
||||
| `gb` | British English |
|
||||
| `d` | German (QWERTZ) |
|
||||
| `f` | French (AZERTY) |
|
||||
| `i` | Italian |
|
||||
| `e` | Spanish |
|
||||
| `dk` | Danish |
|
||||
| `s` | Swedish |
|
||||
| `n` | Norwegian |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `devices/keymap.h`, `libraries/keymap.h`
|
||||
- ADCD 2.1: keymap.library autodocs
|
||||
- See also: [console.md](../10_devices/console.md) — console.device uses keymap for input
|
||||
- See also: [input_events.md](../09_intuition/input_events.md) — InputEvent structure
|
||||
|
|
|
|||
|
|
@ -1,50 +1,224 @@
|
|||
[← Home](../README.md) · [Libraries](README.md)
|
||||
|
||||
# layers.library — Window Clipping Layers
|
||||
# layers.library — Window Clipping and Damage Repair
|
||||
|
||||
## Overview
|
||||
|
||||
`layers.library` provides the clipping and damage-repair infrastructure that Intuition windows are built on. Each window's `RastPort` is backed by a `Layer` that manages overlapping regions.
|
||||
`layers.library` provides the clipping and damage-repair infrastructure that Intuition windows are built on. Every window's `RastPort` is backed by a `Layer` that manages overlapping regions, damage tracking, and optional backing-store for obscured content.
|
||||
|
||||
When you draw to a window, all drawing operations are automatically clipped to the window's visible area by the layer system. You never draw "outside" your window or over another window — layers enforces this transparently.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Screen BitMap"
|
||||
subgraph "Layer A (front)"
|
||||
LA["Fully visible<br/>ClipRect covers<br/>entire layer"]
|
||||
end
|
||||
subgraph "Layer B (middle)"
|
||||
LB["Partially obscured<br/>by Layer A"]
|
||||
LB1["ClipRect 1<br/>(visible)"]
|
||||
LB2["ClipRect 2<br/>(obscured)"]
|
||||
end
|
||||
subgraph "Layer C (back)"
|
||||
LC["Mostly obscured"]
|
||||
end
|
||||
end
|
||||
|
||||
DRAW["Draw into Layer B"] --> CLIP["layers.library<br/>clips to visible<br/>ClipRects only"]
|
||||
CLIP --> LB1
|
||||
|
||||
style LA fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
style LB fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style LC fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
style CLIP fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layer Types
|
||||
|
||||
| Flag | Type | Description |
|
||||
|---|---|---|
|
||||
| `LAYERSIMPLE` | Simple Refresh | Application must redraw damaged areas |
|
||||
| `LAYERSMART` | Smart Refresh | System saves obscured regions |
|
||||
| `LAYERSUPER` | Super BitMap | Application provides full-size bitmap |
|
||||
| `LAYERBACKDROP` | Backdrop | Behind all other layers |
|
||||
| Flag | Type | Backing Store | Damage Handling | Memory Cost |
|
||||
|---|---|---|---|---|
|
||||
| `LAYERSIMPLE` | Simple Refresh | None | App must redraw on `IDCMP_REFRESHWINDOW` | Minimal |
|
||||
| `LAYERSMART` | Smart Refresh | Auto — obscured regions saved/restored | Automatic — OS handles damage | Moderate |
|
||||
| `LAYERSUPER` | Super BitMap | Full off-screen bitmap (app provides) | Full bitmap always valid | High |
|
||||
| `LAYERBACKDROP` | Backdrop | Modifier — behind all normal layers | Depends on refresh type | — |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Simple Refresh"
|
||||
S1["Window obscured"] --> S2["Damage region<br/>recorded"]
|
||||
S2 --> S3["IDCMP_REFRESHWINDOW<br/>sent to app"]
|
||||
S3 --> S4["App redraws<br/>between Begin/EndRefresh"]
|
||||
end
|
||||
|
||||
subgraph "Smart Refresh"
|
||||
M1["Window obscured"] --> M2["Obscured area<br/>saved to buffer"]
|
||||
M2 --> M3["Window revealed"] --> M4["OS restores<br/>from buffer"]
|
||||
end
|
||||
|
||||
style S3 fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
style M4 fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Functions
|
||||
## ClipRects — The Clipping Engine
|
||||
|
||||
Each layer maintains a linked list of **ClipRects** — rectangles that define how each region of the layer should be handled:
|
||||
|
||||
```c
|
||||
struct ClipRect {
|
||||
struct ClipRect *Next; /* next in chain */
|
||||
struct ClipRect *prev; /* previous */
|
||||
struct Layer *lobs; /* layer that obscures this rect (NULL if visible) */
|
||||
struct BitMap *BitMap; /* backing store bitmap (Smart Refresh) */
|
||||
LONG reserved;
|
||||
LONG Flags;
|
||||
struct Rectangle bounds; /* x1,y1,x2,y2 of this rect */
|
||||
};
|
||||
```
|
||||
|
||||
When drawing to a partially obscured layer:
|
||||
1. The drawing call (e.g., `RectFill`) enters `graphics.library`
|
||||
2. Graphics detects `rp->Layer != NULL`
|
||||
3. For each **visible ClipRect**, the drawing is performed clipped to that rectangle
|
||||
4. For **obscured ClipRects** (Smart Refresh), drawing goes to the backing-store bitmap instead
|
||||
5. The application sees nothing — clipping is fully transparent
|
||||
|
||||
---
|
||||
|
||||
## Creating Layers Directly
|
||||
|
||||
While Intuition normally creates layers for windows, you can create them manually for custom display systems:
|
||||
|
||||
```c
|
||||
struct Layer_Info *li = NewLayerInfo();
|
||||
|
||||
struct Layer *layer = CreateUpfrontLayer(li, bitmap,
|
||||
x1, y1, x2, y2, LAYERSMART, NULL);
|
||||
/* Create a layer on top: */
|
||||
struct Layer *frontLayer = CreateUpfrontLayer(li, screenBitMap,
|
||||
10, 10, 200, 100, /* bounds: x1, y1, x2, y2 */
|
||||
LAYERSMART, /* type */
|
||||
NULL); /* super bitmap (NULL for non-SUPER) */
|
||||
|
||||
/* Lock before drawing: */
|
||||
/* Create behind existing layers: */
|
||||
struct Layer *backLayer = CreateBehindLayer(li, screenBitMap,
|
||||
50, 50, 250, 150,
|
||||
LAYERSIMPLE | LAYERBACKDROP,
|
||||
NULL);
|
||||
|
||||
/* Get the RastPort for drawing: */
|
||||
struct RastPort *rp = frontLayer->rp;
|
||||
SetAPen(rp, 1);
|
||||
RectFill(rp, 0, 0, 190, 90); /* coordinates relative to layer */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layer Operations
|
||||
|
||||
### Locking
|
||||
|
||||
**Always lock a layer before drawing** if other tasks might modify it simultaneously:
|
||||
|
||||
```c
|
||||
LockLayer(0, layer);
|
||||
/* ... draw into layer->rp ... */
|
||||
/* ... safe to draw ... */
|
||||
UnlockLayer(layer);
|
||||
|
||||
/* Move: */
|
||||
/* Lock ALL layers (for bulk operations): */
|
||||
LockLayers(layerInfo);
|
||||
UnlockLayers(layerInfo);
|
||||
|
||||
/* Lock a layer for multiple operations: */
|
||||
LockLayerInfo(layerInfo);
|
||||
/* ... manipulate layer stack ... */
|
||||
UnlockLayerInfo(layerInfo);
|
||||
```
|
||||
|
||||
### Moving and Resizing
|
||||
|
||||
```c
|
||||
/* Move layer by delta: */
|
||||
MoveLayer(0, layer, dx, dy);
|
||||
|
||||
/* Resize: */
|
||||
/* Resize layer by delta: */
|
||||
SizeLayer(0, layer, dw, dh);
|
||||
|
||||
/* Cleanup: */
|
||||
/* Move to front/back of stack: */
|
||||
UpfrontLayer(0, layer);
|
||||
BehindLayer(0, layer);
|
||||
```
|
||||
|
||||
### Damage and Refresh
|
||||
|
||||
```c
|
||||
/* For Simple Refresh windows — handle IDCMP_REFRESHWINDOW: */
|
||||
BeginRefresh(window);
|
||||
/* ... redraw damaged area only ... */
|
||||
/* The ClipRect list is temporarily set to damaged regions only */
|
||||
EndRefresh(window, TRUE); /* TRUE = damage fully repaired */
|
||||
```
|
||||
|
||||
### Install Backfill Hook
|
||||
|
||||
```c
|
||||
/* Custom backfill instead of default (clear to pen 0): */
|
||||
struct Hook backfillHook;
|
||||
backfillHook.h_Entry = (HOOKFUNC)MyBackfillFunc;
|
||||
InstallLayerHook(layer, &backfillHook);
|
||||
|
||||
/* The hook is called whenever a region needs to be filled
|
||||
(e.g., when a window is moved and new area is exposed) */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Super BitMap Layers
|
||||
|
||||
Super BitMap layers maintain a full-sized off-screen bitmap that the application owns. The visible portion is blitted to the screen; the rest is always valid in the off-screen buffer:
|
||||
|
||||
```c
|
||||
/* Allocate the full bitmap: */
|
||||
struct BitMap *superBM = AllocBitMap(640, 480, depth,
|
||||
BMF_CLEAR, NULL);
|
||||
|
||||
/* Create super bitmap layer: */
|
||||
struct Layer *superLayer = CreateUpfrontLayer(li, screenBitMap,
|
||||
0, 0, 319, 255, /* visible area on screen */
|
||||
LAYERSUPER,
|
||||
superBM); /* the full-size bitmap */
|
||||
|
||||
/* Scroll the view within the super bitmap: */
|
||||
ScrollLayer(0, superLayer, dx, dy);
|
||||
|
||||
/* Sync super bitmap with display after drawing: */
|
||||
SyncSBitMap(superLayer);
|
||||
|
||||
/* Copy display back to super bitmap before hiding: */
|
||||
CopySBitMap(superLayer);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cleanup
|
||||
|
||||
```c
|
||||
/* Remove layers in reverse order: */
|
||||
DeleteLayer(0, layer);
|
||||
|
||||
/* Dispose the layer info: */
|
||||
DisposeLayerInfo(li);
|
||||
|
||||
/* If using super bitmap, free it after DeleteLayer: */
|
||||
FreeBitMap(superBM);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `graphics/layers.h`, `graphics/clip.h`
|
||||
- NDK39: `graphics/layers.h`, `graphics/clip.h`, `graphics/gfx.h`
|
||||
- ADCD 2.1: layers.library autodocs
|
||||
- See also: [rastport.md](../08_graphics/rastport.md) — drawing through layers
|
||||
- See also: [screens.md](../09_intuition/screens.md) — Intuition screen/window layer management
|
||||
|
|
|
|||
|
|
@ -4,39 +4,180 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`locale.library` (OS 2.1+) provides language-aware string lookup, date/number formatting, and character classification for internationalised applications.
|
||||
`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.
|
||||
|
||||
```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'"]
|
||||
|
||||
style LOC fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style CAT fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Pattern
|
||||
## Catalogue System
|
||||
|
||||
### Creating String IDs
|
||||
|
||||
```c
|
||||
/* Typically in a generated header (from CatComp or FlexCat): */
|
||||
#define MSG_OPEN_FILE 1
|
||||
#define MSG_SAVE_FILE 2
|
||||
#define MSG_QUIT 3
|
||||
#define MSG_ERROR_NOTFOUND 100
|
||||
|
||||
/* Built-in English strings (fallback): */
|
||||
static const char *builtinStrings[] = {
|
||||
[MSG_OPEN_FILE] = "Open File",
|
||||
[MSG_SAVE_FILE] = "Save File",
|
||||
[MSG_QUIT] = "Quit",
|
||||
[MSG_ERROR_NOTFOUND] = "File not found",
|
||||
};
|
||||
```
|
||||
|
||||
### Using Catalogues
|
||||
|
||||
```c
|
||||
struct Library *LocaleBase = OpenLibrary("locale.library", 38);
|
||||
|
||||
/* Open the application's catalogue: */
|
||||
struct Catalog *cat = OpenCatalog(NULL, "myapp.catalog",
|
||||
OC_BuiltInLanguage, "english",
|
||||
TAG_DONE);
|
||||
OC_BuiltInLanguage, (ULONG)"english",
|
||||
TAG_DONE);
|
||||
/* NULL for first arg = use current locale */
|
||||
|
||||
/* Get localised string: */
|
||||
STRPTR str = GetCatalogStr(cat, MSG_HELLO, "Hello"); /* fallback */
|
||||
/* Get localised string (with English fallback): */
|
||||
STRPTR openStr = GetCatalogStr(cat, MSG_OPEN_FILE, "Open File");
|
||||
/* Returns German "Datei öffnen" if German catalogue exists,
|
||||
otherwise the fallback "Open File" */
|
||||
|
||||
/* Use throughout the application: */
|
||||
Printf("%s\n", GetCatalogStr(cat, MSG_QUIT, "Quit"));
|
||||
|
||||
/* Cleanup: */
|
||||
CloseCatalog(cat);
|
||||
CloseLibrary(LocaleBase);
|
||||
```
|
||||
|
||||
### Catalogue File Structure
|
||||
|
||||
```
|
||||
LOCALE:Catalogs/deutsch/myapp.catalog ← German
|
||||
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**:
|
||||
|
||||
```
|
||||
; myapp.cd — catalogue description
|
||||
MSG_OPEN_FILE (1//)
|
||||
Open File
|
||||
;
|
||||
MSG_SAVE_FILE (2//)
|
||||
Save File
|
||||
;
|
||||
```
|
||||
|
||||
```
|
||||
; myapp_deutsch.ct — German translation
|
||||
MSG_OPEN_FILE
|
||||
Datei öffnen
|
||||
;
|
||||
MSG_SAVE_FILE
|
||||
Datei speichern
|
||||
;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Locale-Aware Formatting
|
||||
|
||||
```c
|
||||
struct Locale *loc = OpenLocale(NULL); /* user's default */
|
||||
struct Locale *loc = OpenLocale(NULL); /* user's default locale */
|
||||
|
||||
/* Format a date: */
|
||||
FormatDate(loc, "%A %e %B %Y", &datestamp, &hook);
|
||||
Printf("Country: %s\n", loc->loc_CountryName);
|
||||
Printf("Language: %s\n", loc->loc_PrefLanguages[0]);
|
||||
Printf("Decimal: '%s'\n", loc->loc_DecimalPoint); /* "." or "," */
|
||||
Printf("Grouping: '%s'\n", loc->loc_GroupSeparator); /* "," or "." */
|
||||
Printf("Currency: '%s'\n", loc->loc_MonCS); /* "$", "€", "£" */
|
||||
```
|
||||
|
||||
/* Format a number: */
|
||||
/* Uses loc->loc_GroupSeparator, loc->loc_DecimalPoint */
|
||||
### Date Formatting
|
||||
|
||||
CloseLocale(loc);
|
||||
```c
|
||||
/* Format a date stamp according to locale: */
|
||||
struct DateStamp ds;
|
||||
DateStamp(&ds);
|
||||
|
||||
/* FormatDate uses a hook to receive characters: */
|
||||
char dateBuf[64];
|
||||
int pos = 0;
|
||||
|
||||
/* Simple hook that fills a buffer: */
|
||||
void __saveds __asm DateHookFunc(
|
||||
register __a0 struct Hook *hook,
|
||||
register __a1 char ch)
|
||||
{
|
||||
char *buf = hook->h_Data;
|
||||
buf[pos++] = ch;
|
||||
buf[pos] = 0;
|
||||
}
|
||||
|
||||
struct Hook dateHook;
|
||||
dateHook.h_Entry = (HOOKFUNC)DateHookFunc;
|
||||
dateHook.h_Data = dateBuf;
|
||||
|
||||
FormatDate(loc, "%A, %e %B %Y", &ds, &dateHook);
|
||||
/* Result (German locale): "Mittwoch, 23 April 2025" */
|
||||
/* Result (US locale): "Wednesday, 23 April 2025" */
|
||||
```
|
||||
|
||||
### Format Codes
|
||||
|
||||
| Code | Output | Example |
|
||||
|---|---|---|
|
||||
| `%A` | Full weekday name | "Wednesday" / "Mittwoch" |
|
||||
| `%a` | Abbreviated weekday | "Wed" / "Mi" |
|
||||
| `%B` | Full month name | "April" |
|
||||
| `%b` | Abbreviated month | "Apr" |
|
||||
| `%d` | Day (01–31) | "23" |
|
||||
| `%e` | Day (1–31, no leading zero) | "23" |
|
||||
| `%H` | Hour (00–23) | "14" |
|
||||
| `%I` | Hour (01–12) | "02" |
|
||||
| `%M` | Minute (00–59) | "30" |
|
||||
| `%S` | Second (00–59) | "00" |
|
||||
| `%p` | AM/PM | "PM" |
|
||||
| `%Y` | 4-digit year | "2025" |
|
||||
| `%y` | 2-digit year | "25" |
|
||||
|
||||
---
|
||||
|
||||
## Character Classification
|
||||
|
||||
```c
|
||||
/* Locale-aware character checks: */
|
||||
if (IsAlpha(loc, ch)) /* alphabetic (language-aware) */
|
||||
if (IsUpper(loc, ch)) /* uppercase */
|
||||
if (IsLower(loc, ch)) /* lowercase */
|
||||
if (IsDigit(loc, ch)) /* digit */
|
||||
if (IsAlNum(loc, ch)) /* alphanumeric */
|
||||
if (IsPunct(loc, ch)) /* punctuation */
|
||||
if (IsSpace(loc, ch)) /* whitespace */
|
||||
|
||||
/* Locale-aware case conversion: */
|
||||
char upper = ConvToUpper(loc, ch);
|
||||
char lower = ConvToLower(loc, ch);
|
||||
|
||||
/* Locale-aware string comparison: */
|
||||
LONG result = StrnCmp(loc, str1, str2, -1, SC_COLLATE2);
|
||||
/* SC_ASCII = byte comparison */
|
||||
/* SC_COLLATE1 = primary collation (ignores accents) */
|
||||
/* SC_COLLATE2 = full collation */
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -44,3 +185,5 @@ CloseLocale(loc);
|
|||
## References
|
||||
|
||||
- NDK39: `libraries/locale.h`
|
||||
- ADCD 2.1: locale.library autodocs
|
||||
- CatComp / FlexCat documentation for catalogue compilation
|
||||
|
|
|
|||
|
|
@ -1,32 +1,87 @@
|
|||
[← Home](../README.md) · [Libraries](README.md)
|
||||
|
||||
# rexxsyslib.library — ARexx Interface
|
||||
# rexxsyslib.library — ARexx Scripting Interface
|
||||
|
||||
## Overview
|
||||
|
||||
ARexx is the Amiga's built-in macro/scripting language (based on REXX). `rexxsyslib.library` provides APIs for hosting ARexx ports, sending commands, and receiving results.
|
||||
ARexx is the Amiga's built-in macro/scripting language (based on IBM's REXX). Any application can host an **ARexx port** to receive commands from scripts and other applications — this is the Amiga's primary inter-application communication mechanism for user-facing scripting.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
SCRIPT["ARexx Script<br/>(text file)"] -->|"Sends command<br/>to named port"| PORT["Application<br/>ARexx Port<br/>(e.g., 'MYAPP')"]
|
||||
PORT -->|"Parses command<br/>returns result"| SCRIPT
|
||||
|
||||
APP2["Other Application"] -->|"FindPort + PutMsg"| PORT
|
||||
|
||||
style PORT fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding an ARexx Port
|
||||
## Adding an ARexx Port to Your Application
|
||||
|
||||
```c
|
||||
struct MsgPort *arexxPort = CreateMsgPort();
|
||||
arexxPort->mp_Node.ln_Name = "MYAPP";
|
||||
arexxPort->mp_Node.ln_Pri = 0;
|
||||
AddPort(arexxPort);
|
||||
#include <rexx/storage.h>
|
||||
#include <rexx/rxslib.h>
|
||||
|
||||
/* In event loop, check for ARexx messages: */
|
||||
struct RexxMsg *rmsg;
|
||||
while ((rmsg = (struct RexxMsg *)GetMsg(arexxPort))) {
|
||||
STRPTR cmd = ARG0(rmsg); /* the command string */
|
||||
/* Parse and execute command... */
|
||||
rmsg->rm_Result1 = 0; /* RC = 0 (success) */
|
||||
rmsg->rm_Result2 = 0;
|
||||
ReplyMsg((struct Message *)rmsg);
|
||||
struct MsgPort *arexxPort = CreateMsgPort();
|
||||
arexxPort->mp_Node.ln_Name = "MYAPP"; /* public port name */
|
||||
arexxPort->mp_Node.ln_Pri = 0;
|
||||
AddPort(arexxPort); /* make it publicly findable */
|
||||
|
||||
/* In your main event loop, combine with IDCMP: */
|
||||
ULONG arexxSig = 1 << arexxPort->mp_SigBit;
|
||||
ULONG idcmpSig = 1 << window->UserPort->mp_SigBit;
|
||||
|
||||
ULONG sigs = Wait(arexxSig | idcmpSig);
|
||||
|
||||
if (sigs & arexxSig)
|
||||
{
|
||||
struct RexxMsg *rmsg;
|
||||
while ((rmsg = (struct RexxMsg *)GetMsg(arexxPort)))
|
||||
{
|
||||
STRPTR cmd = ARG0(rmsg); /* the command string */
|
||||
|
||||
/* Parse and execute: */
|
||||
if (stricmp(cmd, "QUIT") == 0)
|
||||
{
|
||||
rmsg->rm_Result1 = 0; /* RC = 0 (success) */
|
||||
rmsg->rm_Result2 = 0;
|
||||
running = FALSE;
|
||||
}
|
||||
else if (strnicmp(cmd, "OPEN ", 5) == 0)
|
||||
{
|
||||
char *filename = cmd + 5;
|
||||
LONG rc = OpenFile(filename);
|
||||
rmsg->rm_Result1 = rc ? 0 : 10; /* 0=ok, 10=error */
|
||||
rmsg->rm_Result2 = 0;
|
||||
}
|
||||
else if (stricmp(cmd, "VERSION") == 0)
|
||||
{
|
||||
rmsg->rm_Result1 = 0;
|
||||
/* Return a string result: */
|
||||
if (rmsg->rm_Action & RXFF_RESULT)
|
||||
rmsg->rm_Result2 = (LONG)CreateArgstring("1.0", 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
rmsg->rm_Result1 = 5; /* RC_WARN — unknown command */
|
||||
rmsg->rm_Result2 = 0;
|
||||
}
|
||||
|
||||
ReplyMsg((struct Message *)rmsg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Cleanup: */
|
||||
RemPort(arexxPort);
|
||||
/* Drain remaining messages: */
|
||||
struct RexxMsg *rmsg;
|
||||
while ((rmsg = (struct RexxMsg *)GetMsg(arexxPort)))
|
||||
{
|
||||
rmsg->rm_Result1 = 20; /* RC_FATAL */
|
||||
ReplyMsg((struct Message *)rmsg);
|
||||
}
|
||||
DeleteMsgPort(arexxPort);
|
||||
```
|
||||
|
||||
|
|
@ -35,17 +90,34 @@ DeleteMsgPort(arexxPort);
|
|||
## Sending ARexx Commands
|
||||
|
||||
```c
|
||||
struct MsgPort *replyPort = CreateMsgPort();
|
||||
struct RexxMsg *rmsg = CreateRexxMsg(replyPort, NULL, NULL);
|
||||
rmsg->rm_Args[0] = CreateArgstring("QUIT", 4);
|
||||
rmsg->rm_Action = RXCOMM;
|
||||
struct Library *RexxSysBase = OpenLibrary("rexxsyslib.library", 0);
|
||||
|
||||
struct MsgPort *replyPort = CreateMsgPort();
|
||||
struct RexxMsg *rmsg = CreateRexxMsg(replyPort, NULL, "MYAPP");
|
||||
rmsg->rm_Args[0] = (STRPTR)CreateArgstring("OPEN test.txt", 13);
|
||||
rmsg->rm_Action = RXCOMM | RXFF_RESULT;
|
||||
|
||||
/* Find the target application's port: */
|
||||
Forbid();
|
||||
struct MsgPort *target = FindPort("TARGETAPP");
|
||||
if (target) {
|
||||
if (target)
|
||||
{
|
||||
PutMsg(target, &rmsg->rm_Node);
|
||||
Permit();
|
||||
WaitPort(replyPort);
|
||||
GetMsg(replyPort);
|
||||
/* rmsg->rm_Result1 = return code */
|
||||
|
||||
Printf("Return code: %ld\n", rmsg->rm_Result1);
|
||||
if (rmsg->rm_Result1 == 0 && rmsg->rm_Result2)
|
||||
{
|
||||
Printf("Result: %s\n", (char *)rmsg->rm_Result2);
|
||||
DeleteArgstring((STRPTR)rmsg->rm_Result2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Permit();
|
||||
Printf("Target port not found\n");
|
||||
}
|
||||
|
||||
DeleteArgstring(rmsg->rm_Args[0]);
|
||||
|
|
@ -55,6 +127,50 @@ DeleteMsgPort(replyPort);
|
|||
|
||||
---
|
||||
|
||||
## ARexx Script Example
|
||||
|
||||
```rexx
|
||||
/* MyScript.rexx — control MYAPP from ARexx */
|
||||
ADDRESS 'MYAPP'
|
||||
|
||||
OPEN 'work:data/image.iff'
|
||||
IF RC > 0 THEN DO
|
||||
SAY 'Failed to open file, RC=' RC
|
||||
EXIT 10
|
||||
END
|
||||
|
||||
VERSION
|
||||
SAY 'App version:' RESULT
|
||||
QUIT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Return Codes
|
||||
|
||||
| Value | Constant | Meaning |
|
||||
|---|---|---|
|
||||
| 0 | `RC_OK` | Success |
|
||||
| 5 | `RC_WARN` | Warning (command not understood, etc.) |
|
||||
| 10 | `RC_ERROR` | Error (command failed) |
|
||||
| 20 | `RC_FATAL` | Fatal error (port shutting down) |
|
||||
|
||||
---
|
||||
|
||||
## Common ARexx-Aware Applications
|
||||
|
||||
| Application | Port Name | Example Commands |
|
||||
|---|---|---|
|
||||
| Workbench | `WORKBENCH` | `OPEN`, `CLOSE`, `WINDOW` |
|
||||
| MultiView | `MULTIVIEW.n` | `OPEN`, `PRINT`, `QUIT` |
|
||||
| IBrowse | `IBROWSE` | `GOTOURL`, `RELOAD` |
|
||||
| CygnusEd | `rexx_ced` | `OPEN`, `SAVE`, `MARK`, `CUT` |
|
||||
| TextPad | `TEXTPAD` | `LOAD`, `SAVE`, `FIND` |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `rexx/storage.h`, `rexx/rxslib.h`
|
||||
- ADCD 2.1: rexxsyslib.library autodocs
|
||||
- See also: [process_management.md](../07_dos/process_management.md) — process/task message ports
|
||||
|
|
|
|||
|
|
@ -4,83 +4,200 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`utility.library` (OS 2.0+) provides the universal tag-based parameter passing system, callback hooks, and date/time utilities used throughout AmigaOS.
|
||||
`utility.library` (OS 2.0+) provides the universal tag-based parameter passing system, callback hooks, and date/time utilities used throughout AmigaOS. Nearly every OS 2.0+ API uses TagItem lists for extensible parameter passing — understanding this library is essential.
|
||||
|
||||
---
|
||||
|
||||
## TagItem System
|
||||
|
||||
### Structure
|
||||
|
||||
```c
|
||||
/* utility/tagitem.h — NDK39 */
|
||||
struct TagItem {
|
||||
ULONG ti_Tag; /* tag identifier */
|
||||
ULONG ti_Data; /* tag value */
|
||||
ULONG ti_Data; /* tag value (ULONG — often cast from pointer) */
|
||||
};
|
||||
```
|
||||
|
||||
/* Special tag values: */
|
||||
#define TAG_DONE 0 /* end of tag list */
|
||||
#define TAG_END TAG_DONE
|
||||
#define TAG_IGNORE 1 /* skip this tag */
|
||||
#define TAG_MORE 2 /* ti_Data = pointer to another TagItem array */
|
||||
#define TAG_SKIP 3 /* skip next ti_Data tags */
|
||||
#define TAG_USER (1<<31) /* user-defined tags start here */
|
||||
### Special Tags
|
||||
|
||||
| Tag | Value | Purpose |
|
||||
|---|---|---|
|
||||
| `TAG_DONE` | 0 | Terminates a tag list — must be the last entry |
|
||||
| `TAG_IGNORE` | 1 | Skip this tag (placeholder) |
|
||||
| `TAG_MORE` | 2 | `ti_Data` = pointer to another TagItem array (chain lists) |
|
||||
| `TAG_SKIP` | 3 | Skip next `ti_Data` tags in the list |
|
||||
| `TAG_USER` | `1<<31` | User-defined tags start at this value |
|
||||
|
||||
### How Tag Lists Work
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Tag List (array)"
|
||||
T1["SA_Width<br/>640"] --> T2["SA_Height<br/>480"]
|
||||
T2 --> T3["SA_Depth<br/>8"]
|
||||
T3 --> TM["TAG_MORE<br/>→ extraTags"]
|
||||
end
|
||||
|
||||
subgraph "Chained List"
|
||||
E1["SA_Title<br/>'My Screen'"] --> E2["TAG_DONE<br/>0"]
|
||||
end
|
||||
|
||||
TM --> E1
|
||||
|
||||
style TM fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style E2 fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
```
|
||||
|
||||
```c
|
||||
/* Typical usage: */
|
||||
struct Screen *scr = OpenScreenTags(NULL,
|
||||
SA_Width, 640,
|
||||
SA_Height, 480,
|
||||
SA_Depth, 8,
|
||||
SA_Title, (ULONG)"My Screen",
|
||||
SA_ShowTitle, TRUE,
|
||||
TAG_DONE);
|
||||
|
||||
/* Tag list as array: */
|
||||
struct TagItem tags[] = {
|
||||
{ SA_Width, 640 },
|
||||
{ SA_Height, 480 },
|
||||
{ SA_Depth, 8 },
|
||||
{ TAG_DONE, 0 }
|
||||
};
|
||||
struct Screen *scr = OpenScreenTagList(NULL, tags);
|
||||
```
|
||||
|
||||
### Tag Utility Functions
|
||||
|
||||
| Function | Description |
|
||||
|---|---|
|
||||
| `FindTagItem(tag, tagList)` | Find first matching tag |
|
||||
| `GetTagData(tag, default, tagList)` | Get tag value with default |
|
||||
| `NextTagItem(&tagListPtr)` | Iterate through tags |
|
||||
| `TagInArray(tag, array)` | Check if tag is in an array |
|
||||
| `FilterTagItems(tagList, filter, logic)` | Filter tag list |
|
||||
| `CloneTagItems(tagList)` | Allocate copy of tag list |
|
||||
| `FreeTagItems(tagList)` | Free cloned tag list |
|
||||
| `MapTags(tagList, mapList, flags)` | Remap tag IDs |
|
||||
| `FindTagItem(tag, tagList)` | Find first matching tag; returns `TagItem *` or NULL |
|
||||
| `GetTagData(tag, default, tagList)` | Get tag value, or default if tag not found |
|
||||
| `NextTagItem(&tagListPtr)` | Iterator — handles TAG_MORE, TAG_SKIP, TAG_IGNORE transparently |
|
||||
| `TagInArray(tag, array)` | Check if a tag ID is in a ULONG array |
|
||||
| `FilterTagItems(tagList, filter, logic)` | Remove/keep tags matching a filter array |
|
||||
| `CloneTagItems(tagList)` | Allocate a copy of the entire tag list |
|
||||
| `FreeTagItems(tagList)` | Free a cloned tag list |
|
||||
| `MapTags(tagList, mapList, flags)` | Remap tag IDs (for converting between APIs) |
|
||||
| `PackBoolTags(initialFlags, tagList, boolMap)` | Convert boolean tags to a flags word |
|
||||
|
||||
### Iterating a Tag List
|
||||
|
||||
```c
|
||||
/* The correct way to iterate — handles all special tags: */
|
||||
struct TagItem *tag;
|
||||
struct TagItem *tstate = tagList;
|
||||
|
||||
while ((tag = NextTagItem(&tstate)))
|
||||
{
|
||||
switch (tag->ti_Tag)
|
||||
{
|
||||
case MY_WIDTH: width = tag->ti_Data; break;
|
||||
case MY_HEIGHT: height = tag->ti_Data; break;
|
||||
case MY_TITLE: title = (char *)tag->ti_Data; break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Never iterate tag lists manually** with `for` loops. Always use `NextTagItem()` — it correctly handles `TAG_MORE` chains, `TAG_SKIP`, and `TAG_IGNORE`. Manual iteration will break on chained or filtered lists.
|
||||
|
||||
---
|
||||
|
||||
## Hook System
|
||||
|
||||
Hooks provide a standardised callback mechanism used throughout AmigaOS — Intuition, BOOPSI, layers.library, and locale.library all use them.
|
||||
|
||||
### Structure
|
||||
|
||||
```c
|
||||
/* utility/hooks.h */
|
||||
struct Hook {
|
||||
struct MinNode h_MinNode;
|
||||
struct MinNode h_MinNode; /* for linking into lists */
|
||||
ULONG (*h_Entry)(void); /* assembler entry point */
|
||||
ULONG (*h_SubEntry)(void); /* C function pointer */
|
||||
APTR h_Data; /* user data */
|
||||
};
|
||||
```
|
||||
|
||||
Convention: `h_Entry` receives `A0=hook, A2=object, A1=message`.
|
||||
### Register Convention
|
||||
|
||||
When a hook is called, registers are set up as:
|
||||
- **A0** = pointer to the `Hook` itself
|
||||
- **A2** = the "object" (context-dependent)
|
||||
- **A1** = the "message" (context-dependent)
|
||||
|
||||
```c
|
||||
/* Convenience macro for SAS/C and GCC: */
|
||||
ULONG myHookFunc(struct Hook *hook __asm("a0"),
|
||||
Object *obj __asm("a2"),
|
||||
APTR msg __asm("a1"))
|
||||
/* SAS/C / GCC with register args: */
|
||||
ULONG __saveds __asm MyHookFunc(
|
||||
register __a0 struct Hook *hook,
|
||||
register __a2 APTR object,
|
||||
register __a1 APTR message)
|
||||
{
|
||||
/* ... */
|
||||
struct MyData *data = (struct MyData *)hook->h_Data;
|
||||
/* ... process callback ... */
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct Hook myHook = { {NULL}, (HOOKFUNC)myHookFunc, NULL, myData };
|
||||
/* Initialise the hook: */
|
||||
struct Hook myHook;
|
||||
myHook.h_Entry = (HOOKFUNC)HookEntry; /* utility.library glue */
|
||||
myHook.h_SubEntry = (HOOKFUNC)MyHookFunc;
|
||||
myHook.h_Data = myPrivateData;
|
||||
```
|
||||
|
||||
### Common Hook Uses
|
||||
|
||||
| API | Object (A2) | Message (A1) |
|
||||
|---|---|---|
|
||||
| BOOPSI `OM_SET` | BOOPSI object | `opSet` message |
|
||||
| `InstallLayerHook` | Layer | `struct BackFillMessage` |
|
||||
| Locale `FormatString` | — | Format data |
|
||||
| `DoMethod` (MUI) | MUI object | Method message |
|
||||
|
||||
---
|
||||
|
||||
## Date Utilities
|
||||
|
||||
```c
|
||||
#include <utility/date.h>
|
||||
|
||||
struct ClockData {
|
||||
UWORD sec; /* 0–59 */
|
||||
UWORD min; /* 0–59 */
|
||||
UWORD hour; /* 0–23 */
|
||||
UWORD mday; /* 1–31 */
|
||||
UWORD month; /* 1–12 */
|
||||
UWORD year; /* 1978+ */
|
||||
UWORD wday; /* 0=Sunday */
|
||||
};
|
||||
|
||||
/* Convert Amiga timestamp to date components: */
|
||||
struct ClockData cd;
|
||||
Amiga2Date(seconds, &cd); /* seconds since 1.1.1978 → date */
|
||||
ULONG secs = Date2Amiga(&cd); /* date → seconds */
|
||||
ULONG secs = CheckDate(&cd); /* validate and convert */
|
||||
Amiga2Date(seconds, &cd);
|
||||
Printf("%02d/%02d/%04d %02d:%02d:%02d\n",
|
||||
cd.mday, cd.month, cd.year,
|
||||
cd.hour, cd.min, cd.sec);
|
||||
|
||||
/* Convert date to Amiga timestamp: */
|
||||
cd.year = 2024; cd.month = 3; cd.mday = 15;
|
||||
cd.hour = 14; cd.min = 30; cd.sec = 0;
|
||||
ULONG secs = Date2Amiga(&cd);
|
||||
|
||||
/* Validate a date and get timestamp: */
|
||||
ULONG secs = CheckDate(&cd); /* returns 0 if invalid */
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The Amiga epoch is **January 1, 1978 00:00:00 UTC**. To convert to/from Unix timestamps (epoch 1970-01-01), add/subtract **252,460,800** seconds (8 years + 2 leap days).
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `utility/tagitem.h`, `utility/hooks.h`, `utility/date.h`
|
||||
- ADCD 2.1: utility.library autodocs
|
||||
- See also: [boopsi.md](../09_intuition/boopsi.md) — BOOPSI uses hooks extensively
|
||||
|
|
|
|||
|
|
@ -4,67 +4,185 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`workbench.library` provides APIs for interacting with the Workbench desktop: AppWindows, AppIcons, AppMenuItems, and startup message handling.
|
||||
`workbench.library` provides APIs for interacting with the Workbench desktop environment: receiving startup arguments, registering **AppWindows** (drag-and-drop targets), **AppIcons** (desktop icons), and **AppMenuItems** (Workbench Tools menu entries).
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Workbench"
|
||||
WB["Workbench Desktop"]
|
||||
WB --> AW["AppWindow<br/>(file drop target)"]
|
||||
WB --> AI["AppIcon<br/>(custom desktop icon)"]
|
||||
WB --> AM["AppMenuItem<br/>(Tools menu entry)"]
|
||||
end
|
||||
|
||||
subgraph "Application"
|
||||
PORT["MsgPort"] --> LOOP["Event Loop"]
|
||||
LOOP --> HANDLE["Handle AppMessage"]
|
||||
end
|
||||
|
||||
AW -->|"User drops file"| PORT
|
||||
AI -->|"User clicks icon"| PORT
|
||||
AM -->|"User selects menu"| PORT
|
||||
|
||||
style AW fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
style AI fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style AM fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WBStartup Message
|
||||
|
||||
When launched from Workbench, a program receives a `WBStartup` message instead of CLI arguments:
|
||||
When launched from Workbench (double-click an icon), the program receives a `WBStartup` message instead of CLI arguments:
|
||||
|
||||
```c
|
||||
struct WBStartup {
|
||||
struct Message sm_Message;
|
||||
struct MsgPort *sm_Process; /* process that sent us */
|
||||
BPTR sm_Segment; /* our loaded segment */
|
||||
LONG sm_NumArgs; /* number of arguments */
|
||||
char *sm_ToolWindow; /* tool window spec */
|
||||
struct WBArg *sm_ArgList; /* argument array */
|
||||
BPTR sm_Segment; /* our loaded segment list */
|
||||
LONG sm_NumArgs; /* number of arguments (1 = just the tool) */
|
||||
char *sm_ToolWindow; /* tool window spec (CON: string) */
|
||||
struct WBArg *sm_ArgList; /* array of arguments */
|
||||
};
|
||||
|
||||
struct WBArg {
|
||||
BPTR wa_Lock; /* directory lock */
|
||||
BYTE *wa_Name; /* filename */
|
||||
BPTR wa_Lock; /* directory lock (BPTR) */
|
||||
BYTE *wa_Name; /* filename (relative to wa_Lock) */
|
||||
};
|
||||
```
|
||||
|
||||
### Handling WBStartup
|
||||
|
||||
```c
|
||||
/* In main(): */
|
||||
struct WBStartup *wbmsg = NULL;
|
||||
if (!argc) { /* launched from Workbench */
|
||||
WaitPort(&((struct Process *)FindTask(NULL))->pr_MsgPort);
|
||||
wbmsg = (struct WBStartup *)GetMsg(
|
||||
&((struct Process *)FindTask(NULL))->pr_MsgPort);
|
||||
/* Process wbmsg->sm_ArgList for file arguments */
|
||||
|
||||
if (argc == 0)
|
||||
{
|
||||
/* Launched from Workbench — get the startup message: */
|
||||
struct Process *me = (struct Process *)FindTask(NULL);
|
||||
WaitPort(&me->pr_MsgPort);
|
||||
wbmsg = (struct WBStartup *)GetMsg(&me->pr_MsgPort);
|
||||
|
||||
/* ArgList[0] = the tool itself (our icon) */
|
||||
/* ArgList[1..n] = files the user shift-clicked or dropped on us */
|
||||
|
||||
/* CD to the tool's directory for file access: */
|
||||
BPTR oldDir = CurrentDir(wbmsg->sm_ArgList[0].wa_Lock);
|
||||
|
||||
/* Process dropped files: */
|
||||
for (int i = 1; i < wbmsg->sm_NumArgs; i++)
|
||||
{
|
||||
BPTR dir = wbmsg->sm_ArgList[i].wa_Lock;
|
||||
char *name = wbmsg->sm_ArgList[i].wa_Name;
|
||||
BPTR old = CurrentDir(dir);
|
||||
/* ... open and process file 'name' ... */
|
||||
CurrentDir(old);
|
||||
}
|
||||
|
||||
/* Read our ToolTypes: */
|
||||
struct DiskObject *dobj = GetDiskObject(wbmsg->sm_ArgList[0].wa_Name);
|
||||
if (dobj)
|
||||
{
|
||||
char *pubscreen = FindToolType(dobj->do_ToolTypes, "PUBSCREEN");
|
||||
FreeDiskObject(dobj);
|
||||
}
|
||||
|
||||
CurrentDir(oldDir);
|
||||
}
|
||||
/* ... do work ... */
|
||||
if (wbmsg) {
|
||||
|
||||
/* ... main program logic ... */
|
||||
|
||||
/* MUST reply before exiting: */
|
||||
if (wbmsg)
|
||||
{
|
||||
Forbid();
|
||||
ReplyMsg((struct Message *)wbmsg);
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> You **must** reply the WBStartup message before exiting, and you **must** call `Forbid()` before `ReplyMsg()`. If you reply without Forbid, Workbench may unload your code segment before your process finishes exiting → crash.
|
||||
|
||||
---
|
||||
|
||||
## AppWindow / AppIcon / AppMenuItem
|
||||
## AppWindow — File Drop Target
|
||||
|
||||
Register a window to receive files when the user drags icons to it:
|
||||
|
||||
```c
|
||||
/* Register a window to receive file drops: */
|
||||
struct AppWindow *appwin = AddAppWindow(1, 0, win, port, NULL);
|
||||
/* When user drags files to the window, receive AppMessage on port */
|
||||
struct MsgPort *appPort = CreateMsgPort();
|
||||
|
||||
struct AppMessage {
|
||||
struct Message am_Message;
|
||||
UWORD am_Type; /* AMTYPE_APPWINDOW, etc. */
|
||||
ULONG am_UserData;
|
||||
ULONG am_ID;
|
||||
LONG am_NumArgs; /* number of files dropped */
|
||||
struct WBArg *am_ArgList; /* array of file references */
|
||||
/* ... */
|
||||
};
|
||||
/* Register the window: */
|
||||
struct AppWindow *appWin = AddAppWindow(
|
||||
1, /* ID (your choice — returned in AppMessage) */
|
||||
0, /* user data */
|
||||
window, /* the Intuition Window */
|
||||
appPort, /* port for AppMessages */
|
||||
NULL); /* tags (NULL = defaults) */
|
||||
|
||||
RemoveAppWindow(appwin);
|
||||
/* In event loop: */
|
||||
struct AppMessage *amsg;
|
||||
while ((amsg = (struct AppMessage *)GetMsg(appPort)))
|
||||
{
|
||||
for (int i = 0; i < amsg->am_NumArgs; i++)
|
||||
{
|
||||
BPTR old = CurrentDir(amsg->am_ArgList[i].wa_Lock);
|
||||
Printf("Dropped: %s\n", amsg->am_ArgList[i].wa_Name);
|
||||
/* ... open and process file ... */
|
||||
CurrentDir(old);
|
||||
}
|
||||
ReplyMsg((struct Message *)amsg);
|
||||
}
|
||||
|
||||
/* Cleanup: */
|
||||
RemoveAppWindow(appWin);
|
||||
DeleteMsgPort(appPort);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AppIcon — Desktop Icon
|
||||
|
||||
Place a custom icon on the Workbench desktop:
|
||||
|
||||
```c
|
||||
struct DiskObject *icon = GetDiskObject("PROGDIR:myapp");
|
||||
|
||||
struct AppIcon *appIcon = AddAppIcon(
|
||||
1, /* ID */
|
||||
0, /* user data */
|
||||
"My App", /* label under the icon */
|
||||
appPort, /* port for messages */
|
||||
NULL, /* lock (NULL = Workbench root) */
|
||||
icon, /* the icon imagery */
|
||||
NULL); /* tags */
|
||||
|
||||
/* When user double-clicks or drops files on the AppIcon: */
|
||||
/* → AppMessage received on appPort (same as AppWindow) */
|
||||
|
||||
RemoveAppIcon(appIcon);
|
||||
FreeDiskObject(icon);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AppMenuItem — Tools Menu Entry
|
||||
|
||||
Add an entry to the Workbench "Tools" menu:
|
||||
|
||||
```c
|
||||
struct AppMenuItem *appMenu = AddAppMenuItem(
|
||||
1, /* ID */
|
||||
0, /* user data */
|
||||
"My Tool", /* menu text */
|
||||
appPort, /* port for messages */
|
||||
NULL); /* tags */
|
||||
|
||||
/* When user selects the menu item: */
|
||||
/* → AppMessage received, am_NumArgs=0 (no file args) */
|
||||
|
||||
RemoveAppMenuItem(appMenu);
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -72,3 +190,5 @@ RemoveAppWindow(appwin);
|
|||
## References
|
||||
|
||||
- NDK39: `workbench/workbench.h`, `workbench/startup.h`
|
||||
- ADCD 2.1: workbench.library autodocs
|
||||
- See also: [icon.md](icon.md) — icon.library for reading .info files and ToolTypes
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
# Networking — Overview
|
||||
|
||||
AmigaOS has no built-in TCP/IP stack. Third-party stacks (AmiTCP, Miami, Roadshow) provide `bsdsocket.library` — a BSD-compatible socket API in user-space. Network hardware is abstracted via the SANA-II device driver standard.
|
||||
|
||||
## Section Index
|
||||
|
||||
| File | Description |
|
||||
|---|---|
|
||||
| [bsdsocket.md](bsdsocket.md) | bsdsocket.library — BSD socket API |
|
||||
| [sana2.md](sana2.md) | SANA-II — standard network device driver interface |
|
||||
| [tcp_ip_stacks.md](tcp_ip_stacks.md) | Stack comparison: AmiTCP, Miami, Roadshow |
|
||||
| [protocols.md](protocols.md) | Protocol implementation: DHCP, DNS, HTTP |
|
||||
| [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 |
|
||||
| [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 |
|
||||
|
|
|
|||
|
|
@ -1,23 +1,39 @@
|
|||
[← Home](../README.md) · [Networking](README.md)
|
||||
|
||||
# bsdsocket.library — BSD Socket API
|
||||
# bsdsocket.library — BSD Socket API Reference
|
||||
|
||||
## Overview
|
||||
|
||||
`bsdsocket.library` is the AmigaOS implementation of the BSD socket API. It is provided by the active TCP/IP stack (AmiTCP, Miami, Roadshow) and presents a POSIX-like socket interface adapted to the Amiga's library-based architecture.
|
||||
`bsdsocket.library` is the AmigaOS implementation of the BSD socket API. It is provided by the active TCP/IP stack (AmiTCP, Miami, Roadshow). See [TCP/IP Stacks](tcp_ip_stacks.md) for how the stack architecture works and how it differs from Unix. See [Protocols](protocols.md) for working code examples.
|
||||
|
||||
---
|
||||
|
||||
## Opening
|
||||
## Per-Task Library Base
|
||||
|
||||
Unlike Unix (kernel-managed fd table), each Amiga task must open its own private `SocketBase`:
|
||||
|
||||
```c
|
||||
struct Library *SocketBase = OpenLibrary("bsdsocket.library", 4);
|
||||
if (!SocketBase) { /* no TCP/IP stack running */ }
|
||||
if (!SocketBase)
|
||||
{
|
||||
Printf("No TCP/IP stack running\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Configure per-task error variable: */
|
||||
LONG errno;
|
||||
SocketBaseTags(
|
||||
SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(LONG))), (ULONG)&errno,
|
||||
SBTM_SETVAL(SBTC_LOGTAGPTR), (ULONG)"myapp",
|
||||
TAG_DONE);
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> **Never share `SocketBase` between tasks.** Each task MUST `OpenLibrary` its own copy. Sharing causes socket state corruption and random crashes. This is the #1 Amiga networking bug.
|
||||
|
||||
---
|
||||
|
||||
## Core Functions (LVO Mapping)
|
||||
## Function Table (LVO Mapping)
|
||||
|
||||
| LVO | Function | BSD Equivalent |
|
||||
|---|---|---|
|
||||
|
|
@ -36,58 +52,83 @@ if (!SocketBase) { /* no TCP/IP stack running */ }
|
|||
| −102 | `gethostbyname(name)` | `gethostbyname()` |
|
||||
| −108 | `gethostbyaddr(addr, len, type)` | `gethostbyaddr()` |
|
||||
| −114 | `getnetbyname(name)` | `getnetbyname()` |
|
||||
| −168 | `Errno()` | `errno` (returns last error) |
|
||||
| −168 | `Errno()` | `errno` |
|
||||
| −174 | `CloseSocket(sock)` | `close()` |
|
||||
| −180 | `WaitSelect(nfds, rd, wr, ex, timeout, sigmask)` | `select()` + signals |
|
||||
| −210 | `inet_addr(cp)` | `inet_addr()` |
|
||||
| −216 | `Inet_NtoA(in)` | `inet_ntoa()` |
|
||||
| −222 | `inet_makeaddr(net, host)` | `inet_makeaddr()` |
|
||||
| −252 | `getservbyname(name, proto)` | `getservbyname()` |
|
||||
| −270 | `SocketBaseTagList(tags)` | (Amiga-specific) |
|
||||
|
||||
---
|
||||
|
||||
## WaitSelect — Amiga-Enhanced select()
|
||||
|
||||
Unlike BSD `select()`, `WaitSelect` integrates with Exec signals:
|
||||
The key Amiga-specific extension: `WaitSelect` simultaneously waits on socket file descriptors **and** Exec signal bits. This replaces the need for separate threads — a single event loop can handle sockets, windows, timers, and ARexx:
|
||||
|
||||
```c
|
||||
fd_set rdset;
|
||||
FD_ZERO(&rdset);
|
||||
FD_SET(sock, &rdset);
|
||||
|
||||
ULONG sigmask = SIGBREAKF_CTRL_C; /* also wait for Ctrl-C */
|
||||
struct timeval tv = { 5, 0 }; /* 5 second timeout */
|
||||
|
||||
LONG n = WaitSelect(sock + 1, &rdset, NULL, NULL, &tv, &sigmask);
|
||||
if (n > 0 && FD_ISSET(sock, &rdset)) { /* data ready */ }
|
||||
if (sigmask & SIGBREAKF_CTRL_C) { /* user pressed Ctrl-C */ }
|
||||
LONG WaitSelect(LONG nfds,
|
||||
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||
struct timeval *timeout,
|
||||
ULONG *sigmask);
|
||||
/* sigmask: on entry = signals to also wait for
|
||||
on exit = which signals fired */
|
||||
```
|
||||
|
||||
See [protocols.md](protocols.md) for a complete working example combining socket I/O with Intuition window events.
|
||||
|
||||
---
|
||||
|
||||
## Simple TCP Client
|
||||
## Error Handling
|
||||
|
||||
```c
|
||||
/* Amiga sockets use Errno() instead of global errno: */
|
||||
LONG sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
struct sockaddr_in addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(80);
|
||||
addr.sin_addr.s_addr = inet_addr("93.184.216.34");
|
||||
|
||||
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
|
||||
send(sock, "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n", 38, 0);
|
||||
char buf[4096];
|
||||
LONG n = recv(sock, buf, sizeof(buf) - 1, 0);
|
||||
buf[n] = 0;
|
||||
Printf("%s\n", buf);
|
||||
if (sock < 0)
|
||||
{
|
||||
LONG err = Errno();
|
||||
Printf("socket() failed: error %ld\n", err);
|
||||
}
|
||||
CloseSocket(sock);
|
||||
|
||||
/* Or set up a per-task errno pointer (recommended): */
|
||||
LONG myErrno;
|
||||
SocketBaseTags(
|
||||
SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(LONG))), (ULONG)&myErrno,
|
||||
TAG_DONE);
|
||||
/* Now myErrno is updated automatically after each call */
|
||||
```
|
||||
|
||||
### Common Error Codes
|
||||
|
||||
| Value | Name | Meaning |
|
||||
|---|---|---|
|
||||
| 48 | `EADDRINUSE` | Address already in use |
|
||||
| 60 | `ETIMEDOUT` | Connection timed out |
|
||||
| 61 | `ECONNREFUSED` | Connection refused |
|
||||
| 64 | `EHOSTDOWN` | Host is down |
|
||||
| 65 | `EHOSTUNREACH` | No route to host |
|
||||
|
||||
---
|
||||
|
||||
## Differences from BSD/POSIX Sockets
|
||||
|
||||
| Aspect | BSD/POSIX | Amiga bsdsocket.library |
|
||||
|---|---|---|
|
||||
| Close socket | `close(fd)` | `CloseSocket(sock)` — `close()` is AmigaDOS |
|
||||
| Error variable | Global `errno` | Call `Errno()` or set `SBTC_ERRNOPTR` |
|
||||
| Multiplexing | `select()` / `poll()` / `epoll()` | `WaitSelect()` — also waits on Exec signals |
|
||||
| Headers | `<sys/socket.h>` | `<proto/socket.h>` or stack-specific |
|
||||
| Lifecycle | Kernel-managed | Must `OpenLibrary`/`CloseLibrary` per task |
|
||||
| fd namespace | Shared with files | Sockets are separate from DOS file handles |
|
||||
| Multi-thread | fd shared across threads | `SocketBase` is per-task, not shareable |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `libraries/bsdsocket.h` (stack-specific)
|
||||
- Roadshow SDK documentation
|
||||
- [sana2.md](sana2.md) — network device driver layer
|
||||
- AmiTCP SDK: `bsdsocket.h` and autodocs
|
||||
- See also: [tcp_ip_stacks.md](tcp_ip_stacks.md) — stack architecture and configuration
|
||||
- See also: [protocols.md](protocols.md) — working code examples (TCP, UDP, DNS)
|
||||
- See also: [sana2.md](sana2.md) — network device driver layer below the stack
|
||||
|
|
|
|||
|
|
@ -1,67 +1,245 @@
|
|||
[← Home](../README.md) · [Networking](README.md)
|
||||
|
||||
# Protocol Implementation — DHCP, DNS, HTTP
|
||||
# Protocol Implementation — DNS, TCP, UDP, WaitSelect
|
||||
|
||||
## Overview
|
||||
|
||||
With `bsdsocket.library` providing a BSD-compatible API, standard network protocols can be implemented using familiar patterns adapted for the Amiga's single-address-space, library-based architecture.
|
||||
Working code examples for common network protocols on AmigaOS using [bsdsocket.library](bsdsocket.md). See [TCP/IP Stacks](tcp_ip_stacks.md) for the architecture that makes this possible and how it differs from Unix.
|
||||
|
||||
> [!NOTE]
|
||||
> Amiga socket functions are **library calls** (via `bsdsocket.library`), not system calls. You must `OpenLibrary("bsdsocket.library", ...)` before using any socket function. Use `CloseSocket()` instead of `close()`, and `Errno()` instead of `errno`.
|
||||
|
||||
---
|
||||
|
||||
## DNS Resolution
|
||||
|
||||
```c
|
||||
/* gethostbyname — provided by bsdsocket.library: */
|
||||
struct Library *SocketBase = OpenLibrary("bsdsocket.library", 4);
|
||||
if (!SocketBase) return;
|
||||
|
||||
struct hostent *he = gethostbyname("www.amiga.org");
|
||||
if (he) {
|
||||
struct in_addr addr = *(struct in_addr *)he->h_addr;
|
||||
Printf("IP: %s\n", Inet_NtoA(addr.s_addr));
|
||||
if (he)
|
||||
{
|
||||
struct in_addr addr;
|
||||
CopyMem(he->h_addr, &addr, sizeof(addr));
|
||||
Printf("Host: %s\n", he->h_name);
|
||||
Printf("IP: %s\n", Inet_NtoA(addr.s_addr));
|
||||
|
||||
/* May have multiple addresses: */
|
||||
char **p;
|
||||
for (p = he->h_addr_list; *p; p++)
|
||||
{
|
||||
CopyMem(*p, &addr, sizeof(addr));
|
||||
Printf(" Addr: %s\n", Inet_NtoA(addr.s_addr));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Printf("DNS lookup failed\n");
|
||||
}
|
||||
|
||||
CloseLibrary(SocketBase);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP Client (Minimal)
|
||||
## TCP Client
|
||||
|
||||
```c
|
||||
struct Library *SocketBase = OpenLibrary("bsdsocket.library", 4);
|
||||
if (!SocketBase) return;
|
||||
|
||||
LONG sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock < 0) { Printf("socket() failed\n"); goto out; }
|
||||
|
||||
struct hostent *he = gethostbyname("www.example.com");
|
||||
if (!he) { Printf("DNS failed\n"); goto close; }
|
||||
|
||||
struct sockaddr_in sa;
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(80);
|
||||
CopyMem(he->h_addr, &sa.sin_addr, he->h_length);
|
||||
|
||||
connect(sock, (struct sockaddr *)&sa, sizeof(sa));
|
||||
if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0)
|
||||
{
|
||||
Printf("connect failed: %ld\n", Errno());
|
||||
goto close;
|
||||
}
|
||||
|
||||
/* Send HTTP request: */
|
||||
char req[] = "GET / HTTP/1.0\r\nHost: www.example.com\r\n\r\n";
|
||||
send(sock, req, strlen(req), 0);
|
||||
|
||||
char buf[8192];
|
||||
LONG total = 0, n;
|
||||
while ((n = recv(sock, buf + total, sizeof(buf) - total - 1, 0)) > 0)
|
||||
total += n;
|
||||
buf[total] = 0;
|
||||
Printf("%s\n", buf);
|
||||
/* Receive response: */
|
||||
char buf[4096];
|
||||
LONG n;
|
||||
while ((n = recv(sock, buf, sizeof(buf) - 1, 0)) > 0)
|
||||
{
|
||||
buf[n] = 0;
|
||||
Printf("%s", buf);
|
||||
}
|
||||
|
||||
CloseSocket(sock);
|
||||
close:
|
||||
CloseSocket(sock);
|
||||
out:
|
||||
CloseLibrary(SocketBase);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DHCP Overview
|
||||
## TCP Server
|
||||
|
||||
DHCP on Amiga is typically handled by the TCP/IP stack itself (Roadshow, Miami) or an external client (AmiTCP + `dhclient`). The sequence is standard:
|
||||
```c
|
||||
LONG listenSock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
1. `DHCPDISCOVER` — broadcast on port 67
|
||||
struct sockaddr_in sa;
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(8080);
|
||||
sa.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
bind(listenSock, (struct sockaddr *)&sa, sizeof(sa));
|
||||
listen(listenSock, 5);
|
||||
|
||||
Printf("Listening on port 8080...\n");
|
||||
|
||||
while (running)
|
||||
{
|
||||
struct sockaddr_in clientAddr;
|
||||
LONG addrLen = sizeof(clientAddr);
|
||||
LONG clientSock = accept(listenSock,
|
||||
(struct sockaddr *)&clientAddr,
|
||||
&addrLen);
|
||||
if (clientSock >= 0)
|
||||
{
|
||||
Printf("Connection from %s\n",
|
||||
Inet_NtoA(clientAddr.sin_addr.s_addr));
|
||||
|
||||
char response[] = "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Type: text/html\r\n\r\n"
|
||||
"<h1>Hello from Amiga!</h1>\n";
|
||||
send(clientSock, response, strlen(response), 0);
|
||||
CloseSocket(clientSock);
|
||||
}
|
||||
}
|
||||
|
||||
CloseSocket(listenSock);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UDP Datagram
|
||||
|
||||
```c
|
||||
LONG udpSock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
|
||||
/* Send: */
|
||||
struct sockaddr_in dest;
|
||||
dest.sin_family = AF_INET;
|
||||
dest.sin_port = htons(9999);
|
||||
dest.sin_addr.s_addr = inet_addr("192.168.1.255");
|
||||
|
||||
char msg[] = "Hello UDP";
|
||||
sendto(udpSock, msg, strlen(msg), 0,
|
||||
(struct sockaddr *)&dest, sizeof(dest));
|
||||
|
||||
/* Receive: */
|
||||
char buf[1024];
|
||||
struct sockaddr_in from;
|
||||
LONG fromLen = sizeof(from);
|
||||
LONG n = recvfrom(udpSock, buf, sizeof(buf), 0,
|
||||
(struct sockaddr *)&from, &fromLen);
|
||||
buf[n] = 0;
|
||||
Printf("From %s: %s\n", Inet_NtoA(from.sin_addr.s_addr), buf);
|
||||
|
||||
CloseSocket(udpSock);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WaitSelect — Combined Socket + GUI Event Loop
|
||||
|
||||
The key pattern for responsive Amiga network applications. `WaitSelect` simultaneously waits on sockets **and** Exec signals (windows, ARexx, timers) — no threads needed:
|
||||
|
||||
```c
|
||||
/* Set up signal masks: */
|
||||
ULONG winSig = 1 << window->UserPort->mp_SigBit;
|
||||
ULONG ctrlSig = SIGBREAKF_CTRL_C;
|
||||
|
||||
fd_set readFDs;
|
||||
struct timeval tv;
|
||||
|
||||
BOOL running = TRUE;
|
||||
while (running)
|
||||
{
|
||||
FD_ZERO(&readFDs);
|
||||
FD_SET(sock, &readFDs);
|
||||
tv.tv_secs = 1;
|
||||
tv.tv_micro = 0;
|
||||
|
||||
ULONG sigmask = winSig | ctrlSig;
|
||||
|
||||
LONG result = WaitSelect(sock + 1, &readFDs, NULL, NULL,
|
||||
&tv, &sigmask);
|
||||
|
||||
/* Socket data ready? */
|
||||
if (result > 0 && FD_ISSET(sock, &readFDs))
|
||||
{
|
||||
LONG n = recv(sock, buffer, sizeof(buffer), 0);
|
||||
if (n > 0)
|
||||
{
|
||||
/* process network data */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* connection closed or error */
|
||||
running = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Window event? */
|
||||
if (sigmask & winSig)
|
||||
{
|
||||
struct IntuiMessage *imsg;
|
||||
while ((imsg = (struct IntuiMessage *)GetMsg(window->UserPort)))
|
||||
{
|
||||
switch (imsg->Class)
|
||||
{
|
||||
case IDCMP_CLOSEWINDOW:
|
||||
running = FALSE;
|
||||
break;
|
||||
/* ... handle other IDCMP events ... */
|
||||
}
|
||||
ReplyMsg((struct Message *)imsg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Ctrl-C? */
|
||||
if (sigmask & ctrlSig)
|
||||
{
|
||||
Printf("*** Break\n");
|
||||
running = FALSE;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> `WaitSelect` eliminates the need for multi-threading in most Amiga network apps. A single event loop handles sockets, GUI, timers, and ARexx simultaneously — simpler and safer than threads in a non-protected memory environment.
|
||||
|
||||
---
|
||||
|
||||
## DHCP
|
||||
|
||||
DHCP is handled by the TCP/IP stack, not by applications. See [TCP/IP Stacks](tcp_ip_stacks.md) for configuration. The standard sequence is:
|
||||
|
||||
1. `DHCPDISCOVER` — stack broadcasts on port 67
|
||||
2. `DHCPOFFER` — server responds with IP offer
|
||||
3. `DHCPREQUEST` — client accepts
|
||||
4. `DHCPACK` — server confirms; lease begins
|
||||
|
||||
For MiSTer/FPGA cores with custom SANA-II drivers, DHCP is handled automatically once the SANA-II driver is online and the stack is configured for `IPTYPE=DHCP`.
|
||||
3. `DHCPREQUEST` — stack accepts
|
||||
4. `DHCPACK` — server confirms; interface is configured
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [bsdsocket.md](bsdsocket.md) — socket API reference
|
||||
- [tcp_ip_stacks.md](tcp_ip_stacks.md) — stack configuration
|
||||
- [bsdsocket.md](bsdsocket.md) — API reference and LVO table
|
||||
- [tcp_ip_stacks.md](tcp_ip_stacks.md) — stack architecture and configuration
|
||||
- [sana2.md](sana2.md) — SANA-II driver layer
|
||||
|
|
|
|||
|
|
@ -4,75 +4,198 @@
|
|||
|
||||
## Overview
|
||||
|
||||
SANA-II is the standard device driver interface for network hardware on AmigaOS. It defines a uniform API that TCP/IP stacks use to communicate with any network card (Ethernet, WiFi, PPP, etc.).
|
||||
SANA-II (Standard Amiga Networking Architecture, version II) is the standardised `exec.device` interface between TCP/IP stacks and network hardware drivers. Any SANA-II compliant driver works with any SANA-II compliant stack — providing hardware independence analogous to NDIS on Windows or the Linux kernel network driver model.
|
||||
|
||||
---
|
||||
See [TCP/IP Stacks](tcp_ip_stacks.md) for how SANA-II fits into the full networking architecture.
|
||||
|
||||
## Architecture
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "TCP/IP Stacks (any)"
|
||||
AT["AmiTCP"]
|
||||
RS["Roadshow"]
|
||||
MI["Miami"]
|
||||
end
|
||||
|
||||
```
|
||||
Application
|
||||
↓
|
||||
bsdsocket.library (TCP/IP stack)
|
||||
↓
|
||||
SANA-II device driver (e.g., prism2.device, a2065.device)
|
||||
↓
|
||||
Network hardware
|
||||
subgraph "SANA-II API (standardised)"
|
||||
API["CMD_READ / CMD_WRITE<br/>S2_ONLINE / S2_OFFLINE<br/>S2_DEVICEQUERY<br/>Buffer Management Hooks"]
|
||||
end
|
||||
|
||||
subgraph "SANA-II Drivers (any)"
|
||||
A2065["a2065.device<br/>(Ethernet)"]
|
||||
ARIADNE["ariadne.device<br/>(Ethernet)"]
|
||||
PPP["ppp.device<br/>(dial-up)"]
|
||||
MISTER["mister_eth.device<br/>(MiSTer virtual)"]
|
||||
end
|
||||
|
||||
AT --> API
|
||||
RS --> API
|
||||
MI --> API
|
||||
API --> A2065
|
||||
API --> ARIADNE
|
||||
API --> PPP
|
||||
API --> MISTER
|
||||
|
||||
style API fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Opening a SANA-II Device
|
||||
|
||||
SANA-II drivers require **buffer management hooks** — callback functions the driver uses to copy packet data to/from the application's buffers:
|
||||
|
||||
```c
|
||||
#include <devices/sana2.h>
|
||||
|
||||
struct MsgPort *port = CreateMsgPort();
|
||||
struct IOSana2Req *s2req = (struct IOSana2Req *)
|
||||
CreateIORequest(port, sizeof(struct IOSana2Req));
|
||||
|
||||
/* Provide buffer management hooks: */
|
||||
static struct TagItem s2tags[] = {
|
||||
{ S2_CopyToBuff, (ULONG)CopyToBuff },
|
||||
{ S2_CopyFromBuff, (ULONG)CopyFromBuff },
|
||||
/* Buffer management hooks (required): */
|
||||
static struct TagItem bufferTags[] = {
|
||||
{ S2_CopyToBuff, (ULONG)MyCopyToBuff },
|
||||
{ S2_CopyFromBuff, (ULONG)MyCopyFromBuff },
|
||||
{ TAG_DONE, 0 }
|
||||
};
|
||||
s2req->ios2_BufferManagement = s2tags;
|
||||
s2req->ios2_BufferManagement = bufferTags;
|
||||
|
||||
OpenDevice("a2065.device", 0, (struct IORequest *)s2req, 0);
|
||||
if (OpenDevice("a2065.device", 0, (struct IORequest *)s2req, 0))
|
||||
{
|
||||
Printf("Cannot open network device\n");
|
||||
}
|
||||
```
|
||||
|
||||
### Buffer Management Hooks
|
||||
|
||||
```c
|
||||
/* Called by the driver to copy received data into your buffer: */
|
||||
BOOL __asm MyCopyToBuff(register __a0 APTR to,
|
||||
register __a1 APTR from,
|
||||
register __d0 ULONG length)
|
||||
{
|
||||
CopyMem(from, to, length);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Called by the driver to copy transmit data from your buffer: */
|
||||
BOOL __asm MyCopyFromBuff(register __a0 APTR to,
|
||||
register __a1 APTR from,
|
||||
register __d0 ULONG length)
|
||||
{
|
||||
CopyMem(from, to, length);
|
||||
return TRUE;
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Buffer management hooks exist because SANA-II drivers may use DMA or special memory regions. The hooks let the driver control how data is copied — the stack never accesses driver buffers directly.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
| Code | Constant | Description |
|
||||
|---|---|---|
|
||||
| 2 | `CMD_READ` | Read a packet |
|
||||
| 3 | `CMD_WRITE` | Send a packet |
|
||||
| 9 | `S2_DEVICEQUERY` | Query hardware capabilities |
|
||||
| 10 | `S2_GETSTATIONADDRESS` | Get MAC address |
|
||||
| 11 | `S2_CONFIGINTERFACE` | Configure interface (set MAC) |
|
||||
| 14 | `S2_ONLINE` | Bring interface online |
|
||||
| 15 | `S2_OFFLINE` | Take interface offline |
|
||||
| 21 | `S2_GETGLOBALSTATS` | Get packet statistics |
|
||||
| 16 | `S2_ADDMULTICASTADDRESS` | Add multicast address |
|
||||
| 17 | `S2_DELMULTICASTADDRESS` | Remove multicast address |
|
||||
| Code | Constant | Direction | Description |
|
||||
|---|---|---|---|
|
||||
| 2 | `CMD_READ` | Receive | Read a packet (kept outstanding; completed when packet arrives) |
|
||||
| 3 | `CMD_WRITE` | Transmit | Send a packet |
|
||||
| 9 | `S2_DEVICEQUERY` | Query | Get hardware capabilities (type, MTU, speed) |
|
||||
| 10 | `S2_GETSTATIONADDRESS` | Query | Get hardware (MAC) address |
|
||||
| 11 | `S2_CONFIGINTERFACE` | Config | Set the hardware address |
|
||||
| 14 | `S2_ONLINE` | Control | Bring interface up (start receiving) |
|
||||
| 15 | `S2_OFFLINE` | Control | Take interface down |
|
||||
| 16 | `S2_ADDMULTICASTADDRESS` | Config | Subscribe to multicast address |
|
||||
| 17 | `S2_DELMULTICASTADDRESS` | Config | Unsubscribe from multicast |
|
||||
| 21 | `S2_GETGLOBALSTATS` | Query | Get packet/byte counters |
|
||||
| 22 | `S2_GETSPECIALSTATS` | Query | Get driver-specific statistics |
|
||||
|
||||
---
|
||||
|
||||
## Packet Reading
|
||||
## Sending a Packet
|
||||
|
||||
```c
|
||||
/* Prepare an Ethernet frame: */
|
||||
s2req->ios2_Req.io_Command = CMD_WRITE;
|
||||
s2req->ios2_WireError = 0;
|
||||
s2req->ios2_PacketType = 0x0800; /* IPv4 EtherType */
|
||||
s2req->ios2_DataLength = packetLength;
|
||||
|
||||
/* Set destination MAC: */
|
||||
CopyMem(destMAC, s2req->ios2_DstAddr, 6);
|
||||
|
||||
/* ios2_Data points to the packet payload (after Ethernet header): */
|
||||
s2req->ios2_Data = packetData;
|
||||
|
||||
DoIO((struct IORequest *)s2req);
|
||||
if (s2req->ios2_Req.io_Error)
|
||||
Printf("Send error: %ld (wire: %ld)\n",
|
||||
s2req->ios2_Req.io_Error, s2req->ios2_WireError);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Receiving Packets
|
||||
|
||||
The stack posts **multiple read requests** to keep a pipeline full:
|
||||
|
||||
```c
|
||||
/* Post a read request (non-blocking): */
|
||||
s2req->ios2_Req.io_Command = CMD_READ;
|
||||
s2req->ios2_WireError = 0;
|
||||
s2req->ios2_PacketType = 0x0800; /* IPv4 */
|
||||
s2req->ios2_PacketType = 0x0800; /* only IPv4 packets */
|
||||
SendIO((struct IORequest *)s2req);
|
||||
/* wait for packet... */
|
||||
|
||||
/* Wait for a packet: */
|
||||
WaitIO((struct IORequest *)s2req);
|
||||
/* s2req->ios2_Data* contains the received packet */
|
||||
|
||||
Printf("Received %ld bytes from %02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||
s2req->ios2_DataLength,
|
||||
s2req->ios2_SrcAddr[0], s2req->ios2_SrcAddr[1],
|
||||
s2req->ios2_SrcAddr[2], s2req->ios2_SrcAddr[3],
|
||||
s2req->ios2_SrcAddr[4], s2req->ios2_SrcAddr[5]);
|
||||
|
||||
/* Immediately post another read to keep the pipeline full: */
|
||||
s2req->ios2_Req.io_Command = CMD_READ;
|
||||
SendIO((struct IORequest *)s2req);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Querying Device Capabilities
|
||||
|
||||
```c
|
||||
struct Sana2DeviceQuery query;
|
||||
query.SizeAvailable = sizeof(query);
|
||||
|
||||
s2req->ios2_Req.io_Command = S2_DEVICEQUERY;
|
||||
s2req->ios2_StatData = &query;
|
||||
DoIO((struct IORequest *)s2req);
|
||||
|
||||
Printf("Hardware type: %ld\n", query.HardwareType); /* 1 = Ethernet */
|
||||
Printf("MTU: %ld bytes\n", query.MTU); /* 1500 for Ethernet */
|
||||
Printf("Speed: %ld bps\n", query.BPS); /* 10000000 for 10 Mbps */
|
||||
Printf("Address size: %ld bits\n", query.AddrFieldSize); /* 48 for Ethernet MAC */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Writing a SANA-II Driver
|
||||
|
||||
A SANA-II driver is a standard exec.device that implements the SANA-II command set. Key requirements:
|
||||
|
||||
| Requirement | Detail |
|
||||
|---|---|
|
||||
| Must be an exec.device | Standard `Open/Close/BeginIO/AbortIO` entry points |
|
||||
| Buffer management | Must use the caller's buffer hooks — never copy directly |
|
||||
| Multiple openers | Must support multiple tasks opening the device simultaneously |
|
||||
| Promiscuous mode | Should support `S2_ONEVENT` for link-level monitoring |
|
||||
| Error reporting | Must set both `io_Error` and `ios2_WireError` |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- SANA-II Network Device Driver Specification (Commodore)
|
||||
- SANA-II Network Device Driver Specification (Commodore, 1992)
|
||||
- NDK39: `devices/sana2.h`
|
||||
- Aminet: `docs/hard/sana2.lha` — specification document
|
||||
- See also: [tcp_ip_stacks.md](tcp_ip_stacks.md) — stack architecture
|
||||
- See also: [bsdsocket.md](bsdsocket.md) — socket API above SANA-II
|
||||
|
|
|
|||
|
|
@ -1,66 +1,426 @@
|
|||
[← Home](../README.md) · [Networking](README.md)
|
||||
|
||||
# TCP/IP Stacks — AmiTCP, Miami, Roadshow
|
||||
# TCP/IP Stacks — Architecture, Integration, and Configuration
|
||||
|
||||
## Overview
|
||||
|
||||
AmigaOS has no built-in TCP/IP stack. Third-party stacks provide `bsdsocket.library`. All stacks present the same API to applications — only configuration and driver support differ.
|
||||
AmigaOS has **no built-in TCP/IP stack** — networking is entirely provided by third-party software that installs as an Amiga shared library (`bsdsocket.library`). This is fundamentally different from every other major OS where TCP/IP is a kernel subsystem.
|
||||
|
||||
---
|
||||
|
||||
## Stack Comparison
|
||||
## Architecture — How Amiga TCP/IP Differs
|
||||
|
||||
| Feature | AmiTCP 3.0b2 | Miami 3.2 | Roadshow 1.15 |
|
||||
|---|---|---|---|
|
||||
| License | Free (Genesis fork) | Commercial | Commercial (demo available) |
|
||||
| API version | bsdsocket.library v3 | v4 | v4 |
|
||||
| IPv6 | No | No | No |
|
||||
| PPP | Via serial | Built-in | Via driver |
|
||||
| DHCP | External (dhclient) | Built-in | Built-in |
|
||||
| DNS cache | No | Yes | Yes |
|
||||
| SANA-II | Yes | Yes | Yes |
|
||||
| GUI config | MUI prefs | Miami prefs | Roadshow prefs |
|
||||
| Active development | No | No | Yes (Olaf Barthel) |
|
||||
| MiSTer recommended | ✅ (free) | — | ✅ (most capable) |
|
||||
### The Library Model vs. Kernel Sockets
|
||||
|
||||
---
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Unix / Windows / macOS"
|
||||
UAPP["Application"] -->|"syscall boundary<br/>(kernel trap)"| KERN["Kernel TCP/IP Stack"]
|
||||
KERN --> KDRV["Kernel NIC Driver"]
|
||||
KDRV --> HW1["Network Hardware"]
|
||||
end
|
||||
|
||||
## Configuration (Roadshow)
|
||||
subgraph "AmigaOS"
|
||||
AAPP["Application"] -->|"Library call<br/>(JSR through LVO)"| BSL["bsdsocket.library<br/>(user-space process)"]
|
||||
BSL -->|"SANA-II IORequests"| SANA["SANA-II Driver<br/>(exec device)"]
|
||||
SANA --> HW2["Network Hardware"]
|
||||
end
|
||||
|
||||
```
|
||||
; DEVS:NetInterfaces/prism2
|
||||
DEVICE=prism2.device
|
||||
UNIT=0
|
||||
IPTYPE=DHCP
|
||||
; or:
|
||||
; ADDRESS=192.168.1.100
|
||||
; NETMASK=255.255.255.0
|
||||
; GATEWAY=192.168.1.1
|
||||
|
||||
; DEVS:NetInterfaces/lo0
|
||||
DEVICE=lo0.device
|
||||
UNIT=0
|
||||
ADDRESS=127.0.0.1
|
||||
NETMASK=255.0.0.0
|
||||
style KERN fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
style BSL fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
| Aspect | Unix/Linux/Windows | AmigaOS |
|
||||
|---|---|---|
|
||||
| **Stack location** | Kernel (ring 0) | User-space process (same address space) |
|
||||
| **Socket API** | System calls (trap to kernel) | Library vector calls (JSR) — no context switch |
|
||||
| **Driver model** | Kernel module / NDIS | SANA-II exec.device (user-space) |
|
||||
| **Multiple stacks** | One per kernel | Multiple possible (only one active) |
|
||||
| **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 |
|
||||
|
||||
### The Full Network Stack
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Applications"
|
||||
IBR["IBrowse<br/>(HTTP)"]
|
||||
FTP["AmiFTP<br/>(FTP)"]
|
||||
IRC["AmiRC<br/>(IRC)"]
|
||||
PING["ping<br/>(ICMP)"]
|
||||
end
|
||||
|
||||
subgraph "bsdsocket.library"
|
||||
SOCK["Socket Layer<br/>(socket, bind, connect,<br/>send, recv, select)"]
|
||||
TCP["TCP"]
|
||||
UDP["UDP"]
|
||||
ICMP["ICMP"]
|
||||
IP["IP Layer<br/>(routing, fragmentation)"]
|
||||
ARP["ARP<br/>(address resolution)"]
|
||||
end
|
||||
|
||||
subgraph "Link Layer"
|
||||
SANA2["SANA-II Interface<br/>(standardised IORequest API)"]
|
||||
end
|
||||
|
||||
subgraph "Hardware Drivers"
|
||||
ETH["Ethernet Driver<br/>(e.g., ariadne.device)"]
|
||||
PPP["PPP Driver<br/>(e.g., ppp.device)"]
|
||||
SLIP["SLIP Driver"]
|
||||
LO["Loopback<br/>(lo0.device)"]
|
||||
end
|
||||
|
||||
subgraph "Physical"
|
||||
NIC["Ethernet NIC<br/>(Zorro/PCMCIA)"]
|
||||
MODEM["Serial Modem<br/>(via serial.device)"]
|
||||
LOOP["Internal loopback"]
|
||||
end
|
||||
|
||||
IBR --> SOCK
|
||||
FTP --> SOCK
|
||||
IRC --> SOCK
|
||||
PING --> SOCK
|
||||
|
||||
SOCK --> TCP --> IP
|
||||
SOCK --> UDP --> IP
|
||||
SOCK --> ICMP --> IP
|
||||
IP --> ARP --> SANA2
|
||||
IP --> SANA2
|
||||
|
||||
SANA2 --> ETH --> NIC
|
||||
SANA2 --> PPP --> MODEM
|
||||
SANA2 --> SLIP --> MODEM
|
||||
SANA2 --> LO --> LOOP
|
||||
|
||||
style SOCK fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
style SANA2 fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style IP fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
### Per-Opener Library Base
|
||||
|
||||
Each Amiga task that opens `bsdsocket.library` gets its own **private library base** with isolated socket state — there is no kernel fd table. This means `SocketBase` must **never** be shared between tasks. See [bsdsocket.md](bsdsocket.md) for the full API reference and per-task setup pattern.
|
||||
|
||||
---
|
||||
|
||||
## Configuration (AmiTCP)
|
||||
## SANA-II — The Network Driver Layer
|
||||
|
||||
SANA-II (Standard Amiga Networking Architecture, version II) is the standardised interface between TCP/IP stacks and network hardware drivers. It's an `exec.device` protocol — drivers communicate via IORequests.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "TCP/IP Stack"
|
||||
IP["IP Layer"]
|
||||
end
|
||||
|
||||
subgraph "SANA-II API"
|
||||
direction TB
|
||||
OPEN["OpenDevice<br/>(driver, unit)"]
|
||||
WRITE["CMD_WRITE<br/>(send frame)"]
|
||||
READ["CMD_READ<br/>(receive frame)"]
|
||||
ONLINE["S2_ONLINE<br/>(bring up link)"]
|
||||
CONFIG["S2_CONFIGINTERFACE<br/>(set HW address)"]
|
||||
QUERY["S2_DEVICEQUERY<br/>(get capabilities)"]
|
||||
end
|
||||
|
||||
subgraph "Drivers"
|
||||
A2065["a2065.device<br/>(Commodore Ethernet)"]
|
||||
ARIADNE["ariadne.device<br/>(Village Tronic)"]
|
||||
CNET["cnet.device<br/>(Hydra/X-Surf)"]
|
||||
PPPDEV["ppp.device<br/>(dial-up modem)"]
|
||||
end
|
||||
|
||||
IP --> OPEN
|
||||
IP --> WRITE
|
||||
IP --> READ
|
||||
OPEN --> A2065
|
||||
OPEN --> ARIADNE
|
||||
OPEN --> CNET
|
||||
OPEN --> PPPDEV
|
||||
|
||||
style OPEN fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
### How the Stack Sends a Packet
|
||||
|
||||
1. Application calls `send(sock, data, len, 0)`
|
||||
2. TCP/IP stack builds TCP segment + IP header
|
||||
3. ARP resolves destination MAC address (or uses cached entry)
|
||||
4. Stack fills a `struct IOSana2Req` with the complete Ethernet frame
|
||||
5. `DoIO` / `SendIO` to the SANA-II driver
|
||||
6. Driver writes frame to hardware registers / DMA buffer
|
||||
|
||||
### How the Stack Receives a Packet
|
||||
|
||||
1. Stack posts **read requests** (`CMD_READ`) to the SANA-II driver (kept outstanding)
|
||||
2. When a frame arrives, the driver completes the IORequest
|
||||
3. Stack parses the Ethernet frame → IP → TCP/UDP → delivers to socket buffer
|
||||
4. Application's `recv()` or `WaitSelect()` returns the data
|
||||
5. Stack immediately posts a new `CMD_READ` to keep the pipeline full
|
||||
|
||||
---
|
||||
|
||||
## Dial-Up Networking — PPP and SLIP
|
||||
|
||||
Before broadband, most Amiga internet connections were via **serial modem** using PPP or SLIP over `serial.device`:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Amiga"
|
||||
BSL2["bsdsocket.library"] --> PPP2["PPP/SLIP Driver"]
|
||||
PPP2 --> SER["serial.device<br/>(CIA UART)"]
|
||||
end
|
||||
|
||||
SER -->|"RS-232<br/>up to 115200 baud"| MODEM["External Modem<br/>(AT commands)"]
|
||||
MODEM -->|"Phone line<br/>V.34/V.90"| ISP["ISP<br/>PPP Server"]
|
||||
|
||||
style PPP2 fill:#fff9c4,stroke:#f9a825,color:#333
|
||||
style MODEM fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
```
|
||||
|
||||
### PPP Connection Sequence
|
||||
|
||||
```
|
||||
1. Modem initialisation:
|
||||
→ ATZ (reset modem)
|
||||
← OK
|
||||
→ AT&F1 (factory defaults, hardware flow control)
|
||||
← OK
|
||||
|
||||
2. Dial ISP:
|
||||
→ ATDT 555-1234 (tone dial)
|
||||
← CONNECT 33600 (connected at 33.6 kbps)
|
||||
|
||||
3. PPP negotiation (automatic):
|
||||
← LCP Configure-Request
|
||||
→ LCP Configure-Ack
|
||||
→ LCP Configure-Request
|
||||
← LCP Configure-Ack
|
||||
→ PAP Authenticate (username/password)
|
||||
← PAP Authenticate-Ack
|
||||
→ IPCP Configure-Request
|
||||
← IPCP Configure-Ack (IP=203.0.113.42, DNS=203.0.113.1)
|
||||
|
||||
4. Link up — IP traffic flows over PPP frames on serial line
|
||||
```
|
||||
|
||||
### PPP Configuration (Miami)
|
||||
|
||||
Miami had the most user-friendly dial-up setup:
|
||||
|
||||
| Setting | Value | Notes |
|
||||
|---|---|---|
|
||||
| Serial device | `serial.device` | Or `duart.device` for A2232 multi-port |
|
||||
| Unit | 0 | |
|
||||
| Baud rate | 57600 or 115200 | Must match modem's DTE rate |
|
||||
| Flow control | RTS/CTS (hardware) | **Required** — XON/XOFF causes corruption |
|
||||
| Init string | `ATZ` then `AT&F1` | Hardware flow control defaults |
|
||||
| Dial command | `ATDT <number>` | |
|
||||
| Login | PAP or script-based | |
|
||||
| VJ compression | Yes | Van Jacobson TCP header compression |
|
||||
|
||||
### PPP Configuration (AmiTCP)
|
||||
|
||||
```
|
||||
; AmiTCP:db/interfaces
|
||||
prism2 DEV=DEVS:Networks/prism2.device UNIT=0 IP=DHCP
|
||||
ppp0 DEV=DEVS:Networks/ppp.device UNIT=0
|
||||
|
||||
; AmiTCP:bin/startnet script must:
|
||||
; 1. Open serial port
|
||||
; 2. Send modem commands
|
||||
; 3. Wait for CONNECT
|
||||
; 4. Hand off to PPP
|
||||
```
|
||||
|
||||
### SLIP (Serial Line IP)
|
||||
|
||||
SLIP is the simpler, older protocol — no authentication, no compression, no error detection:
|
||||
|
||||
```
|
||||
; AmiTCP:db/interfaces
|
||||
sl0 DEV=DEVS:Networks/slip.device UNIT=0
|
||||
|
||||
; SLIP frames:
|
||||
; Each IP packet is framed with END bytes (0xC0)
|
||||
; ESC (0xDB) used to escape END and ESC within data
|
||||
```
|
||||
|
||||
| Feature | SLIP | PPP |
|
||||
|---|---|---|
|
||||
| Authentication | None | PAP, CHAP |
|
||||
| IP negotiation | Manual (both sides must know IPs) | Automatic (IPCP) |
|
||||
| Compression | None | VJ header compression |
|
||||
| Error detection | None | FCS (frame check) |
|
||||
| Multi-protocol | IP only | IP, IPX, AppleTalk |
|
||||
|
||||
---
|
||||
|
||||
## Stack Comparison (Detailed)
|
||||
|
||||
| Feature | AmiTCP 3.0b2 / Genesis | Miami 3.2 | Roadshow 1.15 |
|
||||
|---|---|---|---|
|
||||
| **License** | Free (Genesis fork) | Commercial ($30) | Commercial (demo: 5-min limit) |
|
||||
| **API version** | bsdsocket.library v3 | v4 | v4 |
|
||||
| **Based on** | NetBSD TCP/IP stack | Custom implementation | Custom implementation |
|
||||
| **IPv6** | No | No | No |
|
||||
| **PPP** | External (AmiPPP) | Built-in dialer + PPP | Via SANA-II PPP driver |
|
||||
| **SLIP** | Yes | Yes | Via SANA-II SLIP driver |
|
||||
| **DHCP** | External (dhclient) | Built-in | Built-in |
|
||||
| **DNS cache** | No | Yes | Yes |
|
||||
| **DNS over TCP** | No | Yes | Yes |
|
||||
| **Multi-homing** | Limited | Yes | Yes (multiple interfaces) |
|
||||
| **SANA-II v2** | Yes | Yes | Yes |
|
||||
| **GUI config** | Genesis MUI prefs | Miami prefs GUI | Roadshow prefs editor |
|
||||
| **CLI config** | `ifconfig`, `route` | Via GUI only | Text file + CLI tools |
|
||||
| **Syslog** | Yes | Yes | Yes |
|
||||
| **Active development** | No (last: ~2002) | No (last: ~2003) | Yes (Olaf Barthel, ongoing) |
|
||||
| **Stability** | Good | Good | Excellent |
|
||||
| **MiSTer notes** | ✅ Free, easy to set up | ❌ Requires registration | ✅ Most capable, demo limit |
|
||||
|
||||
### Which Stack for MiSTer?
|
||||
|
||||
- **AmiTCP / Genesis**: Free, works well for basic networking. Use `Genesis.lha` from Aminet.
|
||||
- **Roadshow**: Most capable and actively maintained. Demo works for 5 minutes at a time — sufficient for testing. Full license recommended for permanent setups.
|
||||
|
||||
---
|
||||
|
||||
## Ethernet Cards (SANA-II Hardware)
|
||||
|
||||
| Card | Device | Bus | Chipset | Speed |
|
||||
|---|---|---|---|---|
|
||||
| Commodore A2065 | `a2065.device` | Zorro II | AMD LANCE | 10 Mbps |
|
||||
| Village Tronic Ariadne | `ariadne.device` | Zorro II | AMD PCnet | 10 Mbps |
|
||||
| Village Tronic Ariadne II | `ariadne2.device` | Zorro II | SMC 91C94 | 10 Mbps |
|
||||
| Hydra Systems Amiganet | `amiganet.device` | Zorro II | AMD LANCE | 10 Mbps |
|
||||
| X-Surf | `xsurf.device` | Clock port | RTL8019AS | 10 Mbps |
|
||||
| X-Surf 100 | `xsurf100.device` | Zorro II/III | AX88796B | 100 Mbps |
|
||||
| PCMCIA Ethernet | `cnet.device` | PCMCIA (A600/A1200) | Various | 10 Mbps |
|
||||
|
||||
### MiSTer Virtual NIC
|
||||
|
||||
MiSTer emulates a network interface that bridges to the Linux HPS network:
|
||||
|
||||
```
|
||||
; DEVS:NetInterfaces/mister0 (Roadshow)
|
||||
DEVICE=mister_eth.device
|
||||
UNIT=0
|
||||
IPTYPE=DHCP
|
||||
MTU=1500
|
||||
```
|
||||
|
||||
The virtual driver presents a standard SANA-II interface — the TCP/IP stack sees no difference from a real Ethernet card.
|
||||
|
||||
---
|
||||
|
||||
## Configuration — Roadshow
|
||||
|
||||
### Network Interface
|
||||
|
||||
```
|
||||
; DEVS:NetInterfaces/eth0
|
||||
DEVICE=ariadne.device ; or your card's device name
|
||||
UNIT=0
|
||||
IPTYPE=DHCP ; or STATIC for manual
|
||||
|
||||
; Static configuration:
|
||||
; ADDRESS=192.168.1.100
|
||||
; NETMASK=255.255.255.0
|
||||
```
|
||||
|
||||
### Name Resolution
|
||||
|
||||
```
|
||||
; DEVS:Internet/name_resolution
|
||||
NAMESERVER 8.8.8.8
|
||||
NAMESERVER 8.8.4.4
|
||||
DOMAIN local
|
||||
SEARCH local
|
||||
```
|
||||
|
||||
### Default Gateway
|
||||
|
||||
```
|
||||
; DEVS:Internet/default_gateway
|
||||
DEVICE=ariadne.device
|
||||
UNIT=0
|
||||
GATEWAY=192.168.1.1
|
||||
```
|
||||
|
||||
### Startup
|
||||
|
||||
```
|
||||
; S:User-Startup
|
||||
Run >NIL: C:AddNetInterface eth0
|
||||
WaitForPort AMITCP
|
||||
; Stack is now ready
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration — AmiTCP / Genesis
|
||||
|
||||
### Interface Configuration
|
||||
|
||||
```
|
||||
; AmiTCP:db/interfaces
|
||||
eth0 DEV=DEVS:Networks/ariadne.device UNIT=0 IP=DHCP
|
||||
|
||||
; Static:
|
||||
; eth0 DEV=DEVS:Networks/ariadne.device UNIT=0 IP=192.168.1.100 NETMASK=255.255.255.0
|
||||
```
|
||||
|
||||
### DNS and Hosts
|
||||
|
||||
```
|
||||
; AmiTCP:db/netdb-myhost
|
||||
HOST 127.0.0.1 localhost
|
||||
HOST 192.168.1.1 gateway
|
||||
NAMESERVER 8.8.8.8
|
||||
DOMAIN local
|
||||
```
|
||||
|
||||
### Startup
|
||||
|
||||
```
|
||||
; S:User-Startup
|
||||
Run >NIL: AmiTCP:AmiTCP
|
||||
WaitForPort AMITCP
|
||||
; bsdsocket.library is now available
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verifying the Network
|
||||
|
||||
```
|
||||
; CLI commands (provided by the stack):
|
||||
ifconfig ; show interface configuration
|
||||
netstat -r ; show routing table
|
||||
netstat -a ; show active connections
|
||||
ping 8.8.8.8 ; test connectivity
|
||||
nslookup amiga.org ; test DNS resolution
|
||||
traceroute 8.8.8.8 ; trace route to destination
|
||||
```
|
||||
|
||||
```c
|
||||
/* Programmatic check: */
|
||||
struct Library *SocketBase = OpenLibrary("bsdsocket.library", 4);
|
||||
if (!SocketBase)
|
||||
Printf("No TCP/IP stack running — start AmiTCP or Roadshow\n");
|
||||
else
|
||||
CloseLibrary(SocketBase);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Roadshow SDK: http://roadshow.apc-tcp.de/
|
||||
- AmiTCP SDK: Aminet `comm/tcp/AmiTCP-SDK-4.3.lha`
|
||||
- Genesis (free AmiTCP fork): Aminet `comm/tcp/Genesis.lha`
|
||||
- SANA-II specification: Aminet `docs/hard/sana2.lha`
|
||||
- See also: [bsdsocket.md](bsdsocket.md) — socket API reference
|
||||
- See also: [sana2.md](sana2.md) — SANA-II driver specification
|
||||
- See also: [protocols.md](protocols.md) — DNS, HTTP, UDP implementation examples
|
||||
|
|
|
|||
|
|
@ -2,15 +2,18 @@
|
|||
|
||||
# Toolchain — Overview
|
||||
|
||||
Development tools for building Amiga software, from native compilers to modern cross-compilation environments.
|
||||
|
||||
## Section Index
|
||||
|
||||
| File | Description |
|
||||
|---|---|
|
||||
| [gcc_amiga.md](gcc_amiga.md) | m68k-amigaos-gcc cross-compiler: bebbo's toolchain, Docker setup, CPU targets, libnix/ixemul startup |
|
||||
| [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 |
|
||||
| [gcc_amiga.md](gcc_amiga.md) | m68k-amigaos-gcc cross-compiler |
|
||||
| [sasc.md](sasc.md) | SAS/C 6.x compiler |
|
||||
| [fd_files.md](fd_files.md) | FD/SFD file format and LVO generation |
|
||||
| [pragmas.md](pragmas.md) | Compiler pragmas and inline stubs |
|
||||
| [ndk.md](ndk.md) | NDK versions, contents, and where to get them |
|
||||
| [makefiles.md](makefiles.md) | Makefile patterns for Amiga cross-compilation |
|
||||
| [debugging.md](debugging.md) | Debugging tools: BareFoot, wack, gdb remote |
|
||||
| [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 |
|
||||
| [makefiles.md](makefiles.md) | Makefile patterns for GCC cross-compilation, vasm/vlink assembly, mixed C+asm projects |
|
||||
| [debugging.md](debugging.md) | Debugging tools: Enforcer/MuForce memory watchdog, SnoopDOS tracing, FS-UAE GDB remote, kprintf, debugging checklist |
|
||||
|
|
|
|||
|
|
@ -1,62 +1,191 @@
|
|||
[← Home](../README.md) · [Toolchain](README.md)
|
||||
|
||||
# Debugging Tools — BareFoot, wack, GDB Remote
|
||||
# Debugging Tools — Enforcer, BareFoot, GDB Remote
|
||||
|
||||
## Overview
|
||||
|
||||
Debugging Amiga programs ranges from ROM-resident kernel debuggers (wack/SAD) through software monitors to modern cross-debugging via GDB stubs.
|
||||
Debugging Amiga programs spans from hardware-level ROM debuggers through MMU-based memory watchers to modern cross-debugging via GDB stubs. The unique Amiga challenges — no memory protection, shared address space, custom chip state — require specialised tools.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Development Host (Linux/macOS)"
|
||||
GDB["m68k-elf-gdb<br/>(source-level)"]
|
||||
end
|
||||
|
||||
subgraph "Amiga / Emulator"
|
||||
ENF["Enforcer/MuForce<br/>(MMU memory watch)"]
|
||||
MON["MonAm/Barfly<br/>(software debugger)"]
|
||||
SERD["BareFoot/SAD<br/>(serial ROM debugger)"]
|
||||
APP["Application<br/>under test"]
|
||||
end
|
||||
|
||||
GDB -->|"TCP/Serial<br/>GDB Remote Protocol"| APP
|
||||
ENF -->|"Traps illegal<br/>memory access"| APP
|
||||
MON -->|"Breakpoints,<br/>disassembly"| APP
|
||||
|
||||
style ENF fill:#ffcdd2,stroke:#c62828,color:#333
|
||||
style GDB fill:#c8e6c9,stroke:#2e7d32,color:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tool Comparison
|
||||
|
||||
| Tool | Type | Target | Best for |
|
||||
| Tool | Type | Requires | Best For |
|
||||
|---|---|---|---|
|
||||
| BareFoot | Serial debugger | Real hardware / UAE | OS-level, Exec kernel |
|
||||
| wack/SAD | ROM debugger | Real hardware | Boot-time, crash analysis |
|
||||
| MonAm | Software monitor | AmigaOS | Breakpoints, disassembly |
|
||||
| IDA Pro | Static + remote | Cross-platform | Static RE, decompilation |
|
||||
| GDB (m68k-elf-gdb) | Cross-debugger | FS-UAE / Basilisk | Source-level C debugging |
|
||||
| Enforcer | Memory watchdog | AmigaOS (68020+) | Illegal memory access detection |
|
||||
| MuForce | Memory watchdog | AmigaOS (68040+) | Same as Enforcer for 040/060 |
|
||||
| **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 |
|
||||
| **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 |
|
||||
| **MonAm** | Software monitor/debugger | AmigaOS | Interactive breakpoints, register inspection |
|
||||
| **SnoopDOS** | DOS call tracer | AmigaOS 2.0+ | Tracing file/library opens, locks |
|
||||
| **CPR** | Debug output viewer | AmigaOS | Capturing `kprintf` and `RawPutChar` output |
|
||||
| **m68k-elf-gdb** | Cross-debugger | FS-UAE or vamos | Source-level C debugging from host |
|
||||
| **IDA Pro** | Static + remote | Cross-platform | Static RE, decompilation |
|
||||
|
||||
---
|
||||
|
||||
## Enforcer / MuForce
|
||||
## Enforcer / MuForce — Memory Access Watchdog
|
||||
|
||||
Detects invalid memory accesses using MMU:
|
||||
The most essential Amiga debugging tool. Uses the MMU to trap **all invalid memory accesses** — reading from address 0 (NULL pointer), writing to ROM, accessing non-existent memory, etc.
|
||||
|
||||
```
|
||||
Enforcer Hit! $0000000C read by task "myapp" at $00020456
|
||||
```
|
||||
|
||||
```
|
||||
; Install:
|
||||
; Start Enforcer (runs in background):
|
||||
run >NIL: Enforcer
|
||||
; or for 040/060:
|
||||
|
||||
; For 040/060 systems:
|
||||
run >NIL: MuForce
|
||||
|
||||
; Output goes to the serial port by default.
|
||||
; Redirect to a file or console:
|
||||
run >NIL: Enforcer STDOUT
|
||||
run >NIL: Enforcer FILE RAM:enforcer.log
|
||||
```
|
||||
|
||||
### Reading Enforcer Hits
|
||||
|
||||
```
|
||||
WORD-READ from $00000004 by "myapp" at $00020456
|
||||
Data: $00000000 USP: $0007FFF0 PC: $00020456
|
||||
|
||||
--- Loss of Register Dump ---
|
||||
D0 00000000 D1 0003A2F0 D2 00000010 D3 00000001
|
||||
D4 00000000 D5 FFFFFFFF D6 00000000 D7 00000000
|
||||
A0 00000000 A1 0003A2F0 A2 00070000 A3 00040000
|
||||
A4 00020000 A5 00000000 A6 0003F000 A7 0007FFF0
|
||||
```
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| `WORD-READ from $00000004` | Tried to read a WORD from address 4 (low memory = NULL struct deref) |
|
||||
| `by "myapp"` | Task name that caused the hit |
|
||||
| `at $00020456` | PC (program counter) where the access occurred |
|
||||
|
||||
> [!TIP]
|
||||
> **Address $00000004 = reading `ExecBase` through NULL pointer.** This is the #1 Enforcer hit — it means code is dereferencing a NULL pointer to a structure and accessing field at offset 4. Find the instruction at the PC address in your disassembly.
|
||||
|
||||
### Common Enforcer Hit Patterns
|
||||
|
||||
| Address Range | Likely Cause |
|
||||
|---|---|
|
||||
| `$00000000–$000003FF` | NULL pointer dereference (struct field at offset N) |
|
||||
| `$00F00000–$00FFFFFF` | Reading from ROM area (write-to-ROM bug) |
|
||||
| `$00C00000–$00DFFFFF` | Accessing custom chip area with bad address |
|
||||
| `$01000000+` | Accessing memory beyond physical RAM |
|
||||
|
||||
---
|
||||
|
||||
## SnoopDOS — System Call Tracer
|
||||
|
||||
Monitors all DOS and library calls system-wide — essential for understanding why programs fail to find files or libraries:
|
||||
|
||||
```
|
||||
; Start SnoopDOS (MUI GUI):
|
||||
SnoopDos
|
||||
|
||||
; Typical output:
|
||||
Open "myapp" LIBS:mytool.library OK (Lock=$0003A000)
|
||||
Open "myapp" DEVS:Keymaps/usa FAIL (Object not found)
|
||||
Lock "myapp" SYS:Prefs/Env-Archive OK (Lock=$00042000)
|
||||
OpenLibrary "myapp" intuition.library v39 OK (Base=$0003F000)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FS-UAE GDB Debugging
|
||||
## FS-UAE GDB Remote Debugging
|
||||
|
||||
Modern source-level debugging from your development host:
|
||||
|
||||
```bash
|
||||
# In fs-uae.conf:
|
||||
remote_debugger = 1
|
||||
remote_debugger_port = 6860
|
||||
|
||||
# Connect from host:
|
||||
# Start FS-UAE, then from your development machine:
|
||||
m68k-elf-gdb myapp
|
||||
|
||||
(gdb) target remote localhost:6860
|
||||
(gdb) symbol-file myapp # load debug symbols
|
||||
(gdb) break main
|
||||
(gdb) continue
|
||||
(gdb) print myVariable
|
||||
(gdb) info registers
|
||||
(gdb) x/10i $pc # disassemble at PC
|
||||
(gdb) backtrace # show call stack
|
||||
```
|
||||
|
||||
### Building with Debug Info
|
||||
|
||||
```bash
|
||||
# GCC — include DWARF debug info in hunk format:
|
||||
m68k-amigaos-gcc -g -noixemul -o myapp main.c
|
||||
|
||||
# SAS/C — include SAS debug info:
|
||||
lc -d2 main.c
|
||||
blink main.o TO myapp LIB lib:sc.lib lib:amiga.lib DEBUG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## kprintf / RawPutChar — Debug Output
|
||||
|
||||
For printf-style debugging without a console:
|
||||
|
||||
```c
|
||||
/* kprintf writes directly to the serial port,
|
||||
bypassing all OS output — works even during interrupts: */
|
||||
#include <clib/debug_protos.h>
|
||||
|
||||
kprintf("Value: %ld at %lx\n", value, address);
|
||||
|
||||
/* RawPutChar — single character to serial: */
|
||||
RawPutChar('X');
|
||||
```
|
||||
|
||||
Capture with a terminal emulator on the serial port, or use **CPR** (Capture Raw Putchar) to redirect to a window or file.
|
||||
|
||||
---
|
||||
|
||||
## Debugging Checklist
|
||||
|
||||
| Symptom | Tool | Action |
|
||||
|---|---|---|
|
||||
| Guru Meditation | wack/SAD | Note crash address, check code at that PC |
|
||||
| Random crashes | Enforcer | Run Enforcer, look for illegal accesses before the crash |
|
||||
| "Can't open library" | SnoopDOS | See what path the open is trying |
|
||||
| Memory leak | MungWall | Tracks AllocMem/FreeMem, reports leaks at exit |
|
||||
| Stack overflow | MuGuardianAngel | Detects stack underflow on 040/060 |
|
||||
| Performance issue | sprof (SAS/C) | Profile C functions |
|
||||
| File not found | SnoopDOS | See where the OS is looking for the file |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- BareFoot: Aminet `dev/debug/BareFoot.lha`
|
||||
- Enforcer: Aminet `dev/debug/Enforcer.lha`
|
||||
- MuForce: Aminet `dev/debug/MuForce.lha`
|
||||
- SnoopDOS: Aminet `util/monitor/SnoopDos.lha`
|
||||
- BareFoot: Aminet `dev/debug/BareFoot.lha`
|
||||
- MungWall: Aminet `dev/debug/MungWall.lha`
|
||||
- CPR: Aminet `dev/debug/cpr.lha`
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
## Overview
|
||||
|
||||
SAS/C (originally Lattice C) was the dominant commercial C compiler for AmigaOS. Version 6.58 is the final release. It produces optimised 68k code with full AmigaOS integration, including pragma-based system calls and SAS-specific debug formats.
|
||||
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.
|
||||
|
||||
Most existing Amiga source code and documentation assumes SAS/C conventions. Understanding its idioms is essential for working with legacy codebases.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -12,52 +14,143 @@ SAS/C (originally Lattice C) was the dominant commercial C compiler for AmigaOS.
|
|||
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| Register args | Automatic register parameter passing |
|
||||
| Pragmas | `#pragma libcall` for direct library LVO calls |
|
||||
| Small data | A4-relative addressing for globals |
|
||||
| Profiling | Built-in `sprof` profiler |
|
||||
| Debug format | SAS stabs (`=APS` tag in HUNK_DEBUG) |
|
||||
| Optimizer | Global optimizer (`lc -O`) with peephole |
|
||||
| **Register args** | Automatic register parameter passing for small functions |
|
||||
| **Pragmas** | `#pragma libcall` for direct library LVO calls — no stub library needed |
|
||||
| **Small data model** | A4-relative addressing for global variables (faster, smaller code) |
|
||||
| **Large data model** | Absolute addressing for globals (no 64 KB limit) |
|
||||
| **Profiler** | Built-in `sprof` function-level profiler |
|
||||
| **Debug format** | SAS stabs (`=APS` tag in `HUNK_DEBUG`) |
|
||||
| **Global optimizer** | `lc -O` with peephole, dead code elimination, CSE |
|
||||
| **Code generation** | 68000 through 68060 + 68881/68882 FPU |
|
||||
| **Integrated linker** | `blink` — faster than generic linkers, understands Amiga hunks |
|
||||
|
||||
---
|
||||
|
||||
## Pragma Format
|
||||
|
||||
Pragmas tell the compiler how to call AmigaOS library functions directly via JSR through the library base, with arguments in specific registers:
|
||||
|
||||
```c
|
||||
/* dos_pragmas.h — generated from FD files: */
|
||||
#pragma libcall DOSBase Open 1e 2102
|
||||
/* ^^ ^^ ^^^^
|
||||
name LVO reg-encoding
|
||||
reg-encoding: 2=D2, 1=D1, 0=result in D0, 2 args */
|
||||
/* ^^ ^^^^
|
||||
LVO register encoding
|
||||
|
||||
LVO $1E = -30 (decimal) = offset in jump table
|
||||
Register encoding: read RIGHT to LEFT:
|
||||
2 = D2 (not used here)
|
||||
0 = D0 (return value)
|
||||
1 = D1 (first arg)
|
||||
2 = D2 (second arg)
|
||||
Last digit = number of arguments */
|
||||
|
||||
#pragma libcall DOSBase Close 24 101
|
||||
/* LVO -$24 (-36), 1 arg in D1, returns in D0 */
|
||||
|
||||
#pragma libcall DOSBase Read 2a 32103
|
||||
/* LVO -$2A (-42), 3 args: D1=fh, D2=buffer, D3=length */
|
||||
```
|
||||
|
||||
Register encoding: digits map to registers (1=D0, 2=D1, ... 9=A0, A=A1, etc.)
|
||||
### Register Encoding
|
||||
|
||||
| Digit | Register | Digit | Register |
|
||||
|---|---|---|---|
|
||||
| 0 | D0 | 8 | (unused) |
|
||||
| 1 | D1 | 9 | A0 |
|
||||
| 2 | D2 | A | A1 |
|
||||
| 3 | D3 | B | A2 |
|
||||
| 4 | D4 | C | A3 |
|
||||
| 5 | D5 | D | A4 |
|
||||
| 6 | D6 | E | A5 |
|
||||
| 7 | D7 | F | (unused) |
|
||||
|
||||
The rightmost digit is always the result register, next digit is number of args, then args are listed left-to-right.
|
||||
|
||||
---
|
||||
|
||||
## Compilation
|
||||
## Compilation Workflow
|
||||
|
||||
```
|
||||
lc -v -O -b0 -j73 hello.c ; compile
|
||||
blink hello.o TO hello LIB lib:sc.lib lib:amiga.lib ; link
|
||||
Source (.c) → lc (compile) → Object (.o) → blink (link) → Executable
|
||||
```
|
||||
|
||||
```bash
|
||||
# Compile a single file:
|
||||
lc -v -O -b0 main.c
|
||||
|
||||
# Link:
|
||||
blink main.o util.o TO myapp LIB lib:sc.lib lib:amiga.lib
|
||||
|
||||
# Compile + link in one step:
|
||||
lc -v -O -b0 -j73 main.c util.c LINK TO myapp
|
||||
```
|
||||
|
||||
### Compiler Flags
|
||||
|
||||
| Flag | Meaning |
|
||||
|---|---|
|
||||
| `-v` | Verbose |
|
||||
| `-O` | Optimise |
|
||||
| `-b0` | Small data model (A4-relative) |
|
||||
| `-b1` | Large data model |
|
||||
| `-j73` | Generate 68020/68881 code |
|
||||
| `-v` | Verbose output |
|
||||
| `-O` | Enable global optimiser |
|
||||
| `-b0` | Small data model (A4-relative, max 64 KB globals) |
|
||||
| `-b1` | Large data model (absolute addressing) |
|
||||
| `-j30` | Generate 68030 code |
|
||||
| `-j40` | Generate 68040 code |
|
||||
| `-j73` | Generate 68020 + 68881 FPU code |
|
||||
| `-d0` | No debug info |
|
||||
| `-d2` | Full debug info |
|
||||
| `-d2` | Full debug info (SAS stabs) |
|
||||
| `-r` | Generate resident/reentrant code |
|
||||
| `-L` | Disable auto-open of amiga.lib |
|
||||
| `-w` | Suppress warnings |
|
||||
| `-E` | Preprocess only |
|
||||
|
||||
### Linker Flags (blink)
|
||||
|
||||
| Flag | Meaning |
|
||||
|---|---|
|
||||
| `TO name` | Output file name |
|
||||
| `LIB libs` | Link libraries |
|
||||
| `DEBUG` | Include debug hunks |
|
||||
| `STRIPDEBUG` | Remove debug hunks |
|
||||
| `SMALLCODE` | PC-relative addressing |
|
||||
| `SMALLDATA` | A4-relative data |
|
||||
| `MAP file` | Generate link map |
|
||||
| `NODEBUG` | Omit all debug info |
|
||||
|
||||
---
|
||||
|
||||
## SAS/C-Specific Idioms
|
||||
|
||||
```c
|
||||
/* Register parameters: */
|
||||
ULONG __asm MyFunc(register __d0 ULONG arg1,
|
||||
register __a0 APTR arg2);
|
||||
|
||||
/* Interrupt-safe function: */
|
||||
void __interrupt __saveds MyInterrupt(void);
|
||||
|
||||
/* Resident code: */
|
||||
LONG __saveds __asm LibOpen(register __a6 struct Library *base);
|
||||
|
||||
/* __saveds: saves/restores A4 (small data base) on entry/exit */
|
||||
/* __interrupt: preserves all registers */
|
||||
/* __asm: use register calling convention */
|
||||
```
|
||||
|
||||
### Differences from GCC
|
||||
|
||||
| Feature | SAS/C | GCC (m68k-amigaos) |
|
||||
|---|---|---|
|
||||
| Register args | `register __d0 ULONG x` | `ULONG x __asm("d0")` |
|
||||
| Small data base | A4 (automatic with `-b0`) | `-fbaserel` (A4) |
|
||||
| Library pragmas | `#pragma libcall` | Inline asm stubs in `<inline/lib.h>` |
|
||||
| Startup | `cres.o` (resident) / `c.o` (standard) | `libnix` or `ixemul` |
|
||||
| String constants | Pooled by default | `-fmerge-constants` |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- SAS/C 6.x User Manual
|
||||
- SAS/C 6.x User Manual and Reference Manual
|
||||
- NDK39 pragma files: `NDK_3.9/Include/pragmas/`
|
||||
- See also: [pragmas.md](pragmas.md) — pragma/inline mechanism in depth
|
||||
- See also: [gcc_amiga.md](gcc_amiga.md) — GCC cross-compiler (modern alternative)
|
||||
|
|
|
|||
106
13_toolchain/stormc.md
Normal file
106
13_toolchain/stormc.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
[← Home](../README.md) · [Toolchain](README.md)
|
||||
|
||||
# StormC — Native IDE and Compiler Suite
|
||||
|
||||
## Overview
|
||||
|
||||
**StormC** was a native Amiga integrated development environment (IDE) developed by **Haage & Partner**. Unlike SAS/C (command-line focused) or GCC (cross-compilation), StormC provided a modern GUI-based IDE running directly on the Amiga with project management, integrated editor, debugger, and compiler — similar to what Borland C++ and Visual Studio offered on PC.
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Year | Key Features |
|
||||
|---|---|---|
|
||||
| StormC 1.0 | 1996 | Initial release, C compiler, basic IDE |
|
||||
| StormC 2.0 | 1997 | C++ support, improved optimiser |
|
||||
| StormC 3.0 | 1998 | Full C++ with exceptions, STL, PowerPC support |
|
||||
| StormC 4.0 | 1999 | Final version, OS 3.5 integration |
|
||||
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| **Native IDE** | GUI editor + project manager running on AmigaOS itself |
|
||||
| **C and C++** | Full C89/C90, C++ with exceptions and RTTI |
|
||||
| **PowerPC** | StormC 3+ could target PPC (for CyberStorm PPC, BlizzardPPC) |
|
||||
| **68k code gen** | 68000 through 68060 target support |
|
||||
| **Debugger** | Integrated source-level debugger with breakpoints and watch |
|
||||
| **Linker** | StormLink — Amiga hunk format output |
|
||||
| **Profiler** | Built-in function-level profiler |
|
||||
| **AmigaOS integration** | Full NDK headers, pragma support, Amiga library call stubs |
|
||||
| **MUI support** | Built-in MUI class creation wizards (later versions) |
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
StormC used `.storm` project files (proprietary format) containing:
|
||||
- Source file list and compilation order
|
||||
- Compiler flags per file or project-wide
|
||||
- Include paths and library search paths
|
||||
- Debug/Release build configurations
|
||||
- Target CPU selection
|
||||
|
||||
---
|
||||
|
||||
## Compilation
|
||||
|
||||
```
|
||||
; From the IDE:
|
||||
; Project → Build (or press Ctrl+B)
|
||||
|
||||
; Command-line compiler also available:
|
||||
stormc -O2 -m68020 -o myapp main.c util.c
|
||||
|
||||
; Typical flags:
|
||||
; -m68000 Target 68000
|
||||
; -m68020 Target 68020+
|
||||
; -m68040 Target 68040
|
||||
; -m68060 Target 68060
|
||||
; -O0 to -O3 Optimisation level
|
||||
; -g Debug info
|
||||
; -c Compile only (no link)
|
||||
; -I<path> Include path
|
||||
; -L<path> Library path
|
||||
; -l<lib> Link library
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## StormC vs. Other Amiga Compilers
|
||||
|
||||
| Feature | SAS/C 6.58 | GCC (bebbo) | StormC 4.0 |
|
||||
|---|---|---|---|
|
||||
| **C++ support** | No (C only) | Yes (GCC 6.5) | Yes (with exceptions) |
|
||||
| **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 |
|
||||
| **PowerPC** | No | No | Yes (v3+) |
|
||||
| **Availability** | Abandonware | Free / open source | Abandonware |
|
||||
| **Legacy code compat** | High (dominant compiler) | Moderate (GCC differences) | Moderate |
|
||||
| **Pragma support** | Native `#pragma libcall` | Inline asm stubs | Pragma compatible |
|
||||
|
||||
---
|
||||
|
||||
## Limitations and Legacy
|
||||
|
||||
- **Proprietary project format**: `.storm` files can't be converted to Makefiles easily
|
||||
- **No cross-compilation**: Must run on a real Amiga or emulator
|
||||
- **C++ ABI**: StormC's C++ name mangling and vtable layout differ from GCC — libraries compiled with StormC can't be linked with GCC C++ code
|
||||
- **Abandoned**: Haage & Partner ceased operations; no source release
|
||||
- **PowerPC path abandoned**: The WarpOS/PowerUP split made PPC support fragmented
|
||||
|
||||
Despite these issues, StormC was the most productive **native** Amiga development environment, and many late-era Amiga applications (1996–2000) were developed with it.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Haage & Partner: historical website (archived)
|
||||
- Aminet: `dev/c/StormC` — various StormC patches and updates
|
||||
- See also: [sasc.md](sasc.md) — SAS/C (dominant legacy compiler)
|
||||
- See also: [gcc_amiga.md](gcc_amiga.md) — GCC cross-compiler (modern standard)
|
||||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
# References — Quick Lookup Tables
|
||||
|
||||
Condensed reference tables for frequently needed constants, register addresses, LVO offsets, and error codes.
|
||||
|
||||
## Section Index
|
||||
|
||||
| File | Description |
|
||||
|---|---|
|
||||
| [custom_chip_registers.md](custom_chip_registers.md) | Complete custom chip register map |
|
||||
| [custom_chip_registers.md](custom_chip_registers.md) | Complete custom chip register map ($DFF000–$DFF1FE) |
|
||||
| [exec_lvo_table.md](exec_lvo_table.md) | exec.library LVO offset table |
|
||||
| [dos_lvo_table.md](dos_lvo_table.md) | dos.library LVO offset table |
|
||||
| [error_codes.md](error_codes.md) | Complete DOS error code reference |
|
||||
| [error_codes.md](error_codes.md) | DOS error codes, Exec device errors, trackdisk errors, Intuition errors |
|
||||
|
|
|
|||
|
|
@ -1,9 +1,93 @@
|
|||
[← Home](../README.md) · [References](README.md)
|
||||
|
||||
# DOS Error Codes — Complete Reference
|
||||
# Error Codes — Complete Reference
|
||||
|
||||
Duplicate of [error_handling.md](../07_dos/error_handling.md) error table — see that file for the full list with descriptions.
|
||||
## DOS Error Codes
|
||||
|
||||
This file is kept as a quick cross-reference entry point.
|
||||
Full reference in [error_handling.md](../07_dos/error_handling.md). Summary of the most commonly encountered:
|
||||
|
||||
→ [Full error code table](../07_dos/error_handling.md)
|
||||
| Code | Constant | Description |
|
||||
|---|---|---|
|
||||
| 103 | `ERROR_INSUFFICIENT_FREE_STORE` | Out of memory |
|
||||
| 114 | `ERROR_BAD_TEMPLATE` | Command-line template parse error |
|
||||
| 121 | `ERROR_FILE_NOT_OBJECT` | Not a valid executable |
|
||||
| 202 | `ERROR_OBJECT_IN_USE` | File/dir locked by another process |
|
||||
| 203 | `ERROR_OBJECT_EXISTS` | File already exists (can't create) |
|
||||
| 204 | `ERROR_DIR_NOT_FOUND` | Directory path not found |
|
||||
| 205 | `ERROR_OBJECT_NOT_FOUND` | File not found |
|
||||
| 209 | `ERROR_ACTION_NOT_KNOWN` | Packet type not supported by handler |
|
||||
| 210 | `ERROR_INVALID_COMPONENT_NAME` | Illegal character in filename |
|
||||
| 212 | `ERROR_OBJECT_WRONG_TYPE` | e.g., tried to Enter a file |
|
||||
| 213 | `ERROR_DISK_NOT_VALIDATED` | Disk not yet validated after insert |
|
||||
| 214 | `ERROR_DISK_WRITE_PROTECTED` | Write-protected media |
|
||||
| 216 | `ERROR_DIRECTORY_NOT_EMPTY` | Can't delete non-empty directory |
|
||||
| 218 | `ERROR_DEVICE_NOT_MOUNTED` | Device/volume not mounted |
|
||||
| 221 | `ERROR_DISK_FULL` | No free space |
|
||||
| 222 | `ERROR_DELETE_PROTECTED` | File has delete protection |
|
||||
| 223 | `ERROR_WRITE_PROTECTED` | File has write protection |
|
||||
| 224 | `ERROR_READ_PROTECTED` | File has read protection |
|
||||
| 225 | `ERROR_NOT_A_DOS_DISK` | Unrecognised disk format |
|
||||
| 226 | `ERROR_NO_DISK` | No disk in drive |
|
||||
| 232 | `ERROR_NO_MORE_ENTRIES` | End of directory scan (ExNext) |
|
||||
| 233 | `ERROR_IS_SOFT_LINK` | Object is a soft link |
|
||||
| 303 | `ERROR_BUFFER_OVERFLOW` | Buffer too small |
|
||||
|
||||
---
|
||||
|
||||
## Exec Device Errors
|
||||
|
||||
Returned in `io_Error` field of IORequest:
|
||||
|
||||
| Code | Constant | Description |
|
||||
|---|---|---|
|
||||
| 0 | — | Success |
|
||||
| -1 | `IOERR_OPENFAIL` | OpenDevice failed |
|
||||
| -2 | `IOERR_ABORTED` | I/O request was AbortIO'd |
|
||||
| -3 | `IOERR_NOCMD` | Command not supported by this device |
|
||||
| -4 | `IOERR_BADLENGTH` | Invalid length parameter |
|
||||
| -5 | `IOERR_BADADDRESS` | Invalid address parameter |
|
||||
| -6 | `IOERR_UNITBUSY` | Unit is busy (exclusive access) |
|
||||
| -7 | `IOERR_SELFTEST` | Hardware self-test failure |
|
||||
|
||||
---
|
||||
|
||||
## Trackdisk / SCSI Errors
|
||||
|
||||
| Code | Constant | Description |
|
||||
|---|---|---|
|
||||
| 20 | `TDERR_NotSpecified` | General hardware error |
|
||||
| 21 | `TDERR_NoSecHdr` | Sector header not found |
|
||||
| 22 | `TDERR_BadSecPreamble` | Bad sector preamble |
|
||||
| 23 | `TDERR_BadSecID` | Bad sector ID |
|
||||
| 24 | `TDERR_BadHdrSum` | Header checksum error |
|
||||
| 25 | `TDERR_BadSecSum` | Sector checksum error |
|
||||
| 26 | `TDERR_TooFewSecs` | Too few sectors found |
|
||||
| 27 | `TDERR_BadSecHdr` | Bad sector header |
|
||||
| 28 | `TDERR_WriteProt` | Disk is write protected |
|
||||
| 29 | `TDERR_DiskChanged` | Disk was changed |
|
||||
| 30 | `TDERR_SeekError` | Seek error |
|
||||
| 31 | `TDERR_NoMem` | Not enough memory |
|
||||
| 32 | `TDERR_BadUnitNum` | Invalid unit number |
|
||||
| 33 | `TDERR_BadDriveType` | Bad drive type |
|
||||
| 34 | `TDERR_DriveInUse` | Drive in use |
|
||||
| 35 | `TDERR_PostReset` | Post-reset error |
|
||||
|
||||
---
|
||||
|
||||
## Intuition/Workbench Error Codes
|
||||
|
||||
| Value | Meaning |
|
||||
|---|---|
|
||||
| 0 | Success |
|
||||
| 1 | Not enough memory |
|
||||
| 2 | No chip memory |
|
||||
| 3 | No free store |
|
||||
| 4 | Screen too big |
|
||||
| 5 | No screen |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `dos/dos.h`, `exec/errors.h`, `devices/trackdisk.h`
|
||||
- See: [error_handling.md](../07_dos/error_handling.md) — DOS error handling patterns
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
# Driver Development — Overview
|
||||
|
||||
Technical reference for developing hardware drivers on AmigaOS. Covers the exec.device framework, RTG graphics card drivers (Picasso96/CyberGraphX), SANA-II network drivers, and AHI audio drivers.
|
||||
|
||||
## Section Index
|
||||
|
||||
| File | Description |
|
||||
|---|---|
|
||||
| [sana2_driver.md](sana2_driver.md) | Writing a SANA-II network device driver |
|
||||
| [rtg_driver.md](rtg_driver.md) | Writing Picasso96/RTG display card drivers |
|
||||
| [ahi_driver.md](ahi_driver.md) | Writing AHI audio drivers |
|
||||
| [device_driver_basics.md](device_driver_basics.md) | exec.device framework — how Amiga devices work |
|
||||
| [device_driver_basics.md](device_driver_basics.md) | exec.device framework — IORequest lifecycle, BeginIO/AbortIO, unit management |
|
||||
| [rtg_driver.md](rtg_driver.md) | Picasso96/RTG driver architecture — SetFunction patching, BoardInfo hooks, VRAM/CRTC management, signal routing, Native driver, compatibility issues, system tuning |
|
||||
| [sana2_driver.md](sana2_driver.md) | SANA-II network device driver specification |
|
||||
| [ahi_driver.md](ahi_driver.md) | AHI retargetable audio driver interface |
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -245,11 +245,12 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post
|
|||
| [vasm_vlink.md](13_toolchain/vasm_vlink.md) | vasm assembler, vlink linker |
|
||||
| [gcc_amiga.md](13_toolchain/gcc_amiga.md) | m68k-amigaos-gcc (bebbo fork, Codeberg) |
|
||||
| [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 |
|
||||
| [pragmas.md](13_toolchain/pragmas.md) | Compiler pragmas, inline stubs |
|
||||
| [ndk.md](13_toolchain/ndk.md) | NDK versions, contents, downloads |
|
||||
| [makefiles.md](13_toolchain/makefiles.md) | Cross-compilation Makefile patterns |
|
||||
| [debugging.md](13_toolchain/debugging.md) | Enforcer, GDB remote, BareFoot |
|
||||
| [debugging.md](13_toolchain/debugging.md) | Enforcer, SnoopDOS, GDB remote, kprintf |
|
||||
|
||||
### 14 — References
|
||||
| File | Topic |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue