|
| 1 | +# RGC-BASIC: Blitter, Surfaces & In-Memory Images — Design Specification |
| 2 | + |
| 3 | +**Status:** Draft for discussion and phased implementation |
| 4 | +**Audience:** Implementers (C/WASM/JS) and advanced BASIC authors (e.g. STOS/AMOS-style tools in BASIC) |
| 5 | +**Goal:** Add **rectangular copy** (blitter-style), **off-screen image buffers**, **region → sprite** capture, and **export** paths, with **feature parity** between **native** (`basic-gfx`) and **canvas WASM**, without breaking existing programs. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## 1. Vision |
| 10 | + |
| 11 | +### 1.1 What we are building toward |
| 12 | + |
| 13 | +- **AMOS/STOS-like workflow (subset):** Copy rectangles between buffers, scroll regions, build tile/sprite workflows from **BASIC** — enough to support **editors and utilities** written in RGC-BASIC (as the classic Amiga tools often were). |
| 14 | +- **One language, two hosts:** Same statements and functions behave the same in **terminal + gfx** where applicable, and in **WASM canvas** (with documented limits). |
| 15 | +- **Progressive disclosure:** Start with **bitmap-only** operations that map cleanly to existing `GfxVideoState` + software renderer; add **RGBA surfaces** and **export** when the foundation is solid. |
| 16 | + |
| 17 | +### 1.2 What we are not building (v1) |
| 18 | + |
| 19 | +- A full **Amiga blitter** (minterm, arbitrary masks, copper). |
| 20 | +- **Dual independent playfields** with hardware-style priorities (we already approximate with layers + z-order sprites). |
| 21 | +- **Binary compatibility** with AMOS/STOS file formats (`.abk`, etc.) — optional **later**; **PNG** remains the interchange format unless extended. |
| 22 | + |
| 23 | +--- |
| 24 | + |
| 25 | +## 2. Design principles |
| 26 | + |
| 27 | +1. **Explicit surfaces:** Every copy has a **named target** (default visible bitmap vs numbered off-screen image). No hidden global “blitter context” beyond current `SCREEN` mode. |
| 28 | +2. **Predictable coordinates:** All rects use **pixel coordinates** in the **same space** as `PSET`/`LINE` in bitmap mode (0..319, 0..199 for default 320×200 unless extended). |
| 29 | +3. **Clipping:** All operations **clip to destination bounds** (and optionally source); out-of-budget pixels are **no-ops**, not errors — unless `OPTION STRICT_CLIP` (optional future) is enabled. |
| 30 | +4. **Deterministic limits:** Max surface count, max dimensions, max export size — **documented** and **queryable** from BASIC (`IMAGESIZE?` / `SYSINFO` pattern — exact names TBD). |
| 31 | +5. **WASM parity:** Any feature that touches **memory** must have a **clear JS story** for **readback** (export) without re-entrancy bugs (follow `wasm_gfx_key_state_ptr` patterns: **export pointer + length**, or **copy-out** via async JS). |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +## 3. Current foundation (inventory) |
| 36 | + |
| 37 | +### 3.1 Already in RGC-BASIC |
| 38 | + |
| 39 | +| Area | Mechanism | |
| 40 | +|------|-----------| |
| 41 | +| Text + colour | `GfxVideoState.screen[]`, `color[]`, `SCROLL` offsets | |
| 42 | +| Hi-res bitmap | 320×200 1 bpp packed buffer (`gfx_bitmap_*`, `PSET`/`LINE`) | |
| 43 | +| PNG sprites | Slots, `LOADSPRITE`, `DRAWSPRITE`, tile sheets, `SPRITEMODULATE` | |
| 44 | +| Memory model | Virtual `POKE`/`PEEK` bases; 64K-style addressing | |
| 45 | + |
| 46 | +### 3.2 Gaps this spec fills |
| 47 | + |
| 48 | +- **No** general **rectangular copy** between two raster buffers. |
| 49 | +- **No** first-class **off-screen bitmap/RGBA** that BASIC names and addresses. |
| 50 | +- **No** **grab screen region → sprite slot** without an intermediate PNG file. |
| 51 | +- **No** **export** of a buffer to **PNG** or **raw bytes** for web download. |
| 52 | + |
| 53 | +--- |
| 54 | + |
| 55 | +## 4. Conceptual model |
| 56 | + |
| 57 | +### 4.1 Surface kinds (typed) |
| 58 | + |
| 59 | +| Kind | Bits | Typical use | |
| 60 | +|------|------|-------------| |
| 61 | +| `BITMAP1` | 1 bpp, packed | Current `SCREEN 1` layer; fast; good for classic-style tools | |
| 62 | +| `RGBA8` | 32 bpp premultiplied or straight-alpha | Scratch pads, “canvas” for tools, future true-color ops | |
| 63 | + |
| 64 | +**Rule:** Phase 1 implements **`BITMAP1` only** for **COPY** between **same or two buffers** of the same kind. **RGBA** surfaces are **Phase 2+** (see §7). |
| 65 | + |
| 66 | +### 4.2 Surface identifiers |
| 67 | + |
| 68 | +- **Index `0`:** Reserved for the **visible** hi-res bitmap attached to the current gfx session (when `SCREEN 1` is active). Operations when `SCREEN 0` may **error** or **no-op** with hint — **TBD** (prefer **runtime error** with hint: “BITMAP COPY requires SCREEN 1”). |
| 69 | +- **Index `1..N`:** **Off-screen** surfaces created with **`IMAGE CREATE`** (name TBD). |
| 70 | + |
| 71 | +**Alternative syntax (discussion):** Named images: `IMAGE "work"` vs numeric `IMAGE 1`. Numeric scales better for arrays in BASIC; names aid readability. Spec recommends **numeric handles** + optional **alias** via `IMAGE NAME` later. |
| 72 | + |
| 73 | +### 4.3 Coordinate system |
| 74 | + |
| 75 | +- Origin **top-left** `(0,0)`, `x` right, `y` down. |
| 76 | +- All rectangles: `x, y, width, height` **or** `x1,y1 TO x2,y2` — **one style** should win for consistency with `LINE` (which uses `x,y TO x,y`). **Recommendation:** mirror **`LINE`** / **`BAR`** style: |
| 77 | + `COPY sx,sy TO dx,dy, w, h` **or** |
| 78 | + `COPY sx1,sy1,sx2,sy2 TO dx,dy` (two corners) — pick one and document. |
| 79 | + |
| 80 | +--- |
| 81 | + |
| 82 | +## 5. Feature set by phase |
| 83 | + |
| 84 | +### Phase 1 — Bitmap rectangular copy (blitter-style, minimal) |
| 85 | + |
| 86 | +**Scope:** 1 bpp buffers only; **copy** from **source rect** to **dest** in **same** image or **another** bitmap surface. |
| 87 | + |
| 88 | +**Proposed statements (names illustrative):** |
| 89 | + |
| 90 | +``` |
| 91 | +REM Create off-screen bitmap WxH (1 bpp), returns handle in variable or uses next slot |
| 92 | +IMAGE CREATE img, 320, 200 |
| 93 | +
|
| 94 | +REM Copy rectangle from surface src (0=screen bitmap) to surface dst at (dx,dy) |
| 95 | +REM Clipped; overlapping copy may need temp row buffer (implementation detail) |
| 96 | +IMAGE COPY src, sx, sy, sw, sh TO dst, dx, dy |
| 97 | +
|
| 98 | +REM Fill rect with 0 or 1 (bitmap pen) |
| 99 | +IMAGE FILL dst, x, y, w, h [, mode] |
| 100 | +
|
| 101 | +REM Optional: scroll region by (dx,dy) with fill edge (classic scroll) |
| 102 | +IMAGE SCROLL dst, x, y, w, h, dx, dy [, fill] |
| 103 | +``` |
| 104 | + |
| 105 | +**Semantics:** |
| 106 | + |
| 107 | +- **`IMAGE COPY`:** Bit-level copy; **source and dest format must match** (Phase 1: both `BITMAP1`). |
| 108 | +- **Overlapping copy** on **same** buffer: must behave like **memmove**-safe (copy row-by-row in the correct direction) — specify in impl notes. |
| 109 | +- **Performance:** Native C memcpy of packed rows; WASM same, **no** per-pixel JS. |
| 110 | + |
| 111 | +**Errors:** |
| 112 | + |
| 113 | +- Invalid handle, negative size, `SCREEN 0` when `src` or `dst` is `0` — clear **`Hint:`** lines. |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +### Phase 2 — RGBA surfaces & blending (optional, larger) |
| 118 | + |
| 119 | +**Scope:** Allocate **`RGBA8`** surfaces (same dimensions limits as WASM heap allows). |
| 120 | + |
| 121 | +- `IMAGE CREATE RGBA img, w, h` → transparent black or undefined cleared. |
| 122 | +- Later: `IMAGE BLEND`, `IMAGE ALPHA` — **defer** until Phase 1 is stable. |
| 123 | + |
| 124 | +**Use case:** Paint programs, icon editors — **not required** for “AMOS Screen Copy demo” parity. |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +### Phase 3 — Region → sprite slot (“grab”) |
| 129 | + |
| 130 | +**Goal:** Avoid round-tripping through disk for tools. |
| 131 | + |
| 132 | +**Proposed:** |
| 133 | + |
| 134 | +``` |
| 135 | +REM Capture rectangle from bitmap surface src into sprite slot s as new texture |
| 136 | +REM Equivalent to: crop WxH from (sx,sy), upload as PNG-like RGBA internally |
| 137 | +IMAGE TOSPRITE src, sx, sy, sw, sh, slot [, tile_w, tile_h] |
| 138 | +``` |
| 139 | + |
| 140 | +**Semantics:** |
| 141 | + |
| 142 | +- **Source:** For Phase 1, **`BITMAP1` only** — conversion **1 bpp → RGBA** uses **foreground/background indices** from current `COLOR` / `BACKGROUND` or explicit **palette** parameters — **must be specified** (e.g. pen 1 = white, 0 = black for capture, or sample from PETSCII layer — **avoid** unless defined). |
| 143 | + |
| 144 | +**Simpler v1:** **`IMAGE TOSPRITE` only from `RGBA8` surface** (Phase 2), OR **`TOSPRITE` from bitmap** uses **fixed two-colour** conversion. Document chosen rule. |
| 145 | + |
| 146 | +**Interaction with `LOADSPRITE`:** Same slot rules — **replaces** texture for that slot; **`SPRITEMODULATE`** reset policy matches **`LOADSPRITE`**. |
| 147 | + |
| 148 | +--- |
| 149 | + |
| 150 | +### Phase 4 — Export (CLI + WASM) |
| 151 | + |
| 152 | +**Goal:** **Extract** image bytes for **download** or **processing**. |
| 153 | + |
| 154 | +**Options (pick 1–2 for v1):** |
| 155 | + |
| 156 | +| Method | CLI | WASM | |
| 157 | +|--------|-----|------| |
| 158 | +| **Raw 1bpp** | Write to `OPEN OUT` file | `HEAPU8` slice + JS download blob | |
| 159 | +| **PPM/PGM** | Easy text PGM export | Same, or base64 string | |
| 160 | +| **PNG** | **stb_image_write** (add dependency) or **miniz** | Same compiled to WASM | |
| 161 | + |
| 162 | +**Proposed API:** |
| 163 | + |
| 164 | +``` |
| 165 | +REM Write surface img raw 1bpp to file (path) |
| 166 | +IMAGE SAVE img, "out.raw" [, format] |
| 167 | +
|
| 168 | +REM WASM: also expose via intrinsic |
| 169 | +REM S$ = IMAGEEXPORT$(img, "png") ' returns empty if too large / fail |
| 170 | +``` |
| 171 | + |
| 172 | +**WASM contract:** |
| 173 | + |
| 174 | +- **`_wasm_image_export_ptr(slot, format)`** → ptr + len, or |
| 175 | +- **Chunked** export to **FS** (`/tmp/out.png`) + JS reads **FS** and triggers download (pattern already used in Emscripten tutorials). |
| 176 | + |
| 177 | +**CORS / UX:** Document that **download** is **IDE responsibility** (button calling JS), not automatic. |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | +## 6. STOS/AMOS-style editors in BASIC (target scenarios) |
| 182 | + |
| 183 | +These scenarios **drive** API completeness: |
| 184 | + |
| 185 | +| Tool | Needs | |
| 186 | +|------|--------| |
| 187 | +| **Tile painter** | Bitmap surface, `PSET`/`LINE`, `IMAGE COPY` to stamp tiles, optional **`TOSPRITE`** for preview | |
| 188 | +| **Charset viewer** | Already partly `petscii-data.bas`; **`COPY`** helps **tile extract** | |
| 189 | +| **Map editor** | 2D array + **`IMAGE COPY`** tiles from sheet surface to map surface | |
| 190 | +| **Sprite grabber** | **`TOSPRITE`** from back-buffer | |
| 191 | + |
| 192 | +**Editor loop:** `DO` … `GET` / `INKEY$` / `PEEK` … `IMAGE COPY` … `WAIT` / `SLEEP 1` — same as today. |
| 193 | + |
| 194 | +--- |
| 195 | + |
| 196 | +## 7. API sketch (consolidated) |
| 197 | + |
| 198 | +**Naming:** Replace **`IMAGE`** with **`BITMAP`** if we want to avoid confusion with “character image” — **open decision**. |
| 199 | + |
| 200 | +| Statement / function | Phase | |
| 201 | +|---------------------|-------| |
| 202 | +| `IMAGE CREATE h, w, h` | 1 | |
| 203 | +| `IMAGE COPY …` | 1 | |
| 204 | +| `IMAGE FILL …` | 1 | |
| 205 | +| `IMAGE FREE h` | 1 | |
| 206 | +| `IMAGE TOSPRITE …` | 3 | |
| 207 | +| `IMAGE SAVE` / `IMAGEEXPORT$` | 4 | |
| 208 | +| `IMAGE CREATE RGBA …` | 2 | |
| 209 | + |
| 210 | +**Introspection:** |
| 211 | + |
| 212 | +- `IMAGEWIDTH(h)`, `IMAGEHEIGHT(h)`, `IMAGECOUNT()` or **`MAXIMAGES`**. |
| 213 | + |
| 214 | +--- |
| 215 | + |
| 216 | +## 8. Implementation architecture (C) |
| 217 | + |
| 218 | +### 8.1 Data structures |
| 219 | + |
| 220 | +- Extend **`GfxVideoState`** or add **`GfxImageBank`**: |
| 221 | + - Array of **`GfxRasterSurface`** { `kind`, `w`, `h`, `stride`, `owned_ptr`, `bytes` }. |
| 222 | +- Surface `0` = **alias** to existing **`bitmap[]`** when `SCREEN 1`. |
| 223 | + |
| 224 | +### 8.2 Native (Raylib) |
| 225 | + |
| 226 | +- **COPY** is **CPU**; optional **future:** upload **RGBA** surface to **Texture2D** for fast preview (not required for Phase 1). |
| 227 | + |
| 228 | +### 8.3 WASM |
| 229 | + |
| 230 | +- Surfaces live in **Wasm linear memory**; **`gfx_canvas`** compositor reads **visible** bitmap as today. |
| 231 | +- **Export:** encode PNG in WASM **or** export **raw** and PNG-encode in **JS** (smaller WASM binary) — **tradeoff decision**. |
| 232 | + |
| 233 | +### 8.4 Threading |
| 234 | + |
| 235 | +- Same rules as sprites: **no OpenGL** from worker if **basic-gfx** worker thread; **COPY** is **CPU-only** → safe on worker **if** single-writer discipline holds. |
| 236 | + |
| 237 | +--- |
| 238 | + |
| 239 | +## 9. Testing |
| 240 | + |
| 241 | +- **Golden tests:** Small fixed patterns **COPY** and compare byte arrays (`tests/image_copy_test.c`). |
| 242 | +- **BASIC regressions:** `tests/image_copy.bas` — deterministic output hash or **PEEK** sample points. |
| 243 | +- **WASM:** Playwright: run program, **export**, compare **PNG** checksum or dimensions. |
| 244 | + |
| 245 | +--- |
| 246 | + |
| 247 | +## 10. Open decisions (need your input) |
| 248 | + |
| 249 | +1. **Keyword prefix:** `IMAGE` vs `BITMAP` vs `SURFACE`. |
| 250 | +2. **Rect syntax:** `w,h` vs `x1,y1,x2,y2` only. |
| 251 | +3. **`TOSPRITE` from 1 bpp:** **two-colour** fixed vs **palette** from PETSCII. |
| 252 | +4. **PNG in WASM:** encode in C vs JS. |
| 253 | +5. **Max surfaces / max WxH** on WASM (heap). |
| 254 | + |
| 255 | +--- |
| 256 | + |
| 257 | +## 11. Summary |
| 258 | + |
| 259 | +This spec aims for **honest parity** with **“screen copy + grab region + work buffer”** patterns from **AMOS/STOS**, not hardware emulation. **Phase 1** (**off-screen 1 bpp + `IMAGE COPY`**) unlocks most **editor-in-BASIC** value with **manageable** scope on **CLI and WASM**. **Export** and **RGBA** follow once the **surface allocator** and **COPY** semantics are proven. |
| 260 | + |
| 261 | +--- |
| 262 | + |
| 263 | +*Document version: 0.1 — draft* |
0 commit comments