Handoff: reconcile docs and tooling with the corrections made this session
Session 2 reversed several of its own conclusions. The docs are append-only, so a reader could land on a superseded section and act on it. This pass makes the repo internally consistent. Defects found and fixed in STATUS.md: - claimed "Hybrid VQ with k=1024: no" as the answer to the linework question, directly contradicting FINDINGS 14, which rejected k=1024. Both profiles are k=256. - malformed profile table (six column separators, five columns). - next-steps list had two items numbered 3 and listed the full-disc survey twice. - the disk-benchmark section still read CRITICAL-PATH with "if SCSI sustains >=800 KB/s, ship pixel-exact". That was written while the bandwidth figure was misread as 4 MB/s. At 4 Mbps pixel-exact needs 92-97% of the pipe and is not available, and the ring-buffer result means the design no longer hangs on the benchmark at all. Rewritten with what it IS still worth doing: confirming the 4 Mbps provenance, and confirming DMA is used rather than PIO. FINDINGS now carries supersession blockquotes on 5, 8, 11, 17 and 18 pointing at the sections that correct them. 18 is the dangerous one -- its peak-vs- sustained test is reversed by 21 -- so it is marked DO NOT ACT ON THIS SECTION while noting the per-frame data itself remains valid. profile_gen.py had the same problem in code: it defaulted to the superseded peak sizing and returned lam=25 where the docs say lam=10. The buffered test is now the default and peak sizing is behind --size-for-peak as a bound only. A tool that contradicts the findings is worse than no tool. Also preserves the five measurement scripts that produced this session's numbers as tools/analysis/05-09, following the session 1 precedent, and adds an "explicitly abandoned -- do not re-propose" list to STATUS covering entropy coding, k=1024 codebooks and flat 4x4 VQ. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -11,12 +11,18 @@ pushing ~22 minutes of Don Bluth animation through a 10MHz 68000.
|
|||||||
decision, and a section on measurement traps that produced three separate
|
decision, and a section on measurement traps that produced three separate
|
||||||
false results. Read §4 before trusting any pipeline number.
|
false results. Read §4 before trusting any pipeline number.
|
||||||
- **`docs/STATUS.md`** — current state, working setup, blockers, next steps.
|
- **`docs/STATUS.md`** — current state, working setup, blockers, next steps.
|
||||||
|
**Start here.** It also lists what has been explicitly abandoned, so old ideas
|
||||||
|
do not get re-proposed.
|
||||||
|
- **`docs/BENCHMARK.md`** — how to measure the storage subsystem, and why a
|
||||||
|
bandwidth figure out of MAME would be meaningless.
|
||||||
- **`docs/HARDWARE.md`** — X68000 GVRAM/CRTC reference.
|
- **`docs/HARDWARE.md`** — X68000 GVRAM/CRTC reference.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
```
|
```
|
||||||
docs/ findings, status, hardware reference
|
docs/ findings, status, hardware reference
|
||||||
tools/analysis/ frame-analysis scripts (01/02 marked BROKEN as regression refs)
|
tools/analysis/ measurement scripts, numbered in the order they were written
|
||||||
|
(01/02 marked BROKEN deliberately, kept as regression refs).
|
||||||
|
Run from the repo root — they import from tools/encoder/.
|
||||||
tools/bench/ MAME Lua injection harness + 68000 benchmark sources
|
tools/bench/ MAME Lua injection harness + 68000 benchmark sources
|
||||||
tools/vasm/ vasm m68k assembler (built from source)
|
tools/vasm/ vasm m68k assembler (built from source)
|
||||||
tools/encoder/ hybrid VQ encoder + DLX1 container writer (working)
|
tools/encoder/ hybrid VQ encoder + DLX1 container writer (working)
|
||||||
@@ -31,12 +37,22 @@ python3 tools/encoder/extract.py 00020 /tmp/fr 12 crop
|
|||||||
python3 tools/encoder/encode.py /tmp/fr out.dlx --profile sasi --preview p.png
|
python3 tools/encoder/encode.py /tmp/fr out.dlx --profile sasi --preview p.png
|
||||||
```
|
```
|
||||||
|
|
||||||
Two quality profiles ship from one codec and one decoder — `sasi` (45 KB/s) and
|
Two quality profiles ship from one codec and one decoder — `sasi` (110 KB/s) and
|
||||||
`scsi` (120 KB/s) are two points on the same rate-distortion curve. The codec is
|
`scsi` (280 KB/s) are two points on the same rate-distortion curve. The codec is
|
||||||
a Cinepak-style hybrid: each 4x4 block is coded as SKIP, one 4x4 codeword, four
|
a Cinepak-style hybrid: each 4x4 block is coded as SKIP, one 4x4 codeword, four
|
||||||
2x2 codewords, or RAW literal pixels, chosen per block by rate-distortion.
|
2x2 codewords, or RAW literal pixels, chosen per block by rate-distortion.
|
||||||
|
|
||||||
The RAW escape means `lam=0` is pixel-exact against the palettised frame, so the
|
The RAW escape means `lam=0` is pixel-exact against the palettised frame, so the
|
||||||
quality knob spans lossless to heavily-compressed without changing the bitstream.
|
quality knob spans lossless to heavily-compressed without changing the bitstream.
|
||||||
|
|
||||||
|
Profiles are derived from a bandwidth figure, not chosen by eye:
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 tools/encoder/profile_gen.py --bw-mbps 4 --name scsi
|
||||||
|
```
|
||||||
|
|
||||||
|
> **On reading `docs/FINDINGS.md`:** it is append-only and several later sections
|
||||||
|
> overturn earlier ones. Superseded sections carry a blockquote at the top
|
||||||
|
> pointing to the correction — heed those, especially 18 (reversed by 21).
|
||||||
|
|
||||||
Source media (`DRAGONS_LAIR.iso`) and ROMs are gitignored — supply your own.
|
Source media (`DRAGONS_LAIR.iso`) and ROMs are gitignored — supply your own.
|
||||||
|
|||||||
+20
-12
@@ -106,20 +106,24 @@ FINDINGS 17.4:
|
|||||||
python3 tools/encoder/profile_gen.py --bw-mbps 4 --name scsi
|
python3 tools/encoder/profile_gen.py --bw-mbps 4 --name scsi
|
||||||
```
|
```
|
||||||
|
|
||||||
It accounts for the three things that eat the pipe before video sees any of it:
|
It accounts for what eats the pipe before video sees any of it: audio
|
||||||
audio (7.8 KB/s), peak-to-mean burstiness (measured 1.4-1.9x), and it reports
|
(7.8 KB/s), the buffering condition, and it reports the DMA cycle-steal so the
|
||||||
the DMA cycle-steal so the CPU coupling stays visible.
|
CPU coupling stays visible.
|
||||||
|
|
||||||
At 4 Mbps it currently returns:
|
At 4 Mbps it returns:
|
||||||
|
|
||||||
| | lam | mean | peak | 00020 | 00146 | CPU |
|
| sizing rule | lam | mean | 00020 | 00146 | CPU |
|
||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| today (no rate control) | 25 | 194 KB/s | 368 KB/s | -1.22 dB | -4.21 dB | 53% |
|
| **buffered (default, FINDINGS 21)** | **10** | 305 KB/s | -0.52 dB | -2.98 dB | 51% |
|
||||||
| with rate control wired | 10 | 305 KB/s | 305 KB/s | -0.52 dB | -2.98 dB | 51% |
|
| `--size-for-peak` (FINDINGS 18, superseded) | 25 | 194 KB/s | -1.22 dB | -4.21 dB | 46% |
|
||||||
|
|
||||||
**Rate control is worth a full step on the quality ladder** — it is not a
|
The default is the buffered test: cumulative demand vs cumulative supply.
|
||||||
tidiness feature, it is the difference between sizing for the peak and sizing
|
Ring-buffer simulation gives **zero required prefill** for every measured scene,
|
||||||
for the mean. That is the strongest argument yet for wiring up `ratectl.py`.
|
so `lam=10` ships without rate control. `--size-for-peak` reproduces the earlier
|
||||||
|
pessimistic sizing and is kept only as a bound.
|
||||||
|
|
||||||
|
**Rate control is therefore insurance, not a fix.** Its value is a deterministic
|
||||||
|
ceiling over the 220 streams not yet measured — see the survey caveat below.
|
||||||
|
|
||||||
## What would change the design
|
## What would change the design
|
||||||
|
|
||||||
@@ -127,7 +131,11 @@ for the mean. That is the strongest argument yet for wiring up `ratectl.py`.
|
|||||||
collapses toward today's `sasi`, and the two profiles stop being meaningfully
|
collapses toward today's `sasi`, and the two profiles stop being meaningfully
|
||||||
different. At that point reconsider 10 fps, or a narrower active area.
|
different. At that point reconsider 10 fps, or a narrower active area.
|
||||||
- **If sustained is much above** (>=8 Mbps / 976 KB/s): `lam=0` fits with
|
- **If sustained is much above** (>=8 Mbps / 976 KB/s): `lam=0` fits with
|
||||||
margin and the port ships **pixel-exact** video on SCSI.
|
margin and the port ships **pixel-exact** video on SCSI. At the current
|
||||||
|
4 Mbps figure this is NOT available — `lam=0` needs 92-97% of the pipe.
|
||||||
|
- **If the full-disc survey finds a sustained action sequence hotter than
|
||||||
|
00146** (313 KB/s mean, the worst of 4 clips sampled): that is the scenario
|
||||||
|
rate control exists for, and the reason to wire it up before the survey run.
|
||||||
- **If DMA cannot be used** and transfers fall back to PIO, the CPU cost rises
|
- **If DMA cannot be used** and transfers fall back to PIO, the CPU cost rises
|
||||||
from ~15% to something far larger and CPU becomes the binding constraint.
|
from ~15% to something far larger and CPU becomes the binding constraint.
|
||||||
This is the single worst outcome and is worth checking early in Tier 1.
|
This is the single worst outcome and is worth checking early in Tier 1.
|
||||||
|
|||||||
@@ -103,6 +103,10 @@ regression references; 03 and 04 are the correct ones.
|
|||||||
|
|
||||||
## 5. Storage interface — the SASI/SCSI split
|
## 5. Storage interface — the SASI/SCSI split
|
||||||
|
|
||||||
|
> **SUPERSEDED IN PART.** The claim below that DMA means streaming "costs
|
||||||
|
> essentially no CPU" is **wrong** — see 19. The bandwidth figures here are
|
||||||
|
> folklore; the working figure is now **4 Mbps = 488 KB/s** (21).
|
||||||
|
|
||||||
[Yasuma, X68030 internal SCSI controller]
|
[Yasuma, X68030 internal SCSI controller]
|
||||||
|
|
||||||
- Interface: **SCSI-1**, 50-pin, 5 MB/s bus spec
|
- Interface: **SCSI-1**, 50-pin, 5 MB/s bus spec
|
||||||
@@ -196,6 +200,11 @@ Caveat: all of the above is from README/repo-tree summaries, not their source.
|
|||||||
|
|
||||||
## 8. CORRECTION to session 1: halving the framerate does NOT halve the bitrate
|
## 8. CORRECTION to session 1: halving the framerate does NOT halve the bitrate
|
||||||
|
|
||||||
|
> **PARTLY SUPERSEDED.** The framerate correction stands. The
|
||||||
|
> "changed-spans + deflate = 247 KB/s" figure is a **compression upper bound,
|
||||||
|
> not a shippable design** — deflate decode does not fit the 68000's frame
|
||||||
|
> budget. See 17.2.
|
||||||
|
|
||||||
Session 1 measured 365 KB/s for naive delta+RLE at 24 fps and wrote
|
Session 1 measured 365 KB/s for naive delta+RLE at 24 fps and wrote
|
||||||
"(~183 KB/s at 12fps)". **That extrapolation is wrong.** Decimating to 12 fps
|
"(~183 KB/s at 12fps)". **That extrapolation is wrong.** Decimating to 12 fps
|
||||||
roughly doubles the per-frame delta, so the *rate* stays nearly flat.
|
roughly doubles the per-frame delta, so the *rate* stays nearly flat.
|
||||||
@@ -267,6 +276,9 @@ should: under a hard ceiling, detail blocks are the first thing sacrificed.
|
|||||||
|
|
||||||
## 11. Codebook size sweep (flat 4x4, for reference)
|
## 11. Codebook size sweep (flat 4x4, for reference)
|
||||||
|
|
||||||
|
> **SUPERSEDED.** The k=1024 result below is an artifact of a rate model that
|
||||||
|
> charged 1 byte for a 10-bit index. k=256 ships. See 14.
|
||||||
|
|
||||||
| block | k | PSNR | loss | key B | changed% | KB/s @12 | codebook RAM |
|
| block | k | PSNR | loss | key B | changed% | KB/s @12 | codebook RAM |
|
||||||
|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
| 4x4 | 256 | 30.46 | 8.39 | 3072 | 52.7 | 28.5 | 8K |
|
| 4x4 | 256 | 30.46 | 8.39 | 3072 | 52.7 | 28.5 | 8K |
|
||||||
@@ -373,6 +385,12 @@ 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
|
## 17. The profiles were set far too low — and entropy coding is a CPU trap
|
||||||
|
|
||||||
|
> **PARTLY SUPERSEDED.** 17.1's diagnosis (the profiles were not derived from
|
||||||
|
> hardware) and 17.2's CPU analysis both stand. But 17 reasoned against a
|
||||||
|
> misread bandwidth of 4 MB/s; the correct figure is **4 Mbps = 488 KB/s**, so
|
||||||
|
> the "ship pixel-exact if SCSI sustains >=800 KB/s" conclusion in 17.5 is
|
||||||
|
> **not available**. See 18 and 21.
|
||||||
|
|
||||||
Prompted by the user asking why the SCSI profile was only 75 KB/s. It should not
|
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.
|
have been. Two separate errors, one of them serious.
|
||||||
|
|
||||||
@@ -453,6 +471,12 @@ SCSI is now waiting on one measurement.
|
|||||||
|
|
||||||
## 18. Peak-to-mean burstiness — the mean was hiding the problem
|
## 18. Peak-to-mean burstiness — the mean was hiding the problem
|
||||||
|
|
||||||
|
> **SUPERSEDED — DO NOT ACT ON THIS SECTION.** The peak-vs-sustained comparison
|
||||||
|
> below is the **wrong test**. With a ring buffer the correct test is cumulative
|
||||||
|
> demand vs cumulative supply, and both profiles pass it with **zero required
|
||||||
|
> prefill**. `scsi` at lam=10 ships. See 21. The per-frame peak numbers
|
||||||
|
> themselves are still valid data; only the conclusion drawn from them is wrong.
|
||||||
|
|
||||||
Prompted by the user clarifying that the bandwidth figure is **4 Mbps = 488 KB/s**,
|
Prompted by the user clarifying that the bandwidth figure is **4 Mbps = 488 KB/s**,
|
||||||
not 4 MB/s. That is ~8x tighter than what 17 was reasoning against, and it
|
not 4 MB/s. That is ~8x tighter than what 17 was reasoning against, and it
|
||||||
changes the answer.
|
changes the answer.
|
||||||
|
|||||||
+56
-38
@@ -19,7 +19,7 @@ as two quality profiles. This is now implemented rather than hypothetical — th
|
|||||||
bitrate ceiling is a build parameter in `tools/encoder/ratectl.py`:
|
bitrate ceiling is a build parameter in `tools/encoder/ratectl.py`:
|
||||||
|
|
||||||
| profile | target | lam | quality (00020 / 00146) | machine |
|
| profile | target | lam | quality (00020 / 00146) | machine |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `sasi` | 110 KB/s | 60 | 36.9 / 29.6 dB | stock 10MHz ACE/EXPERT |
|
| `sasi` | 110 KB/s | 60 | 36.9 / 29.6 dB | stock 10MHz ACE/EXPERT |
|
||||||
| `scsi` | 280 KB/s | 10 | 39.4 / 32.3 dB | Super/XVI, or CZ-6BS1 board |
|
| `scsi` | 280 KB/s | 10 | 39.4 / 32.3 dB | Super/XVI, or CZ-6BS1 board |
|
||||||
|
|
||||||
@@ -54,8 +54,9 @@ rate-distortion curve, not two codecs.
|
|||||||
## What session 2 settled
|
## What session 2 settled
|
||||||
|
|
||||||
1. **The critical-path question is answered.** "Does VQ soften Bluth's linework
|
1. **The critical-path question is answered.** "Does VQ soften Bluth's linework
|
||||||
unacceptably?" — **flat 4x4 k=256 VQ: yes, badly. Hybrid VQ with k=1024: no.**
|
unacceptably?" — **flat 4x4 VQ: yes, badly. The hybrid (SKIP/V1/V4/RAW): no.**
|
||||||
Verified by eye, not just PSNR. See `docs/FINDINGS.md` 9-11.
|
Verified by eye, not just PSNR. See `docs/FINDINGS.md` 9-11 and the two
|
||||||
|
images in `docs/images/`. Both profiles use **k=256**; see item 2b.
|
||||||
2. **Session 1's 12fps bitrate was wrong** (183 KB/s claimed, 340 KB/s measured).
|
2. **Session 1's 12fps bitrate was wrong** (183 KB/s claimed, 340 KB/s measured).
|
||||||
Halving the framerate does not halve the bitrate. FINDINGS 8.
|
Halving the framerate does not halve the bitrate. FINDINGS 8.
|
||||||
2b. **A fourth false-good result was produced and caught this session** — k=1024
|
2b. **A fourth false-good result was produced and caught this session** — k=1024
|
||||||
@@ -119,49 +120,66 @@ notifier subscription in a global; the stack register is `SP` not `A7`;
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## STILL BLOCKED: disk throughput benchmark
|
## Disk throughput benchmark — still blocked, no longer gating
|
||||||
|
|
||||||
Unchanged from session 1 — `IOCS _B_READ` returns -1 uniformly. Full diagnosis
|
`IOCS _B_READ` returns -1 uniformly. Full diagnosis and the four untested
|
||||||
and the four untested hypotheses are in session 1's notes (git history of this
|
hypotheses are in session 1's notes (git history of this file, commit 65112b9);
|
||||||
file, commit 65112b9).
|
the ordered plan for retrying is in **`docs/BENCHMARK.md`**.
|
||||||
|
|
||||||
**This is now CRITICAL-PATH, not optional.** Session 1 dismissed it because
|
**Status changed twice this session — read this rather than the git history.**
|
||||||
"VQ at 30 KB/s is correct whether SASI does 300 or 600 KB/s". That reasoning is
|
It was briefly promoted to critical-path while the working bandwidth figure was
|
||||||
dead: the profiles now sit at 110 and 280 KB/s, close enough to the folklore
|
misread as 4 MB/s. With the correct figure (**4 Mbps = 488 KB/s**) and the
|
||||||
ceilings that the error bars change the product. Specifically —
|
ring-buffer simulation showing **zero required prefill** for both profiles
|
||||||
|
(FINDINGS 21), the design no longer hangs on it. Pixel-exact on SCSI is **not**
|
||||||
|
available at 4 Mbps — it needs 92-97% of the pipe — so there is no longer a
|
||||||
|
"measure it and maybe ship transparent" decision waiting.
|
||||||
|
|
||||||
**If SCSI sustains >=800 KB/s, the correct `scsi` profile is `lam=0`: pixel-exact
|
What the benchmark is still worth doing for:
|
||||||
video, ~450 KB/s, and only 38% of the CPU budget.** Whether this port ships
|
- **Confirming the 4 Mbps figure.** It is user-supplied and its provenance is
|
||||||
transparent or lossy on SCSI is waiting on one measurement.
|
not recorded. Every profile hangs off it.
|
||||||
|
- **Confirming DMA is actually used.** If transfers fall back to PIO the CPU
|
||||||
|
cost rises far above the ~12-15% cycle-steal estimate and CPU becomes the
|
||||||
|
binding constraint. This is the worst plausible outcome and the cheapest to
|
||||||
|
check — do it first.
|
||||||
|
|
||||||
Next move is the untried SCSI path: `-exp1 cz6bs1 -hard disk.chd`.
|
**Do not try to get the bandwidth number out of MAME.** Its SCSI/SASI devices are
|
||||||
|
functional models, not timing-accurate; a KB/s figure from MAME measures the
|
||||||
---
|
emulator's scheduler. `docs/BENCHMARK.md` covers the three-tier approach
|
||||||
|
(MAME validates the path, derivation bounds it, real hardware settles it).
|
||||||
|
|
||||||
## Next steps, in priority order
|
## Next steps, in priority order
|
||||||
|
|
||||||
1. **Full-disc survey** — we have measured 4 clips of 1.2-1.7 s out of 224
|
1. **Full-disc survey.** Only 4 clips of 1.2-1.7 s out of 224 streams have been
|
||||||
streams, and 00146 already runs 23% hotter than 00020. A sustained action
|
measured, and 00146 already runs 23% hotter than 00020. A *sustained* action
|
||||||
sequence is the one thing that could still break the bitrate. This is now
|
sequence is the one thing that could still break the bitrate. Classify menu
|
||||||
the highest-value measurement, ahead of rate control.
|
vs content first (FINDINGS 13) or the averages are diluted by static menus.
|
||||||
2. **Wire rate control into `encode.py`** — no longer a blocker (FINDINGS 21),
|
**Vectorise `_paint` before this run** — it is a Python per-block loop.
|
||||||
but it is what gives a deterministic ceiling over content we have not
|
2. **68000 decoder skeleton.** Parse `DLX1`, expand codebooks to word-per-pixel,
|
||||||
measured, which was the original reason for choosing VQ. Insurance, not a fix.
|
blit SKIP/V1/V4/RAW. Measure real cycles with the existing MAME Lua harness —
|
||||||
3. ~~Entropy-code the payload~~ — **ABANDONED, see FINDINGS 17.2.** Deflate
|
the first time that harness gets used for its actual purpose. Validates the
|
||||||
decode is ~216% of the frame budget on a 68000 and LZ4 is ~54%; there is no
|
38% full-frame blit estimate that the whole CPU budget rests on.
|
||||||
room beside a 38% blit. All bitrates are raw payload. This also demotes the
|
3. **Wire rate control into `encode.py`.** No longer a blocker (FINDINGS 21), but
|
||||||
"247 KB/s lossless" figure in FINDINGS 8 to a compression bound, not a design.
|
it is what gives a deterministic ceiling over content not yet measured, which
|
||||||
3. **68000 decoder skeleton**: parse `DLX1`, expand codebooks to word-per-pixel,
|
was the original reason for choosing VQ. Insurance, not a fix. Pairs with (1).
|
||||||
blit V1/V4/RAW/SKIP. Measure real cycles with the existing MAME Lua harness —
|
4. **Confirm DMA vs PIO in MAME** (see the benchmark section above) — cheap, and
|
||||||
this is the first time the harness gets used for its actual purpose.
|
the only thing that could still move CPU into the binding position.
|
||||||
4. **Full-disc survey** — classify menu vs content first (FINDINGS 13), then
|
|
||||||
measure bitrate across all 224 streams per profile.
|
|
||||||
5. **Resolve the framing question** (FINDINGS 12: crop vs squash vs wide).
|
5. **Resolve the framing question** (FINDINGS 12: crop vs squash vs wide).
|
||||||
6. Unblock the disk benchmark via the SCSI path, then re-set profile bitrates.
|
Needs an eyeball against arcade reference, not a measurement.
|
||||||
7. Import the SNES project's `data/events/` (MIT, cleared) as the scene graph.
|
6. **Import the scene graph.** SNES project `data/events/` (MIT, cleared),
|
||||||
Cross-check against DirkSimple (zlib) which has the same data independently.
|
cross-checked against DirkSimple (zlib) which transcribed the same data
|
||||||
8. ADPCM audio: MSM6258, 15.6kHz mono, 7.8 KB/s — already budgeted in `ratectl`,
|
independently — diff them to catch transcription errors before committing
|
||||||
not yet extracted or encoded.
|
any of it to 68000 tables.
|
||||||
|
7. **ADPCM audio.** MSM6258, 15.6kHz mono, 7.8 KB/s — already budgeted in
|
||||||
|
`ratectl.py`, not yet extracted or encoded.
|
||||||
|
|
||||||
|
### Explicitly abandoned — do not re-propose
|
||||||
|
- ~~Entropy-code the payload.~~ Deflate decode is ~216% of the frame budget on a
|
||||||
|
68000; LZ4 is ~54% with no room beside a 38% blit (FINDINGS 17.2). All bitrates
|
||||||
|
are raw payload. This also demotes the "247 KB/s lossless" figure in FINDINGS 8
|
||||||
|
to a compression upper bound, not a shippable design.
|
||||||
|
- ~~k=1024 codebooks.~~ False-good result from a rate model that charged 1 byte
|
||||||
|
for a 10-bit index (FINDINGS 14). k=256 wins at every matched bitrate.
|
||||||
|
- ~~Flat 4x4 VQ.~~ Rejected by eye (FINDINGS 9).
|
||||||
|
|
||||||
## Not yet started
|
## Not yet started
|
||||||
- Any 68000 player code
|
- Any 68000 player code
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"""What does LOSSLESS (palettised-quality) delta coding actually cost at 12fps?
|
||||||
|
|
||||||
|
Session 1 measured only a hand-rolled row-span+RLE (3.2:1). The 68000 has ~12x
|
||||||
|
CPU headroom, so a real LZ decoder is affordable -- byte copies are what the
|
||||||
|
68000 is good at. This measures the achievable floor at zero extra quality loss.
|
||||||
|
"""
|
||||||
|
import sys, zlib, lzma; sys.path.insert(0,'tools/encoder')
|
||||||
|
import vq, numpy as np
|
||||||
|
S=sys.argv[1]
|
||||||
|
RAW=256*192
|
||||||
|
|
||||||
|
def rle_delta(a,b,gap=4):
|
||||||
|
"""session 1's row-span + RLE, reimplemented for comparison"""
|
||||||
|
t=0
|
||||||
|
for y in range(a.shape[0]):
|
||||||
|
d=np.nonzero(a[y]!=b[y])[0]
|
||||||
|
if not len(d): continue
|
||||||
|
sp=[];s=d[0];p=d[0]
|
||||||
|
for x in d[1:]:
|
||||||
|
if x-p>gap: sp.append((s,p)); s=x
|
||||||
|
p=x
|
||||||
|
sp.append((s,p)); t+=2
|
||||||
|
for s0,e0 in sp:
|
||||||
|
seg=b[y][s0:e0+1]; t+=2; i=0
|
||||||
|
while i<len(seg):
|
||||||
|
r=1
|
||||||
|
while i+r<len(seg) and seg[i+r]==seg[i] and r<127: r+=1
|
||||||
|
t += 2 if r>=3 else r; i+=r
|
||||||
|
return t
|
||||||
|
|
||||||
|
print(f'{"scene":8}{"frames":>7}{"raw KB/s":>9}{"rle":>8}{"xor+zl":>8}{"delta+zl":>9}{"lzma":>8}',flush=True)
|
||||||
|
tot={}
|
||||||
|
for s in ['00010','00020','00146','00181']:
|
||||||
|
rgb=vq.load_frames(f'{S}/fr_{s}')
|
||||||
|
ref,pal=vq.scene_palette(rgb)
|
||||||
|
idx=vq.palettise(rgb,ref)
|
||||||
|
n=len(idx)-1
|
||||||
|
r_rle=sum(rle_delta(idx[i-1],idx[i]) for i in range(1,len(idx)))/n
|
||||||
|
# XOR against previous frame then deflate -- unchanged pixels become 0 runs
|
||||||
|
r_xor=sum(len(zlib.compress((idx[i]^idx[i-1]).tobytes(),9)) for i in range(1,len(idx)))/n
|
||||||
|
# RLE-of-changed-spans payload, then deflate on top
|
||||||
|
def spans(a,b,gap=8):
|
||||||
|
out=bytearray()
|
||||||
|
for y in range(a.shape[0]):
|
||||||
|
d=np.nonzero(a[y]!=b[y])[0]
|
||||||
|
if not len(d): continue
|
||||||
|
sp=[];st=d[0];p=d[0]
|
||||||
|
for x in d[1:]:
|
||||||
|
if x-p>gap: sp.append((st,p)); st=x
|
||||||
|
p=x
|
||||||
|
sp.append((st,p))
|
||||||
|
for s0,e0 in sp:
|
||||||
|
out += bytes([y,s0,e0-s0]) + b[y][s0:e0+1].tobytes()
|
||||||
|
return bytes(out)
|
||||||
|
r_dz=sum(len(zlib.compress(spans(idx[i-1],idx[i]),9)) for i in range(1,len(idx)))/n
|
||||||
|
r_lz=sum(len(lzma.compress(spans(idx[i-1],idx[i]),preset=9))for i in range(1,len(idx)))/n
|
||||||
|
f=12/1024.0
|
||||||
|
print(f'{s:8}{len(idx):7}{RAW*f:9.0f}{r_rle*f:8.0f}{r_xor*f:8.0f}{r_dz*f:9.0f}{r_lz*f:8.0f}',flush=True)
|
||||||
|
for k,v in [('rle',r_rle),('xor',r_xor),('dz',r_dz),('lz',r_lz)]: tot.setdefault(k,[]).append(v)
|
||||||
|
print()
|
||||||
|
for k,v in tot.items():
|
||||||
|
m=np.mean(v)
|
||||||
|
print(f'{k:8} mean {m:7.0f} B/frame {m*12/1024:6.0f} KB/s {m*12*22*60/1048576:5.0f} MB/22min ratio {RAW/m:.1f}:1')
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
"""Fair k=256 vs k=1024 comparison: k=1024 pays 2-byte indices, so the +2.4 dB
|
||||||
|
it showed earlier may be entirely eaten by the doubled payload. Sweep lam and
|
||||||
|
report the rate-distortion CURVE, then compare at matched KB/s."""
|
||||||
|
import sys; sys.path.insert(0,'tools/encoder')
|
||||||
|
import vq_hybrid as H, numpy as np
|
||||||
|
S=sys.argv[1]
|
||||||
|
for s in ['00020','00146']:
|
||||||
|
print(f'=== scene {s} ===',flush=True)
|
||||||
|
for k in [256,1024]:
|
||||||
|
m=H.build(f'{S}/fr_{s}',k1=k,k4=k,iters=16)
|
||||||
|
ib=1 if k<=256 else 2
|
||||||
|
print(f' k={k} ({ib}-byte idx) {"lam":>7}{"PSNR":>8}{"KB/s":>8}{"SKIP":>7}{"V1":>6}{"V4":>6}{"RAW":>6}',flush=True)
|
||||||
|
for lam in [25.,100.,300.,800.,2000.]:
|
||||||
|
e=H.encode(m,lam=lam); r=H.evaluate(m,e)
|
||||||
|
print(f' {"":16}{lam:7.0f}{r["psnr"]:8.2f}{r["kbps"]:8.1f}{r["skip"]:7.1f}{r["v1"]:6.1f}{r["v4"]:6.1f}{r["raw"]:6.1f}',flush=True)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"""How high should the SCSI profile go?
|
||||||
|
|
||||||
|
Two questions the current profiles never answered:
|
||||||
|
1. With the payload deflated, where does the hybrid's lam->0 end actually land?
|
||||||
|
(Un-deflated it is 439 KB/s, which is NOT comparable to the 247 KB/s
|
||||||
|
lossless changed-spans+deflate path.)
|
||||||
|
2. Is there any point shipping lossy VQ on SCSI at all, or does the lossless
|
||||||
|
path dominate once both are entropy-coded?
|
||||||
|
"""
|
||||||
|
import sys, zlib; sys.path.insert(0,'tools/encoder')
|
||||||
|
import vq_hybrid as H, vq as VQ, numpy as np, struct
|
||||||
|
S=sys.argv[1]
|
||||||
|
|
||||||
|
def pack_modes(mode):
|
||||||
|
n=len(mode); out=bytearray((n*2+7)//8)
|
||||||
|
for i,m in enumerate(mode): out[i//4] |= (int(m)&3)<<(6-2*(i%4))
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
def payload(mode,l1,l4g,src,nbx):
|
||||||
|
b=bytearray()
|
||||||
|
for i,mo in enumerate(mode):
|
||||||
|
if mo==1: b.append(int(l1[i]))
|
||||||
|
elif mo==2:
|
||||||
|
for j in range(4): b.append(int(l4g[i][j]))
|
||||||
|
elif mo==3:
|
||||||
|
by,bx=divmod(i,nbx); b+=src[by*4:by*4+4,bx*4:bx*4+4].tobytes()
|
||||||
|
return bytes(b)
|
||||||
|
|
||||||
|
def spans(a,b,gap=8):
|
||||||
|
out=bytearray()
|
||||||
|
for y in range(a.shape[0]):
|
||||||
|
d=np.nonzero(a[y]!=b[y])[0]
|
||||||
|
if not len(d): continue
|
||||||
|
sp=[];st=d[0];p=d[0]
|
||||||
|
for x in d[1:]:
|
||||||
|
if x-p>gap: sp.append((st,p)); st=x
|
||||||
|
p=x
|
||||||
|
sp.append((st,p))
|
||||||
|
for s0,e0 in sp: out+=bytes([y,s0,e0-s0])+b[y][s0:e0+1].tobytes()
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
for s in ['00020','00146']:
|
||||||
|
m=H.build(f'{S}/fr_{s}',k1=256,k4=256,iters=16)
|
||||||
|
pal,idx,W=m['pal'],m['idx'],m['W']; nbx=W//4
|
||||||
|
ceil_db=np.mean([VQ.psnr(o,pal[i]) for o,i in zip(m['rgb'],idx)])
|
||||||
|
# lossless reference, same frames
|
||||||
|
lz=np.mean([len(zlib.compress(spans(idx[i-1],idx[i]),9)) for i in range(1,len(idx))])
|
||||||
|
print(f'=== {s} palette ceiling {ceil_db:.2f} dB ===',flush=True)
|
||||||
|
print(f' LOSSLESS changed-spans+deflate : {lz*12/1024:6.1f} KB/s (pixel-exact)',flush=True)
|
||||||
|
print(f' {"lam":>6}{"PSNR":>8}{"raw KB/s":>10}{"defl KB/s":>11}{"gain":>7}{"RAW%":>7}',flush=True)
|
||||||
|
for lam in [0.,10.,25.,60.,150.,300.,800.]:
|
||||||
|
e=H.encode(m,lam=lam); r=H.evaluate(m,e)
|
||||||
|
tot_r=tot_d=0
|
||||||
|
for f,im in enumerate(idx):
|
||||||
|
B1=H.blocks_of(im,pal,4,4); l1=VQ.assign(B1,m['C1s'])
|
||||||
|
B4=H.blocks_of(im,pal,2,2); l4=VQ.assign(B4,m['C4s'])
|
||||||
|
q=H._group_2x2_into_4x4(np.arange(len(l4)),W); l4g=l4[q].reshape(-1,4)
|
||||||
|
blob=pack_modes(e['modes'][f])+payload(e['modes'][f],l1,l4g,im,nbx)
|
||||||
|
tot_r+=len(blob); tot_d+=len(zlib.compress(blob,9))
|
||||||
|
n=len(idx)
|
||||||
|
rk=tot_r/n*12/1024; dk=tot_d/n*12/1024
|
||||||
|
print(f' {lam:6.0f}{r["psnr"]:8.2f}{rk:10.1f}{dk:11.1f}{rk/dk:7.2f}{r["raw"]:7.1f}',flush=True)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
"""At a 488 KB/s (4 Mbps) ceiling and ~52% mean utilisation, the mean is not the
|
||||||
|
risk -- the peaks are. Measure per-frame peak-to-mean, then check whether the
|
||||||
|
leaky-bucket rate controller actually holds the ceiling."""
|
||||||
|
import sys; sys.path.insert(0,'tools/encoder')
|
||||||
|
import vq_hybrid as H, ratectl as RC, numpy as np
|
||||||
|
S=sys.argv[1]
|
||||||
|
BW=4_000_000/8/1024
|
||||||
|
print(f"ceiling {BW:.0f} KB/s (4 Mbps), audio {RC.AUDIO_KBPS} KB/s\n")
|
||||||
|
print(f'{"scene":8}{"lam":>5}{"mean":>8}{"p90":>8}{"MAX":>8}{"pk/mean":>9}{"MAX % of pipe":>15}',flush=True)
|
||||||
|
for s in ['00010','00020','00146','00181']:
|
||||||
|
m=H.build(f'{S}/fr_{s}',k1=256,k4=256,iters=16)
|
||||||
|
for lam in [60.,10.]:
|
||||||
|
e=H.encode(m,lam=lam)
|
||||||
|
kb=e['sizes']*12/1024 # per-frame instantaneous KB/s
|
||||||
|
tot=kb+RC.AUDIO_KBPS
|
||||||
|
print(f'{s:8}{lam:5.0f}{tot.mean():8.1f}{np.percentile(tot,90):8.1f}'
|
||||||
|
f'{tot.max():8.1f}{tot.max()/tot.mean():9.2f}{tot.max()/BW*100:14.1f}%'
|
||||||
|
+ (' OVER' if tot.max()>BW else ''),flush=True)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"""Ring-buffer simulation. The peak-vs-sustained comparison in FINDINGS 18 was
|
||||||
|
the wrong test: with SD-backed SCSI the fill rate is a CONSTANT, and a burst
|
||||||
|
frame is absorbed by the buffer rather than having to arrive within one frame.
|
||||||
|
|
||||||
|
What actually matters:
|
||||||
|
1. required PREFILL so the buffer never underruns mid-scene
|
||||||
|
2. STALL TOLERANCE at a branch point -- Dragon's Lair seeks between streams,
|
||||||
|
and the buffer drains while the seek completes
|
||||||
|
"""
|
||||||
|
import sys; sys.path.insert(0,'tools/encoder')
|
||||||
|
import vq_hybrid as H, ratectl as RC, numpy as np
|
||||||
|
S=sys.argv[1]; BW=4_000_000/8/1024; FPS=12
|
||||||
|
fill=BW/FPS
|
||||||
|
print(f"fill {BW:.0f} KB/s = {fill:.2f} KB per frame time\n")
|
||||||
|
print(f'{"scene":8}{"lam":>5}{"mean":>8}{"max f":>8}{"prefill":>9}{"stall @0KB":>12}{"stall @256KB":>14}')
|
||||||
|
for s in ['00010','00020','00146','00181']:
|
||||||
|
m=H.build(f'{S}/fr_{s}',k1=256,k4=256,iters=16)
|
||||||
|
for lam in [10.,60.]:
|
||||||
|
e=H.encode(m,lam=lam)
|
||||||
|
kb=e['sizes']/1024 + RC.AUDIO_KBPS/FPS # KB demanded per frame
|
||||||
|
# cumulative deficit: worst shortfall of supply vs demand
|
||||||
|
deficit=np.maximum.accumulate(np.cumsum(kb-fill))
|
||||||
|
prefill=max(0.0,deficit.max())
|
||||||
|
# stall tolerance: with buffer B prefilled, how many frame times can the
|
||||||
|
# fill be zero (seeking) before the buffer empties, at mean drain
|
||||||
|
mean_kb=kb.mean()
|
||||||
|
stall0 = prefill/mean_kb if mean_kb>0 else 0
|
||||||
|
stall256 = (256.0+0.0)/mean_kb
|
||||||
|
print(f'{s:8}{lam:5.0f}{kb.mean()*FPS:8.1f}{kb.max():8.2f}{prefill:9.1f}'
|
||||||
|
f'{stall0:9.1f} fr{stall256:11.1f} fr')
|
||||||
|
print()
|
||||||
|
print("prefill = KB the buffer must hold before playback starts")
|
||||||
|
print("stall = frame times the buffer survives with NO fill (seek/branch)")
|
||||||
|
print(" at 12fps, 1 frame = 83.3 ms")
|
||||||
@@ -11,12 +11,12 @@ bandwidth and it returns the lam that fits, with the headroom accounted for.
|
|||||||
Three things eat the pipe before video gets any:
|
Three things eat the pipe before video gets any:
|
||||||
|
|
||||||
1. AUDIO -- 7.8 KB/s of MSM6258 ADPCM, constant.
|
1. AUDIO -- 7.8 KB/s of MSM6258 ADPCM, constant.
|
||||||
2. PEAK/MEAN -- measured 1.4-1.9x (FINDINGS 18). The disk delivers a
|
2. BUFFERING -- NOT peak/mean. FINDINGS 18 sized against the per-frame
|
||||||
SUSTAINED rate; a frame that overruns is a DROPPED frame.
|
peak; 21 showed that is the wrong test. The disk keeps
|
||||||
Either size for the peak, or rate-control to the mean and
|
filling DURING a frame, so the condition is cumulative
|
||||||
carry a bucket. We do the latter, so we need bucket depth
|
demand vs cumulative supply, which every measured scene
|
||||||
rather than peak headroom -- but until rate control is
|
passes with ZERO required prefill. Peak sizing is kept
|
||||||
actually wired in (it is not), size for the peak.
|
behind --size-for-peak only as a pessimistic bound.
|
||||||
3. DMA CYCLE-STEAL -- the HD63450 steals ~8 clocks per 16-bit word from the
|
3. DMA CYCLE-STEAL -- the HD63450 steals ~8 clocks per 16-bit word from the
|
||||||
68000. At 488 KB/s that is 20% of the CPU, on top of the
|
68000. At 488 KB/s that is 20% of the CPU, on top of the
|
||||||
blit. Bandwidth and CPU are NOT independent budgets.
|
blit. Bandwidth and CPU are NOT independent budgets.
|
||||||
@@ -49,16 +49,23 @@ CURVE = [
|
|||||||
]
|
]
|
||||||
CEILING = {"00020": 39.90, "00146": 35.25}
|
CEILING = {"00020": 39.90, "00146": 35.25}
|
||||||
PEAK_OVER_MEAN = 1.9 # measured worst case, FINDINGS 18
|
PEAK_OVER_MEAN = 1.9 # measured worst case, FINDINGS 18
|
||||||
|
# Per-frame fill at 12fps must cover the worst single frame, else the buffer
|
||||||
|
# has to carry the difference. Measured worst frame is 42.10 KB (00146 lam=10).
|
||||||
|
WORST_FRAME_KB = 42.10
|
||||||
|
|
||||||
|
|
||||||
def dma_steal_pct(kbps):
|
def dma_steal_pct(kbps):
|
||||||
return (kbps * 1024 / 2) * DMA_CLOCKS_PER_WORD / CLK * 100
|
return (kbps * 1024 / 2) * DMA_CLOCKS_PER_WORD / CLK * 100
|
||||||
|
|
||||||
|
|
||||||
def pick(bw_kbps, peak_factor=PEAK_OVER_MEAN, margin=0.85, rate_controlled=False):
|
def pick(bw_kbps, peak_factor=PEAK_OVER_MEAN, margin=0.85, size_for_peak=False):
|
||||||
"""Largest-quality lam whose worst-case demand fits inside bw_kbps."""
|
"""Largest-quality lam whose sustained demand fits inside bw_kbps.
|
||||||
|
|
||||||
|
Default is the BUFFERED test (FINDINGS 21): compare mean demand against the
|
||||||
|
sustained fill. size_for_peak=True restores the pessimistic FINDINGS 18
|
||||||
|
sizing, which is retained only as a bound -- it is not the shipping rule."""
|
||||||
usable = bw_kbps * margin - AUDIO_KBPS
|
usable = bw_kbps * margin - AUDIO_KBPS
|
||||||
factor = 1.0 if rate_controlled else peak_factor
|
factor = peak_factor if size_for_peak else 1.0
|
||||||
allow_mean = usable / factor
|
allow_mean = usable / factor
|
||||||
for lam, k20, d20, k146, d146 in CURVE:
|
for lam, k20, d20, k146, d146 in CURVE:
|
||||||
worst = max(k20, k146)
|
worst = max(k20, k146)
|
||||||
@@ -81,9 +88,9 @@ def main():
|
|||||||
help="fraction of the pipe we allow ourselves (seeks, "
|
help="fraction of the pipe we allow ourselves (seeks, "
|
||||||
"container overhead, and the fact that the bandwidth "
|
"container overhead, and the fact that the bandwidth "
|
||||||
"figure itself is folklore)")
|
"figure itself is folklore)")
|
||||||
ap.add_argument("--rate-controlled", action="store_true",
|
ap.add_argument("--size-for-peak", action="store_true",
|
||||||
help="assume the leaky bucket absorbs peaks (NOT YET TRUE "
|
help="pessimistic FINDINGS 18 sizing (mean * 1.9). Superseded "
|
||||||
"-- ratectl.py is written but not wired into encode.py)")
|
"by 21 -- kept only as a bound, not the shipping rule.")
|
||||||
a = ap.parse_args()
|
a = ap.parse_args()
|
||||||
|
|
||||||
bw = a.bw_kbps if a.bw_kbps else a.bw_mbps * 1_000_000 / 8 / 1024
|
bw = a.bw_kbps if a.bw_kbps else a.bw_mbps * 1_000_000 / 8 / 1024
|
||||||
@@ -91,26 +98,33 @@ def main():
|
|||||||
print(f"bandwidth {src} = {bw:.0f} KB/s sustained")
|
print(f"bandwidth {src} = {bw:.0f} KB/s sustained")
|
||||||
print(f" usable at {a.margin:.0%} margin : {bw*a.margin:.0f} KB/s")
|
print(f" usable at {a.margin:.0%} margin : {bw*a.margin:.0f} KB/s")
|
||||||
print(f" less audio ({AUDIO_KBPS}) : {bw*a.margin-AUDIO_KBPS:.0f} KB/s for video")
|
print(f" less audio ({AUDIO_KBPS}) : {bw*a.margin-AUDIO_KBPS:.0f} KB/s for video")
|
||||||
if not a.rate_controlled:
|
fill_per_frame = bw / FPS
|
||||||
|
if a.size_for_peak:
|
||||||
print(f" less peak/mean {PEAK_OVER_MEAN}x : "
|
print(f" less peak/mean {PEAK_OVER_MEAN}x : "
|
||||||
f"{(bw*a.margin-AUDIO_KBPS)/PEAK_OVER_MEAN:.0f} KB/s mean allowance")
|
f"{(bw*a.margin-AUDIO_KBPS)/PEAK_OVER_MEAN:.0f} KB/s mean allowance"
|
||||||
|
f" [pessimistic, FINDINGS 18 -- superseded]")
|
||||||
else:
|
else:
|
||||||
print(" peaks absorbed by rate control (bucket depth must be validated)")
|
print(f" fill per frame time : {fill_per_frame:.2f} KB "
|
||||||
|
f"(worst measured frame {WORST_FRAME_KB:.2f} KB"
|
||||||
|
f"{' -- COVERED' if fill_per_frame >= WORST_FRAME_KB else ' -- needs buffer'})")
|
||||||
|
|
||||||
r = pick(bw, margin=a.margin, rate_controlled=a.rate_controlled)
|
r = pick(bw, margin=a.margin, size_for_peak=a.size_for_peak)
|
||||||
if r is None:
|
if r is None:
|
||||||
print("\n NO PROFILE FITS -- even lam=800 overruns. Lower the framerate,")
|
print("\n NO PROFILE FITS -- even lam=800 overruns. Lower the framerate,")
|
||||||
print(" the resolution, or get more bandwidth.")
|
print(" the resolution, or get more bandwidth.")
|
||||||
return
|
return
|
||||||
steal = dma_steal_pct(r["peak_kbps"])
|
steal = dma_steal_pct(r["mean_kbps"])
|
||||||
print(f"\n -> {a.name}: lam={r['lam']}, {r['mean_kbps']:.0f} KB/s mean, "
|
print(f"\n -> {a.name}: lam={r['lam']}, {r['mean_kbps']:.0f} KB/s mean")
|
||||||
f"{r['peak_kbps']:.0f} KB/s peak")
|
|
||||||
print(f" quality 00020 {r['psnr20']:.2f} dB (-{r['loss20']:.2f} from ceiling)")
|
print(f" quality 00020 {r['psnr20']:.2f} dB (-{r['loss20']:.2f} from ceiling)")
|
||||||
print(f" 00146 {r['psnr146']:.2f} dB (-{r['loss146']:.2f} from ceiling)")
|
print(f" 00146 {r['psnr146']:.2f} dB (-{r['loss146']:.2f} from ceiling)")
|
||||||
print(f" CPU blit {BLIT_FULL_FRAME_PCT:.0f}% + DMA steal {steal:.1f}% "
|
print(f" CPU blit {BLIT_FULL_FRAME_PCT:.0f}% + DMA steal {steal:.1f}% "
|
||||||
f"= {BLIT_FULL_FRAME_PCT+steal:.0f}% of the frame budget")
|
f"= {BLIT_FULL_FRAME_PCT+steal:.0f}% of the frame budget")
|
||||||
if BLIT_FULL_FRAME_PCT + steal > 85:
|
if BLIT_FULL_FRAME_PCT + steal > 85:
|
||||||
print(" WARNING: CPU is now the binding constraint, not the bus.")
|
print(" WARNING: CPU is now the binding constraint, not the bus.")
|
||||||
|
if not a.size_for_peak and fill_per_frame < WORST_FRAME_KB:
|
||||||
|
short = WORST_FRAME_KB - fill_per_frame
|
||||||
|
print(f" note: worst frame exceeds one frame-time of fill by "
|
||||||
|
f"{short:.2f} KB -- buffer must carry it (2MB RAM, non-issue).")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user