Skip to content

Commit 23bcb88

Browse files
committed
docs: add frontend implementation brief for native video v2
- Self-contained handoff document for the zaparoo-launcher team: the v2 DDR contract (word layout, buffer addresses, publish/stop protocol, write ordering), the 352x240 writer changes, safe-area and 480i flicker rules, the calibration screen spec, and the hardware verification checklist.
1 parent 5db804a commit 23bcb88

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Frontend implementation brief: native CRT video v2 (zaparoo-launcher)
2+
3+
**Audience:** the zaparoo-launcher team / an implementation agent with no prior
4+
context. This document is self-contained; `docs/native-video-plan.md` (same
5+
repo) has the full background and rationale if you want it.
6+
**Counterpart:** Menu_MiSTer fork, branch `fix/native-video-centering` — the
7+
FPGA side of everything below is implemented, simulated, and pushed. The
8+
launcher work in this brief is the only remaining piece.
9+
**Existing code this modifies:** `src/app/native_video_writer.cpp` and the
10+
`--crt` startup path in zaparoo-launcher (see also its `docs/native-core-poc.md`).
11+
12+
---
13+
14+
## 1. What changed and why you're doing this
15+
16+
The menu core no longer outputs a 320x240 picture with hand-tuned porches, and
17+
it no longer has any OSD video options. It now generates broadcast-standard
18+
15 kHz timing in three modes, and **everything the launcher used to rely on
19+
the OSD for (CRT mode on/off, H/V centering) now travels through the DDR
20+
control block you already write**. Key consequences for the app:
21+
22+
- The framebuffer is now **352x240** (not 320x240). 352 px fills a standard
23+
NTSC/PAL active line edge-to-edge; the old 320 was ~10% too narrow on every
24+
correctly calibrated CRT.
25+
- The picture now *overscans* like broadcast TV: the outer few percent of the
26+
framebuffer is cropped on most sets. The UI must adopt safe-area rules
27+
(section 5) — this is as much a part of the fix as the FPGA work.
28+
- There is no "CRT mode" toggle anywhere. **Publishing frames IS the mode
29+
switch**: the core shows its noise pattern until your control word goes
30+
live and reverts when you zero it.
31+
- Two new modes exist when you're ready for them: **720x480i60** (mode 1) and
32+
**352x288p50 PAL** (mode 2). The core side is done; you opt in per-frame
33+
via the mode field.
34+
35+
Backward compatibility is handled on the core side: an old launcher writing
36+
the legacy 320x240 layout still displays (centered with 16-px black side
37+
bars), and your existing fb-geometry validation already self-disables the
38+
writer against an old core. Ship order doesn't matter.
39+
40+
## 2. DDR contract v2 (normative)
41+
42+
Physical base `0x3A000000`, mmap **0x300000** (3 MB, up from 640 KB).
43+
44+
| Offset | Contents |
45+
|---|---|
46+
| `+0x0` | **word0**: `(frame_counter << 2) \| active_buffer`. Bit 1 reserved, write 0. `0` means "writer stopped". |
47+
| `+0x4` | **word1**: `[31:16]` magic `0x5A50` ("ZP"); `[15:8]` h_offset, signed int8, pixels, + = right; `[7:4]` v_offset, signed 4-bit, lines, + = down; `[3:0]` mode |
48+
| `+0x1000` | buffer 0 |
49+
| `+0x180000` | buffer 1 |
50+
51+
Modes: `0` = 352x240 @ 60p (NTSC, default), `1` = 720x480 @ 60i,
52+
`2` = 352x288 @ 50p (PAL). Stride is always tight (`width * 4` bytes).
53+
Pixel format is unchanged: memcpy linuxfb BGRX rows as-is; the core swaps
54+
bytes in RTL.
55+
56+
Per-mode framebuffer numbers:
57+
58+
| Mode | fb size | stride | frame bytes |
59+
|---|---|---|---|
60+
| 0 | 352x240 | 1408 | 0x52800 (337 920) |
61+
| 2 | 352x288 | 1408 | 0x63000 (405 504) |
62+
| 1 | 720x480 | 2880 | 0x151800 (1 382 400) |
63+
64+
Protocol rules:
65+
66+
1. **Init:** write word1 (magic + mode + saved offsets) **before** the first
67+
word0 publish. The core reads both words in one atomic 64-bit beat once
68+
per vblank, so word1-then-word0 ordering guarantees the first frame is
69+
interpreted correctly.
70+
2. **Publish:** render into the inactive buffer, then write word0 once with
71+
the incremented counter and that buffer's index (single 32-bit store —
72+
this is the atomic commit). Counter is 30 bits, start at 1.
73+
3. **Mode/offset change at runtime:** update word1 first, then bump word0.
74+
The core latches mode and offsets at the field boundary; modes 0↔1 keep
75+
the same line rate (instant re-lock), 0/1↔2 is a 50↔60 Hz retune (the CRT
76+
takes a moment, like real hardware).
77+
4. **Stop:** zero word0 (zero word1 too for tidiness). The core reverts to
78+
its noise pattern within one frame. This is also your crash-recovery
79+
story — if the launcher dies and the words go stale, the core keeps
80+
scanning the last frame; only a zeroed word0 releases it, so keep the
81+
existing stop-handler behavior.
82+
5. **Offsets:** the core honors **−8…+8 px** horizontal, **−8…+2 lines**
83+
vertical, and clamps anything outside (a garbage word1 degrades to a
84+
saturated shift, never broken sync). Don't rely on the clamp — keep the
85+
calibration UI within those ranges.
86+
6. **480i is rendered progressive:** publish one normal 720x480 frame; the
87+
core extracts fields itself (reads source line `2*line + field`). No
88+
field splitting, no half-frame timing on the ARM side.
89+
90+
## 3. Task 1 — Phase A (required): 352x240 writer + safe-area UI
91+
92+
This is the must-ship piece; modes 1 and 2 are follow-ups.
93+
94+
1. `--crt` startup sets fb0 to **352x240 32bpp** (the `vmode -r 352 240
95+
rgb32` equivalent of the current 320x240 setup). Update the fb-geometry
96+
validation to expect 352x240.
97+
2. Update writer constants: width 352, stride 1408, frame size 0x52800,
98+
buffers at `+0x1000` / `+0x180000`, mmap 0x300000.
99+
3. Write word1 on init: magic `0x5A50`, mode 0, offsets from launcher config
100+
(default 0/0). Clear both words on stop.
101+
4. UI safe-area pass (section 5).
102+
5. Calibration screen (section 6).
103+
104+
Acceptance: on hardware with the new core, the launcher UI fills a CRT
105+
edge-to-edge; killing the launcher returns the noise pattern; a capture
106+
device reports 15.734 kHz / 240p.
107+
108+
## 4. Tasks 2 & 3 — PAL and 480i (when ready)
109+
110+
**PAL (mode 2):** add a "video standard: NTSC / PAL" user setting. PAL
111+
renders **352x288** and publishes mode 2. Note most PAL sets accept 60 Hz
112+
RGB over SCART ("PAL-60"), so mode 0 remains a fine default in PAL regions;
113+
mode 2 is for strict-50 Hz sets and correct-speed feel.
114+
115+
**480i (mode 1):** add a 720x480 rendering path and (optionally) per-screen
116+
mode selection — e.g. main UI in 240p, text-heavy screens in 480i.
117+
Flicker discipline is mandatory (section 5, rule 4).
118+
119+
## 5. UI rendering rules (apply to every mode)
120+
121+
These are not suggestions; geometry alone doesn't fix "every CRT crops
122+
differently":
123+
124+
1. **Render full-bleed.** Background art/color must reach all four edges.
125+
The outer few percent will be cropped on most sets and visible on a few —
126+
both must look intentional.
127+
2. **Safe areas** (SMPTE SD practice):
128+
- *Action safe* (all interactive/meaningful content): central **90%**
129+
~317x216 of 352x240, ~317x259 of 352x288, ~648x432 of 720x480.
130+
- *Title safe* (text that must be readable): central **80%**
131+
~282x192 / ~282x230 / ~576x384.
132+
3. **Pixel aspect ratio is 10:11** (pixels ~9% narrower than square) in all
133+
three modes. Ignorable for boxes-and-text; correct for logos/art that
134+
must not look squished (a true circle needs ~10% more width in pixels).
135+
4. **480i flicker discipline:** every scanline repaints 30x/second, so 1-px
136+
horizontal lines and fine text shimmer. Use ≥2 px horizontal strokes,
137+
avoid hard 1-px horizontal edges, or apply a mild vertical blur (the
138+
standard console-era 480i dashboard trick). Existing CRT typography rules
139+
in `native-core-poc.md` (integer snapping, bitmap fonts) stay in force.
140+
141+
## 6. Calibration screen
142+
143+
The launcher now owns centering (the OSD options are gone):
144+
145+
- Draw a border test pattern (240p-test-suite style: 1-px frame at the
146+
extreme edge, rectangles at the 90% and 80% safe areas, cross-hatch).
147+
- Arrow keys nudge h_offset (−8…+8, 1-px steps) and v_offset (−8…+2),
148+
publishing word1 live so the user sees the picture move in real time.
149+
- Persist the values in launcher config; load them at init. Defaults are
150+
zero — the standard timing is the centering mechanism, trims only
151+
compensate for miscentered sets.
152+
153+
## 7. Verification checklist (frontend-visible items)
154+
155+
- Fill/centering on **2–3 different CRTs** plus a capture device (should
156+
report 15.734 kHz exactly; 480i should be detected as 480i, not 240p).
157+
- Writer-stop: kill the launcher → noise pattern returns.
158+
- Trim screen: live nudge both axes; values survive a restart; out-of-range
159+
values (if forced) shift-and-saturate without disturbing sync.
160+
- Compat matrix: old launcher + new core → centered 320x240 with side bars;
161+
new launcher + old core → writer self-disables via fb-geometry validation,
162+
core shows noise (obvious, not subtle, breakage).
163+
- 480i: fine horizontal lines should shimmer, not stack (no line pairing).
164+
- HDMI output still locks in every mode (the core's ascal path handles it;
165+
just confirm).
166+
167+
## 8. Reference
168+
169+
- FPGA-side spec and rationale: `docs/native-video-plan.md` (Menu_MiSTer).
170+
- RTL that consumes this contract: `rtl/native_video_reader.sv` (the word1
171+
parse and buffer addresses are the source of truth, with simulation
172+
coverage in `tb/native_video_reader_tb.sv`).
173+
- Current writer: `src/app/native_video_writer.cpp` (zaparoo-launcher).
174+
- Why the scaler is bypassed: `docs/native-core-poc.md` (zaparoo-launcher).

0 commit comments

Comments
 (0)