diff --git a/docs/FINDINGS.md b/docs/FINDINGS.md index aa082ac..6337d73 100644 --- a/docs/FINDINGS.md +++ b/docs/FINDINGS.md @@ -370,3 +370,83 @@ Both checked this session: Having **two independent permissively-licensed transcriptions** of the arcade scene graph is better than one: they can be diffed against each other to catch transcription errors before any of it is committed to 68000 tables. + +## 17. The profiles were set far too low — and entropy coding is a CPU trap + +Prompted by the user asking why the SCSI profile was only 75 KB/s. It should not +have been. Two separate errors, one of them serious. + +### 17.1 The profile bitrates were not derived from the hardware at all + +They were read off the knee of the rate-distortion curve and then presented as +though bandwidth-driven. Against the (folklore) bus figures from 5: + +| profile | was | bus figure | utilisation | +|---|---|---|---| +| `sasi` | 45 KB/s | ~300-500 KB/s | **12%** | +| `scsi` | 75 KB/s | ~1 MB/s | **7%** | + +Nothing justified leaving 90% of the pipe unused. Raised to `sasi` 110 KB/s +(lam=60) and `scsi` 280 KB/s (lam=10), which is 35% and 28% utilisation — +still conservative, because the bus figures are folklore. + +### 17.2 CPU is NOT the reason to stay low — but entropy coding would be + +Budget is 833,333 cycles/frame at 12 fps. At session 1's measured ~6.5 cycles +per GVRAM pixel write: + +| work | cycles | % of budget | +|---|---|---| +| blit 20.1% of pixels (session 1's 24fps figure) | 64k | 7.7% | +| blit 40% of pixels (the same content at 12fps) | 128k | 15.3% | +| **blit the FULL frame, every frame** | **319k** | **38.3%** | +| deflate decode, ~30 KB/frame output | 1,800k | **216%** | +| LZ4/LZSS decode, ~30 KB/frame output | 450k | **54%** | + +Two conclusions, and the second one corrects 8: + +- **Raising the VQ bitrate is nearly free on CPU.** Even a full-frame pixel-exact + blit fits in 38% of budget, and VQ decode is table copies — RAW, the mode that + dominates at high bitrate, is the *cheapest* mode to blit, not the dearest. +- **The 247 KB/s "lossless changed-spans + deflate" figure in 8 is a compression + upper bound, NOT a shippable design.** Deflate's Huffman decode is bitwise and + costs about 2.2x the entire frame budget on a 68000. Even byte-oriented LZ4 at + 54% leaves too little beside a 38% blit. **Do not plan on entropy coding.** + All profile bitrates are raw payload. + +This inverts session 1's "the CPU is idle, I/O is the ceiling" for the *decode* +path specifically: the blit is cheap, but any bit-oriented decompressor is not. +VQ is the right architecture precisely because its decode is a table copy. + +### 17.3 The hybrid at lam=0 IS the lossless codec + +Measured, un-entropy-coded raw payload, and deflated for reference only: + +| scene | lam=0 raw | lam=0 deflated | lossless changed-spans+deflate | PSNR | +|---|---|---|---|---| +| 00020 | 442.1 KB/s | 274.5 KB/s | 267.3 KB/s | 39.90 = ceiling | +| 00146 | 467.6 KB/s | 223.2 KB/s | 219.1 KB/s | 35.25 = ceiling | + +The hybrid at `lam=0` converges to within 3% of the purpose-built lossless coder. +That confirms the architecture unifies: there is no separate lossless path to +maintain, just the same bitstream with the knob open. + +### 17.4 Full curve in raw (shippable) bytes + +| lam | 00020 PSNR | 00020 KB/s | 00146 PSNR | 00146 KB/s | RAW% | +|---|---|---|---|---|---| +| 0 | **39.90** (exact) | 442.1 | **35.25** (exact) | 467.6 | ~76% | +| 10 | 39.38 | 248.1 | 32.27 | 305.2 | ~44% | +| 25 | 38.68 | 182.2 | 31.04 | 193.5 | ~26% | +| 60 | 36.94 | 108.0 | 29.61 | 103.1 | ~10% | +| 150 | 35.31 | 55.6 | 28.63 | 56.1 | ~1% | +| 300 | 34.80 | 44.1 | 28.28 | 44.4 | 0% | + +### 17.5 This makes the blocked disk benchmark critical-path + +Session 1 judged it "NOT on the critical path" because VQ at 30 KB/s was correct +whether SASI did 300 or 600 KB/s. That reasoning no longer holds. The profiles +now sit at 110 and 280 KB/s, close enough to the folklore ceilings that the +error bars matter, and **if SCSI sustains >=800 KB/s the correct `scsi` profile +is lam=0 — pixel-exact video.** Whether this port ships transparent or lossy on +SCSI is now waiting on one measurement. diff --git a/docs/STATUS.md b/docs/STATUS.md index 7c30317..b073a9e 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -18,10 +18,14 @@ Session 1 left "which machine do we target" open. The user's answer: **ship both as two quality profiles. This is now implemented rather than hypothetical — the bitrate ceiling is a build parameter in `tools/encoder/ratectl.py`: -| profile | target | lam | quality (00020 / 00146) | machine | -|---|---|---|---|---| -| `sasi` | 45 KB/s | 300 | 34.8 / 28.3 dB | stock 10MHz ACE/EXPERT | -| `scsi` | 75 KB/s | 100 | 35.9 / 29.0 dB | Super/XVI, or CZ-6BS1 board | +| profile | target | lam | quality (00020 / 00146) | bus utilisation | machine | +|---|---|---|---|---|---| +| `sasi` | 110 KB/s | 60 | 36.9 / 29.6 dB | 35% of 300 KB/s | stock 10MHz ACE/EXPERT | +| `scsi` | 280 KB/s | 10 | 39.4 / 32.3 dB | 28% of 1 MB/s | Super/XVI, or CZ-6BS1 board | + +`scsi` is now within **0.5 dB of the palette ceiling** on 00020. These were +initially set at 45 / 75 KB/s, which was 12% / 7% bus utilisation — read off the +RD curve rather than derived from the hardware. See FINDINGS 17. Codebooks are **k=256 with 1-byte indices** in both profiles. k=1024 was measured and rejected — see FINDINGS 14, it was a false-good result from a rate model @@ -71,8 +75,8 @@ multi-byte fields are **big-endian** so the 68000 reads them with a plain `move` - **Rate control is written but not yet wired into `encode.py`** — the CLI uses a fixed `lam` from the profile. `ratectl.encode_rate_controlled()` exists and builds a lam-ladder per frame; it needs hooking up and validating. -- **Payload is not entropy-coded.** Deflate on the payload should buy ~1.4x - (measured on the lossless path, FINDINGS 8). LZ decode is cheap on a 68000. +- **Payload is deliberately NOT entropy-coded** — deflate decode does not fit in + the 68000's frame budget (FINDINGS 17.2). Do not "optimise" this later. - Codebooks are per-scene and rebuilt from scratch; no inter-scene reuse. - `_paint` is a Python per-block loop — fine for prototyping, slow for a full disc encode. Vectorise before the 224-stream run. @@ -107,12 +111,16 @@ Unchanged from session 1 — `IOCS _B_READ` returns -1 uniformly. Full diagnosis and the four untested hypotheses are in session 1's notes (git history of this file, commit 65112b9). -**This now matters more than session 1 thought.** Session 1 dismissed it because -"VQ at 30 KB/s is correct whether SASI does 300 or 600 KB/s". But we now ship -*two profiles*, and the profile bitrates (45 / 120 KB/s) are set against -**folklore** bandwidth figures. A real measurement would let us set them -honestly instead of conservatively. Next move is the untried SCSI path: -`-exp1 cz6bs1 -hard disk.chd`. +**This is now CRITICAL-PATH, not optional.** Session 1 dismissed it because +"VQ at 30 KB/s is correct whether SASI does 300 or 600 KB/s". That reasoning is +dead: the profiles now sit at 110 and 280 KB/s, close enough to the folklore +ceilings that the error bars change the product. Specifically — + +**If SCSI sustains >=800 KB/s, the correct `scsi` profile is `lam=0`: pixel-exact +video, ~450 KB/s, and only 38% of the CPU budget.** Whether this port ships +transparent or lossy on SCSI is waiting on one measurement. + +Next move is the untried SCSI path: `-exp1 cz6bs1 -hard disk.chd`. --- @@ -120,7 +128,10 @@ honestly instead of conservatively. Next move is the untried SCSI path: 1. **Wire rate control into `encode.py`** and validate that the hard ceiling actually holds on an action scene (the whole point of choosing VQ). -2. **Entropy-code the payload** (deflate) — ~1.4x for cheap 68000 decode cost. +2. ~~Entropy-code the payload~~ — **ABANDONED, see FINDINGS 17.2.** Deflate + decode is ~216% of the frame budget on a 68000 and LZ4 is ~54%; there is no + room beside a 38% blit. All bitrates are raw payload. This also demotes the + "247 KB/s lossless" figure in FINDINGS 8 to a compression bound, not a design. 3. **68000 decoder skeleton**: parse `DLX1`, expand codebooks to word-per-pixel, blit V1/V4/RAW/SKIP. Measure real cycles with the existing MAME Lua harness — this is the first time the harness gets used for its actual purpose. diff --git a/tools/encoder/ratectl.py b/tools/encoder/ratectl.py index bbb58b4..0bf8b37 100644 --- a/tools/encoder/ratectl.py +++ b/tools/encoder/ratectl.py @@ -19,25 +19,38 @@ import vq_hybrid as H # see docs/FINDINGS.md 5 -- these are FOLKLORE-grade until the disk benchmark # is unblocked, so they are deliberately conservative fractions of the quoted # ceiling (audio, seeks and container overhead come out of the same pipe). -# Calibrated against the CORRECTED rate-distortion measurement (FINDINGS 14). +# Calibrated against the corrected rate-distortion curve (FINDINGS 14-15) AND +# against the bus and CPU budgets (FINDINGS 17). Session 2 initially set these +# far too low: 45 / 75 KB/s is 12% of the pessimistic SASI folklore figure and +# 7% of the SCSI one. Nothing justified that -- the numbers were read off the +# knee of the RD curve, not off the hardware. # -# k=256 with 1-byte indices beats k=1024 with 2-byte indices at every matched -# bitrate. The earlier "+2.4 dB for k=1024" was an artifact of a rate model that -# charged 1 byte for a 10-bit index. 1-byte indices also mean the 68000 decoder -# reads a plain move.b with no alignment case, and the codebook is 8 KB not 32 KB. +# What actually bounds the high end: +# - Bus: unmeasured. ~300-500 KB/s SASI / ~1 MB/s SCSI, both FOLKLORE. +# This is the binding unknown and the reason the disk benchmark matters. +# - CPU: a FULL-frame blit is only 38% of the 12fps budget, and VQ decode is +# table copies (RAW, the mode that dominates at high rate, is the CHEAPEST +# to blit). So raising the bitrate is nearly free on CPU. +# - Entropy coding is NOT the way to buy headroom here: deflate decode is +# ~216% of the frame budget on a 68000 and even LZ4 is ~54%. See FINDINGS 17. +# The rates below are therefore RAW payload, no entropy coding. # # The two profiles are the SAME codec, decoder and bitstream -- only `lam` differs. PROFILES = { - "sasi": dict(kbps=45, lam=300.0, k1=256, k4=256, + "sasi": dict(kbps=110, lam=60.0, k1=256, k4=256, desc="stock 10MHz ACE/EXPERT, SASI", - quality="34.8 dB on 00020 / 28.3 dB on 00146"), - "scsi": dict(kbps=75, lam=100.0, k1=256, k4=256, + quality="36.9 dB on 00020 / 29.6 dB on 00146", + util="~105 KB/s = 35% of the pessimistic 300 KB/s SASI figure"), + "scsi": dict(kbps=280, lam=10.0, k1=256, k4=256, desc="Super/XVI, or CZ-6BS1 board in a 10MHz machine", - quality="35.9 dB on 00020 / 29.0 dB on 00146"), + quality="39.4 dB on 00020 / 32.3 dB on 00146", + util="~275 KB/s = 28% of the 1 MB/s SCSI folklore figure"), } -# Not a shipping profile, but the curve continues: lam=25 is ~185 KB/s at ~38.7 dB -# with 26% RAW blocks, and lam->0 is pixel-exact (0.00 dB loss). Entropy-coding -# the payload (NOT YET IMPLEMENTED) should shift the whole curve ~1.4x left. +# lam=0 is PIXEL-EXACT against the palettised frame (0.00 dB loss) at ~450 KB/s +# of raw payload, and costs only 38% of the CPU budget. If the blocked disk +# benchmark confirms SCSI sustains >=800 KB/s, the `scsi` profile should become +# lam=0 and the port ships transparent video. That decision is waiting on a +# measurement, not on a design choice. AUDIO_KBPS = 7.8 # MSM6258 ADPCM 15.6kHz mono -- comes out of the same budget