Skip to content

Commit e6e7723

Browse files
committed
docs: add extended SCREEN modes plan (higher resolutions, 256-color)
1 parent f3a05a6 commit e6e7723

1 file changed

Lines changed: 173 additions & 0 deletions

File tree

docs/screen-modes-plan.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Extended SCREEN Modes / Resolutions Plan
2+
3+
**Status:** Proposal.
4+
**Owner:** Chris Garrett.
5+
**Date:** 2026-04-17.
6+
**Depends on:** renderer migration (`docs/wasm-webgl-migration-plan.md`) is NOT a hard prerequisite — phase 1 and 2 work on the current renderer. Phase 3 (higher resolutions on web) is where raylib-emscripten pays off.
7+
8+
---
9+
10+
## 1. Goals
11+
12+
- Support additional screen modes / resolutions beyond the current `SCREEN 0` (40×25 text) and `SCREEN 1` (320×200 1bpp bitmap).
13+
- Keep the existing low-res modes backwards-compatible: POKE/PEEK screen RAM + colour RAM + keyboard matrix at documented addresses still works.
14+
- New hi-res modes do NOT expose POKE addressing. Sprite and primitive APIs (`PSET`, `LINE`, `DRAWTEXT`, `LOADSPRITE`, `DRAWSPRITE`, future) are the drawing surface.
15+
- Allow `SCREEN n` to switch modes mid-program — mode change reallocates buffers, clears screen, returns control.
16+
- Extend palette to 256-color indexed as the near-term ceiling. Higher (RGBA / 24-bit) deferred to a later phase.
17+
18+
## 2. Non-goals
19+
20+
- Dropping support for `SCREEN 0` / `SCREEN 1`. They remain the primary PETSCII / retro experience.
21+
- Hardware-accelerated shader effects (CRT scanlines, glow, etc.) — separate plan.
22+
- Tile / sprite atlases beyond what `LOADSPRITE` already does.
23+
- 3D. Not happening.
24+
25+
## 3. Mode numbering
26+
27+
| Mode | Resolution | Palette | POKE? | Introduced | Notes |
28+
|------|------------|---------|-------|------------|-------|
29+
| `SCREEN 0` | 40×25 text (optional 80×25) | 16-colour C64 | yes | existing | Text RAM 1024, colour RAM 55296. |
30+
| `SCREEN 1` | 320×200 1bpp bitmap | 16-colour (fg/bg) | yes | existing | `BITMAPCLEAR`, `PSET`, `LINE`, text-in-bitmap. |
31+
| `SCREEN 2` | 640×200 1bpp bitmap | 16-colour (fg/bg) | no | new, phase 2 | Widescreen retro; 2× horizontal of mode 1. |
32+
| `SCREEN 3` | 640×480 1bpp bitmap | 16-colour (fg/bg) | no | new, phase 2 | VGA hi-res, mono. |
33+
| `SCREEN 4` | 320×200 8bpp indexed | 256 | no | new, phase 3 | VGA mode 13h analogue. |
34+
| `SCREEN 5` | 640×200 8bpp indexed | 256 | no | new, phase 3 | Widescreen 256-color. |
35+
| `SCREEN 6` | 640×480 8bpp indexed | 256 | no | new, phase 3 | VGA mode 12h-ish. |
36+
| `SCREEN 7+` | reserved ||| future | 24-bit RGB / RGBA / tilemap. |
37+
38+
Text mode parity in hi-res: if `PRINT` / `TEXTAT` / `DRAWTEXT` are used in `SCREEN 2+`, glyphs are stamped into the framebuffer using the same 8×8 char ROM and letterboxed/clipped to resolution. `LOCATE` snaps to 8-pixel cells.
39+
40+
## 4. BASIC-level API
41+
42+
### 4.1 Mode selection
43+
44+
```
45+
SCREEN mode
46+
```
47+
48+
- Integer in 0..6 (growing). Errors on unknown mode.
49+
- Mode switch clears the framebuffer, resets fg/bg to sensible defaults for that mode, and invalidates any sprite z-order cached state (sprites themselves persist).
50+
- Existing `SCREEN 0` / `SCREEN 1` semantics are unchanged.
51+
52+
### 4.2 Mode introspection
53+
54+
```
55+
n = SCREENMODE ' current mode number
56+
w = SCREENWIDTH ' pixel width of current mode
57+
h = SCREENHEIGHT ' pixel height of current mode
58+
c = SCREENCOLORS ' 16 or 256
59+
```
60+
61+
Lets portable programs ask at runtime rather than hardcoding.
62+
63+
### 4.3 Drawing in `SCREEN 2+`
64+
65+
- `COLOR idx`, `BACKGROUND idx` — indexed palette entry. 0..15 for modes 2/3 (map to same 16-color palette). 0..255 for modes 4/5/6.
66+
- `PSET x, y [, c]`, `PRESET x, y` — same semantics as mode 1, coord range is mode-specific.
67+
- `LINE x1, y1 TO x2, y2 [, c]` — same.
68+
- `BITMAPCLEAR` — clears to background.
69+
- `RECT`, `FILL` (new, phase 2) — convenient primitives; implementable in mode 1 too as a backport, but lead in `SCREEN 2+`.
70+
- `PRINT` / `TEXTAT` / `LOCATE` — glyph stamp at 8×8 cells.
71+
- `DRAWTEXT x, y, "string" [, fg, bg]` — pixel-space text (scheduled in `docs/bitmap-text-plan.md`).
72+
- `LOADSPRITE`, `DRAWSPRITE`, `SPRITECOPY`, `SPRITEMOVE`, etc. — unchanged API, new modes just have more room.
73+
74+
### 4.4 Disallowed in `SCREEN 2+`
75+
76+
- `POKE 1024+offset, ch` — no text RAM at fixed address. Error: "POKE screen RAM only in SCREEN 0/1".
77+
- `POKE 55296+offset, col` — no colour RAM. Same error.
78+
- `PEEK 1024` etc. — same.
79+
80+
Keyboard matrix `PEEK(56320+n)` stays available in all modes (it's input, not framebuffer).
81+
82+
### 4.5 Palette in 256-color modes
83+
84+
```
85+
PALETTE idx, r, g, b
86+
```
87+
88+
- `idx` 0..255, `r/g/b` 0..255.
89+
- Entries 0..15 default to the C64 16-color palette for continuity; programs may override.
90+
- Palette persists across mode switches within the same 256-color class; reset on switch to/from 16-color.
91+
92+
## 5. Implementation phases
93+
94+
### Phase 1 — runtime-sized buffers (blocks nothing, enables everything)
95+
96+
**Scope**: refactor `gfx/gfx_video.c/h` and both renderers so the bitmap plane, text RAM, colour RAM, and sprite-composite target are runtime-sized, not compile-time `#define`d. No new BASIC-level modes yet.
97+
98+
**Files**:
99+
- `gfx/gfx_video.h`: replace `GFX_BITMAP_WIDTH`/`HEIGHT` `#define`s with fields on `GfxVideoState` (`bitmap_w`, `bitmap_h`, `bitmap_bpp`). Keep the `#define`s as default values for mode 1.
100+
- `gfx/gfx_video.c`: replace static `uint8_t bitmap[GFX_BITMAP_BYTES]` with a `uint8_t *bitmap` + allocated length. Add `gfx_video_set_mode(GfxVideoState *, int mode)` that allocates/reallocates.
101+
- `gfx/gfx_raylib.c`: `ensure_pixbuf` and `LoadRenderTexture(nat_w, nat_h)` already re-allocate on size change. Minor: `nat_w`/`nat_h` in `main` become mode-dependent, re-queried after each `SCREEN` call via a getter. Letterbox/fullscreen code unaffected.
102+
- `gfx/gfx_canvas.c` (FROZEN per `docs/wasm-webgl-migration-plan.md` §4.1 — but this is a refactor not a feature, borderline): make the RGBA buffer runtime-sized. Since canvas is frozen, **prefer** to keep canvas at fixed 320×200 and implement the new modes raylib-side only. Programs using new modes on the canvas renderer get a runtime error "SCREEN mode N not available on canvas renderer; use ?renderer=raylib".
103+
- `basic.c`: `SCREEN` statement handler validates mode number and calls `gfx_video_set_mode`.
104+
105+
**Tests**: existing `SCREEN 0` / `SCREEN 1` tests keep passing. Add regression test that switches `SCREEN 0 → 1 → 0` mid-program and verifies no memory corruption.
106+
107+
**Output**: no user-visible change. Foundation laid.
108+
109+
### Phase 2 — 16-color hi-res modes (`SCREEN 2`, `SCREEN 3`)
110+
111+
**Scope**: enable the two 1bpp widescreen / VGA hi-res modes using the same palette model as `SCREEN 1`. Sprites + primitives + glyph text all working.
112+
113+
**Files**:
114+
- `gfx/gfx_video.c`: extend `gfx_video_set_mode` to handle modes 2 and 3. `gfx_bitmap_get_pixel` / `gfx_bitmap_set_pixel` already take `(x,y)` and mask into packed bits — only the stride (`GFX_BITMAP_WIDTH / 8u`) needs replacing with `s->bitmap_w / 8`.
115+
- `gfx/gfx_raylib.c`: `render_bitmap_screen` uses `s->bitmap_w`/`s->bitmap_h` instead of macros. `nat_w`/`nat_h` in `main` track current mode. `LoadRenderTexture` resized on mode change (free old + load new).
116+
- `basic.c`: `SCREEN` accepts 0..3. `SCREENMODE`/`SCREENWIDTH`/`SCREENHEIGHT`/`SCREENCOLORS` builtins added.
117+
- Sprite path: `gfx_sprite_*` already samples screen dims from the video state; verify no hardcoded 320/200.
118+
119+
**Tests**:
120+
- Draw a single-pixel outline at `(0,0)` / `(639,479)` in mode 3; verify both corners rendered.
121+
- Fullscreen with mode 3 on a 16:9 monitor → pillarbox bars on sides (4:3 content).
122+
- Mid-program switch `SCREEN 1 → 3 → 1` preserves sprite slots.
123+
124+
### Phase 3 — 256-color modes (`SCREEN 4/5/6`) + PALETTE
125+
126+
**Scope**: 8bpp indexed framebuffer, programmable palette, same resolutions as phase 2 plus `SCREEN 4` for low-res 256-color.
127+
128+
**Files**:
129+
- `gfx/gfx_video.h/c`: add 8bpp code path. `GfxVideoState` gets `bitmap_bpp` field (1 or 8). Backing buffer becomes `uint8_t *bitmap` where size = `w * h` bytes for 8bpp, `(w * h) / 8` for 1bpp. `gfx_palette[256]` (`Color[256]`) added.
130+
- `gfx/gfx_raylib.c`: `render_bitmap_screen` branches on `s->bitmap_bpp`: 1bpp existing path, 8bpp new path doing palette lookup per pixel into `g_pixbuf` (still 1 CPU pass, then 1 GPU upload).
131+
- `basic.c`: `PALETTE idx, r, g, b` statement. `SCREEN` accepts 0..6. `COLOR` / `BACKGROUND` accept 0..255 in 256-color modes.
132+
133+
**Tests**:
134+
- Set palette entry 200 to RGB(255, 128, 0); `PSET 10, 10, 200`; grab screenshot; assert that pixel is orange.
135+
- `SCREEN 1 → 4 → 1`: entering 256-color resets palette 0..15 to C64 defaults on exit.
136+
137+
### Phase 4 — future (not this plan)
138+
139+
- 24-bit / RGBA direct-color mode (e.g. `SCREEN 7` 640×480 RGBA).
140+
- Tilemap-native mode (`SCREEN 8`?) with a tile atlas and scroll registers.
141+
- CRT / scanline / bloom shader post-process on the final `DrawTexturePro` pass. Raylib `BeginShaderMode` + GLSL fragment shader.
142+
143+
## 6. Memory / performance notes
144+
145+
Buffer sizes:
146+
147+
| Mode | Framebuffer bytes | WASM memory impact |
148+
|------|-------------------|--------------------|
149+
| 1 (320×200 1bpp) | 8,000 | baseline |
150+
| 2 (640×200 1bpp) | 16,000 | +8 KB |
151+
| 3 (640×480 1bpp) | 38,400 | +30 KB |
152+
| 4 (320×200 8bpp) | 64,000 | +56 KB |
153+
| 5 (640×200 8bpp) | 128,000 | +120 KB |
154+
| 6 (640×480 8bpp) | 307,200 | +299 KB |
155+
156+
Plus the renderer's CPU-side `Color[w*h]` buffer (4 bytes per pixel): mode 6 = 1.2 MB. Mode 6 on current `gfx_canvas.c` software path would struggle on older CPUs (1.2 M pixels × 60 fps = 73 M pixel writes/s just for the clear-and-upload). Raylib path = GPU-upload once, GPU-composite sprites on top — trivial.
157+
158+
This is the concrete reason phase 3 (640×480 256-color) really wants the raylib-emscripten migration first on web. Desktop is already fine either way.
159+
160+
## 7. Open questions
161+
162+
- Should mode switch preserve content in the new framebuffer (e.g. when going 1 → 3, upscale the old contents)? Or always clear? Default: **always clear**, simpler and matches DOS-era behaviour. Reopen if users complain.
163+
- Keep PETSCII char ROM for text in hi-res modes, or allow a custom font per mode? Phase 2 uses existing 8×8 ROM. Custom fonts tracked in `docs/bitmap-text-plan.md`.
164+
- Runtime cost of per-mode render target recreate on frequent `SCREEN` switches? Probably fine (mode switches are rare) but cache last two targets if it becomes a hot path.
165+
- WASM canvas path (frozen): definitely lock to SCREEN 0/1 only and error on SCREEN 2+? Or attempt a CPU-only 16-color hi-res to keep feature parity? **Proposal**: error, with a clear message pointing to `?renderer=raylib`. Keeps the freeze clean and the raylib path's value visible.
166+
- Sprite coordinate space: sprites placed in mode-1 coordinates (320×200) stay visually small in `SCREEN 3` (640×480). Intended behaviour: sprites use framebuffer-pixel coords, so mode switch changes effective scale. Alternative: add `SPRITESCALE factor` / make sprites scale-aware. Decide during phase 2 implementation.
167+
168+
## 8. Success criteria
169+
170+
- Existing `examples/*.bas` programs using `SCREEN 0` / `SCREEN 1` run identically after phase 1 refactor (byte-for-byte framebuffer regression test OK).
171+
- `examples/gfx_hires_demo.bas` (new, phase 2) runs on `basic-gfx` native and on the raylib-emscripten WASM path with identical output.
172+
- Phase 3 adds `examples/gfx_256color_demo.bas` with a palette fade.
173+
- No canvas renderer regressions during any phase (canvas stays on `SCREEN 0/1` only; clear error for other modes).

0 commit comments

Comments
 (0)