Align the container to the disc, and find the decoder-free packed player fits
Two sessions, unrecorded until now, committed together because their edits share files and cannot be split cleanly after the fact. Session 28 (FINDINGS 60): the container is DLX5 -- every record sector-aligned, 120/120 starting on a boundary where 3/120 did, +0.48% on the wire and zero clocks -- and the ring's release rounds to RECALN so no pad is stranded. Two encoder levers measured and refused: `--spans all` buys +0.19 dB for +67% of the wire, and joint span/lam selection emits byte-identical containers because `lam` never leaves its floor on any of 120 frames. Session 29 (FINDINGS 61): the packed full-frame blit is 27.3% of a 12 fps frame, a channel fills GVRAM in buffer mode off the disc with the CPU halted, and it walks the 1,024 B line stride itself through array chaining. At the 9 clk/B dual-address floor the codec is 110.4% of a frame and a decoder-free packed literal player is 55.2%, at +4.89 dB -- 2.75 dB past a ceiling the codec's scene-wide palette cannot cross. Encoder work is parked; the codec is kept and not built on. check.sh is ALL GREEN before and after, plus one new stage that gates the ORDER of the measured paint costs rather than their values. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
#!/usr/bin/env python3
|
||||
"""The DECODER-FREE PACKED player, priced against the measured cost model.
|
||||
|
||||
python3 tools/analysis/29_packed_player.py [container.dlx] [--kbps R]
|
||||
|
||||
THE QUESTION, and why it is being asked again. FINDINGS 44.7 removed the codec
|
||||
and asked what a player that just puts literal frames on screen would cost. It
|
||||
answered "it fits the clocks and dies on the medium": 1,152 KB/s and 1.61 GB,
|
||||
because 256-colour GVRAM's default write path throws away the high byte of every
|
||||
word and a picture byte therefore costs two disc bytes. 46.5/47.1 then found
|
||||
the off switch -- CRTC R20 bit 11 -- and 47.2 built the layout and rendered it
|
||||
pixel-exactly on both emulators at 1.0 B/pixel. 47.5 re-derived the budget on
|
||||
that and withdrew 44.7's conclusion CONDITIONALLY.
|
||||
|
||||
Everything in 47.5 is arithmetic over a cost model that has since been REPLACED.
|
||||
When it was written the transport was an unmeasured `c`; sessions 25b-28 put the
|
||||
transport on the 68000 and measured it (58.2: PIO is 87.28 clk/B), put it on the
|
||||
DMAC and bounded it (59.2: this machine can run dual-address only, and a
|
||||
dual-address byte has a 9 clk/B FLOOR), and re-derived what a frame can afford
|
||||
(59.7/60.7: 6.69 clk/B on the gate container). 47.6.1 also filed the CPU paint
|
||||
cost as an ASSUMPTION -- "the `movem` shape of the packed writes is an
|
||||
assumption", no clock in 47.5 measured.
|
||||
|
||||
So this tool re-asks 44.7's question with:
|
||||
|
||||
* the paint MEASURED, not assumed -- tools/bench/blit.s V8 is V1 with 128
|
||||
words a row instead of 256, and tools/bench/blit.lua times it next to V1,
|
||||
V2 and V3 in the same run, so the packed number is quoted against a
|
||||
variant whose value (53.6%) is a session-9 result that has not moved;
|
||||
* the transport swept over the SAME `W` ladder 15_bus_occupancy.py uses,
|
||||
every rung of it sourced or measured (buscost.py);
|
||||
* the audio DMA charged, at the rate the IPL ROM's own channel-3 setup
|
||||
implies (21_iplrom_dmac.py) -- 60.x's rule that a budget debits I/O;
|
||||
* and the wire and the volume stated for each, because 44.7's answer was
|
||||
never about clocks.
|
||||
|
||||
WHAT IT DOES NOT DO. It does not settle 47.4 -- whether buffer mode BLANKS the
|
||||
graphics layer, which MAME asserts and px68k is silent about (48.1), and which
|
||||
needs a real board. It PRICES both branches instead, and the blanking section
|
||||
is where the measured paint earns its keep: the black interval is the paint, and
|
||||
until now the paint was a range read off an unpacked measurement ("~27% to ~54%",
|
||||
48.3) rather than a number.
|
||||
"""
|
||||
import sys, os, re, argparse, csv
|
||||
sys.path.insert(0, "tools/encoder")
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import numpy as np
|
||||
from dlx import DLX
|
||||
import buscost as B
|
||||
|
||||
CPUHZ = 10e6 # stock X68000, MAME 0.277 x68k.cpp:1133
|
||||
GAME_S = 22.8 * 60 # the full-disc survey's runtime (ROADMAP C1)
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("container", nargs="?", default="tmp/rc_fr_singe_scsi_span.dlx",
|
||||
help="the CODEC baseline this is compared against")
|
||||
ap.add_argument("--csv", default="tmp/c68k_frames.csv",
|
||||
help="per-frame C68K measurement of that container")
|
||||
ap.add_argument("--blit-log", default="tmp/blit_v8.log",
|
||||
help="tools/bench/blit.lua's log -- where the MEASURED paint "
|
||||
"costs are read from. No defaults are compiled in.")
|
||||
ap.add_argument("--kbps", type=float, default=None,
|
||||
help="a delivery rate to score the wire against. OPTIONAL and "
|
||||
"there is no default (FINDINGS 50).")
|
||||
a = ap.parse_args()
|
||||
|
||||
# --- the measured paint, read out of the run's own log ---------------------
|
||||
# NOT transcribed into this file. A constant copied out of a log is a constant
|
||||
# that stops tracking the log, and this project has been caught by a stale
|
||||
# number twice (60.8). If the log is not there the tool refuses rather than
|
||||
# substituting a plausible one.
|
||||
if not os.path.exists(a.blit_log):
|
||||
sys.exit(f"missing {a.blit_log} -- run tools/bench/blit.lua first:\n"
|
||||
f" cd tmp && mame x68000 -bios ipl10 -ramsize 2M -video soft "
|
||||
f"-window -sound none -nothrottle -plugins \\\n"
|
||||
f" -autoboot_script ../tools/bench/blit.lua -seconds_to_run 60")
|
||||
blit = {}
|
||||
for line in open(a.blit_log, errors="replace"):
|
||||
m = re.search(r"V(\d+)\s+(\d+) cyc", line)
|
||||
if m:
|
||||
blit[int(m.group(1))] = int(m.group(2))
|
||||
for v in (1, 2, 3, 4, 8, 9, 10):
|
||||
if v not in blit:
|
||||
sys.exit(f"{a.blit_log} has no V{v} result -- the summary is incomplete, "
|
||||
f"so the run did not finish and nothing here can be quoted.")
|
||||
|
||||
# --- the codec baseline: the container, and its MEASURED decode ------------
|
||||
d = DLX(a.container)
|
||||
FPS = d.fps
|
||||
FRAME_CLK = CPUHZ / FPS
|
||||
meas = {}
|
||||
if os.path.exists(a.csv):
|
||||
for r in csv.DictReader(open(a.csv)):
|
||||
meas[int(r["frame"])] = int(r["cycles"])
|
||||
if not meas:
|
||||
sys.exit(f"missing {a.csv} -- the codec row's decode term is MEASURED and "
|
||||
f"there is no derived stand-in for it.")
|
||||
NF = max(meas) + 1
|
||||
codec_decode = np.mean([meas[f] for f in range(NF)])
|
||||
codec_bpf = sum(d.record_lengths()[:NF]) / NF # the PADDED record (60.7)
|
||||
|
||||
# --- geometry, which is where the decoder-free rows come from -------------
|
||||
W_PX, H_PX = d.W, d.H
|
||||
NPX = W_PX * H_PX
|
||||
UNPACKED_BPF = NPX * 2 # one pixel per word, high byte discarded
|
||||
PACKED_BPF = NPX * 1 # R20 bit 11 + page scroll (47.2, measured)
|
||||
|
||||
aud_bpf = B.ADPCM_BYTES_PER_S / FPS
|
||||
AUD_CLK = aud_bpf * B.ADPCM_CLK_BYTE_BEST # best case, so every row is
|
||||
# the optimistic end
|
||||
# A device->GVRAM channel cannot walk a 1024-byte line stride inside one
|
||||
# transfer: it writes a contiguous run. 192 rows therefore need 192 array-chain
|
||||
# entries -- and SESSION 29 RAN THAT, off the disc, through src/player/dma.i's
|
||||
# DM_BARV/DM_BTCV: eight rows at the 1024 B stride landed from ONE channel start
|
||||
# with the CPU halted throughout (tools/bench/dma_run.sh, `[chain]`). So the
|
||||
# MECHANISM is demonstrated and the CPU does not restart the channel per row.
|
||||
# The COST is still datasheet arithmetic -- 36 clocks an entry, Fig 4-25 sheet 1,
|
||||
# buscost.DMA_CHAIN_CLK -- because MAME's DMAC runs on wall-clock attotimes and
|
||||
# cannot be asked what anything costs (42.5).
|
||||
CHAIN_CLK = H_PX * B.DMA_CHAIN_CLK
|
||||
|
||||
print(f"""{a.container}: {NF} frames of {W_PX}x{H_PX} at {FPS:g} fps
|
||||
frame slot on a 10 MHz 68000: {FRAME_CLK:,.0f} clocks
|
||||
paint costs MEASURED by tools/bench/blit.lua, read from {a.blit_log}:
|
||||
V1 unpacked movem blit {blit[1]:>9,} clk {100*blit[1]/FRAME_CLK:5.1f}% (96 KB read + 96 KB write)
|
||||
V2 byte-source expansion {blit[2]:>9,} clk {100*blit[2]/FRAME_CLK:5.1f}% (48 KB read + 96 KB write)
|
||||
V3 write-only floor {blit[3]:>9,} clk {100*blit[3]/FRAME_CLK:5.1f}% (no source read at all)
|
||||
V8 PACKED movem blit {blit[8]:>9,} clk {100*blit[8]/FRAME_CLK:5.1f}% (48 KB read + 48 KB write)
|
||||
|
||||
V8 is {100*blit[8]/blit[1]:.1f}% of V1 and {100*blit[8]/blit[3]:.1f}% of V3 -- so PACKED PAINT COSTS WHAT THE
|
||||
UNPACKED PATH PAYS TO WRITE ALONE, with its source read thrown in free.
|
||||
It is not exactly half of V1 because the 192-row loop does not halve with
|
||||
the words: per word V1 is {blit[1]/(NPX):.3f} clk and V8 is {blit[8]/(NPX//2):.3f}.""")
|
||||
|
||||
# --- the architectures ----------------------------------------------------
|
||||
# Each is (label, bytes on the wire per frame, CPU clocks per frame that are
|
||||
# NOT the transport, and whether the transport lands in GVRAM or in RAM).
|
||||
ARCH = [
|
||||
("CODEC, CPU-decoded (the shipping design)", codec_bpf, codec_decode, "ring"),
|
||||
("free / DMAC device->GVRAM / unpacked", UNPACKED_BPF, CHAIN_CLK, "gvram"),
|
||||
("free / DMAC device->GVRAM / PACKED", PACKED_BPF, CHAIN_CLK, "gvram"),
|
||||
("free / CPU-painted / unpacked, 2 B/px wire", UNPACKED_BPF, blit[1], "ring"),
|
||||
("free / CPU-painted / unpacked, 1 B/px wire", PACKED_BPF, blit[2], "ring"),
|
||||
("free / CPU-painted / PACKED", PACKED_BPF, blit[8], "ring"),
|
||||
]
|
||||
|
||||
LADDER = [
|
||||
(5.0, "single address, held -- needs a request line (B3)"),
|
||||
(9.0, "dual address, held -- the FLOOR (59.2/59.7)"),
|
||||
(12.0, "single address, arbitrated"),
|
||||
(16.0, "what the ROM programs for SASI, best"),
|
||||
(19.0, "what the ROM programs for SASI, worst"),
|
||||
(87.28, "PIO -- MEASURED, 58.2"),
|
||||
]
|
||||
|
||||
print("\n" + "=" * 78)
|
||||
print("WHAT EACH ARCHITECTURE COSTS A FRAME, over the transport ladder\n")
|
||||
print(f" audio DMA is charged in every row at {AUD_CLK:,.0f} clk "
|
||||
f"({100*AUD_CLK/FRAME_CLK:.2f}%), best case.\n")
|
||||
hdr = f" {'architecture':<44}{'B/frame':>9}" + "".join(f"{f'W={w:g}':>9}" for w, _ in LADDER)
|
||||
print(hdr)
|
||||
print(" " + "-" * (len(hdr) - 2))
|
||||
for label, bpf, cpu, dest in ARCH:
|
||||
cells = []
|
||||
for w, _ in LADDER:
|
||||
tot = bpf * w + cpu + AUD_CLK
|
||||
pct = 100 * tot / FRAME_CLK
|
||||
cells.append(f"{pct:>8.1f}%" if pct < 1000 else f"{pct:>8.0f}%")
|
||||
print(f" {label:<44}{bpf:>9,.0f}" + "".join(cells))
|
||||
print(f"""
|
||||
100% is the frame deadline. Every cell is CPU work plus transport plus
|
||||
best-case audio; none of them overlap, because the 68000 has no cache and a
|
||||
two-word prefetch queue that empties at once (buscost.DMA_OVERLAPS = False).
|
||||
|
||||
THE TWO ROWS THAT MATTER ARE THE FLOOR COLUMN, W=9, because 59.2 found that
|
||||
the only configurations this machine can be shown to run are dual-address,
|
||||
and a dual-address byte is a 4-clock read of the device plus a 5-clock write
|
||||
to memory. Every column left of it is a hardware fact nobody here has.""")
|
||||
|
||||
# --- the wire, which is what 44.7 actually died on ------------------------
|
||||
print("\n" + "=" * 78)
|
||||
print("THE WIRE AND THE MEDIUM -- 44.7's real objection\n")
|
||||
print(f" {'architecture':<44}{'B/frame':>9}{'KB/s':>9}{'GB for 22.8 min':>18}")
|
||||
print(" " + "-" * 78)
|
||||
seen = set()
|
||||
for label, bpf, cpu, dest in ARCH:
|
||||
kbs = bpf * FPS / 1024
|
||||
gb = bpf * FPS * GAME_S / 1e9
|
||||
print(f" {label:<44}{bpf:>9,.0f}{kbs:>9.1f}{gb:>18.2f}")
|
||||
print(f"""
|
||||
The codec row is the gate container, which is deliberately the heaviest thing
|
||||
the encoder emits (59.7). The default `need` recipe is 267.9 KB/s and E7's
|
||||
byte target at the 9 clk/B floor is 327 KB/s (60.7).
|
||||
|
||||
SO THE PACKED DECODER-FREE PLAYER ASKS FOR {PACKED_BPF*FPS/1024:.0f} KB/s -- {PACKED_BPF*FPS/1024/327:.2f}x E7's target and
|
||||
{PACKED_BPF*FPS/1024/(codec_bpf*FPS/1024):.2f}x the gate container -- and it asks for it AT A FIXED RATE. A codec's
|
||||
bitrate is a lever; a literal frame's is geometry, and there is no scene in
|
||||
the picture that costs less than another.""")
|
||||
if a.kbps:
|
||||
R = a.kbps * 1024
|
||||
print(f"\n against a supplied {a.kbps:g} KB/s:")
|
||||
for label, bpf, cpu, dest in ARCH:
|
||||
need = bpf * FPS
|
||||
print(f" {label:<44}{'FITS' if need <= R else 'SHORT BY '}"
|
||||
f"{'' if need <= R else f'{(need-R)/1024:.0f} KB/s'}"
|
||||
f" ({need/1024:.0f} KB/s wanted)")
|
||||
|
||||
# --- 47.4's two branches, priced -----------------------------------------
|
||||
print("\n" + "=" * 78)
|
||||
print("IF BUFFER MODE BLANKS THE LAYER (47.4 / 48, MAME's reading)\n")
|
||||
print(""" R20 bit 11 only has to be SET across the GVRAM writes, so the black
|
||||
interval is the paint and not the frame -- and which paint depends on where
|
||||
the transport lands. That asymmetry has not been stated before:\n""")
|
||||
print(f" {'architecture':<44}{'black interval':>16} {'set for':<14}")
|
||||
print(" " + "-" * 78)
|
||||
for label, bpf, cpu, dest in ARCH[1:]:
|
||||
if dest == "gvram":
|
||||
# the channel writes GVRAM, so the bit is set for the whole transfer
|
||||
# DMA rungs only: a PIO transport is not a channel writing GVRAM, so
|
||||
# 87.28 has no meaning in a device->GVRAM row.
|
||||
rows = [bpf * w + CHAIN_CLK for w, _ in LADDER if w < 20]
|
||||
span = f"{100*min(rows)/FRAME_CLK:.0f}%..{100*max(rows)/FRAME_CLK:.0f}%"
|
||||
note = "the whole DMA"
|
||||
else:
|
||||
span = f"{100*cpu/FRAME_CLK:.1f}%"
|
||||
note = "the blit only"
|
||||
print(f" {label:<44}{span:>16} {note:<14}")
|
||||
print(f"""
|
||||
THE CPU-PAINTED PACKED PATH HAS THE SMALLEST BLACK WINDOW OF ANY OF THEM --
|
||||
{100*blit[8]/FRAME_CLK:.1f}% -- because its transport lands in RAM, where bit 11 is irrelevant,
|
||||
and only the {blit[8]:,}-clock blit needs the bit set. The DMAC-direct path,
|
||||
which is cheaper in clocks at every rung of the ladder, is the one that must
|
||||
hold the bit across its whole transfer. Under MAME's reading the cheap
|
||||
architecture is the dark one.
|
||||
|
||||
Both are a strobe at the frame rate over the whole picture, and the packed
|
||||
layout has no page to flip to: both 256-colour pages carry picture, which is
|
||||
the entire point of it (48.3). {100*blit[8]/FRAME_CLK:.1f}% black at 12 Hz is not a tear.
|
||||
|
||||
IF PX68K IS RIGHT AND IT DOES NOT BLANK, every number above stands as
|
||||
written. Neither emulator is authority and 48.1 is why the prior leans
|
||||
MAME's way: MAME asserts the semantic twice and deliberately, px68k's
|
||||
display path never reads the bit at all. That is an assertion against a
|
||||
silence, not a tie, and it is settled by a board and the two-line probe in
|
||||
tools/bench/probe_bit11_blank.lua.""")
|
||||
|
||||
# --- 47.6.4: does the CODEC survive the packed layout? --------------------
|
||||
# Open since session 16 and never touched: "under the packed layout a word spans
|
||||
# two columns 128 apart. Whether the existing codec survives that is untouched."
|
||||
# There are exactly two ways it could, and blit.s V9 and V10 are them.
|
||||
sk_blocks = sk_tot = pair_sk = pair_tot = 0
|
||||
for f in range(NF):
|
||||
m = d.modes(f).reshape(d.nby, d.nbx)
|
||||
L, R = m[:, :d.nbx // 2], m[:, d.nbx // 2:]
|
||||
sk_blocks += int((m == 0).sum()); sk_tot += m.size
|
||||
pair_sk += int(((L == 0) & (R == 0)).sum()); pair_tot += L.size
|
||||
paint_now = 1 - sk_blocks / sk_tot
|
||||
paint_pair = 1 - pair_sk / pair_tot
|
||||
|
||||
print("\n" + "=" * 78)
|
||||
print("CAN THE CODEC BE PACKED TOO? -- 47.6.4, open since session 16\n")
|
||||
print(f""" A 4x4 block owns four bytes at STRIDE 2 under the packed layout, because
|
||||
the high bytes of its four words belong to the block 128 columns away. So a
|
||||
block decoder has two options and neither is free:
|
||||
|
||||
{'V4 block order, UNPACKED (the shipping shape)':<52}{blit[4]:>9,} clk {100*blit[4]/FRAME_CLK:5.1f}%
|
||||
{'V9 block order, PACKED, 16 move.b at stride 2':<52}{blit[9]:>9,} clk {100*blit[9]/FRAME_CLK:5.1f}%
|
||||
{'V10 block order, PACKED, blocks PAIRED (movem back)':<52}{blit[10]:>9,} clk {100*blit[10]/FRAME_CLK:5.1f}%
|
||||
|
||||
V9 IS {100*blit[9]/blit[4]-100:.0f}% DEARER THAN V4, not cheaper. Packing buys a block decoder
|
||||
nothing on the wire either -- a codeword is already one byte a pixel -- so
|
||||
that route buys NOTHING and costs {blit[9]-blit[4]:,} clocks a frame to buy it.
|
||||
|
||||
V10 halves the paint, and pays for it in the mode map. A pair skips only if
|
||||
BOTH its blocks skip, and on this container:
|
||||
|
||||
SKIP blocks now {100*sk_blocks/sk_tot:5.1f}% painted now {100*paint_now:5.1f}%
|
||||
SKIP block PAIRS {100*pair_sk/pair_tot:5.1f}% painted paired {100*paint_pair:5.1f}%
|
||||
|
||||
So pairing paints {paint_pair/paint_now:.2f}x as many blocks for {blit[10]/blit[4]:.2f}x the paint per block --
|
||||
{100*(paint_pair/paint_now)*(blit[10]/blit[4])-100:+.0f}% on the clock, and about {100*(paint_pair/paint_now-1):+.0f}% on the BYTES, because a coded
|
||||
block is bytes in the container whether its half of the pair changed or not.
|
||||
E7 needs the bytes DOWN {100*(codec_bpf*FPS/1024)/327-100:.0f}%.
|
||||
|
||||
SO PACKING BELONGS TO THE LITERAL PLAYER AND ONLY TO IT. 47.6.4 is closed:
|
||||
the packed layout is not an upgrade the existing codec can take, it is the
|
||||
thing you get INSTEAD of the codec.""")
|
||||
|
||||
# --- the palette, which is where the literal player stops being a compromise --
|
||||
# 46.3 measured these while pricing the TEXT PLANE and the 256-colour rows were
|
||||
# only there for scale. They answer a question nobody put to them: a literal
|
||||
# player has no codebooks, so it is not tied to a scene-wide palette the way the
|
||||
# codec is (vq.scene_palette exists BECAUSE codewords are indices into it), and
|
||||
# per-frame palettes become legal. Re-run 18_text_plane_16col.py to reproduce.
|
||||
PSNR_SHIPPED = 29.19 # docs/STATUS.md, --spans all, c=5, 496.7 KB/s
|
||||
PSNR_SCENE_256 = 31.33 # 18_text_plane_16col.py, tmp/fr_singe, 120 frames
|
||||
PSNR_FRAME_256 = 34.08 # the same window, per-frame palettes
|
||||
PAL_BYTES = 512 # 256 entries x 1 word
|
||||
|
||||
pal_bpf = PACKED_BPF + PAL_BYTES
|
||||
# The palette write, DERIVED from a MEASURED per-word constant: V8 moves a word
|
||||
# into GVRAM for blit[8]/(NPX//2) clocks and the palette is 256 consecutive
|
||||
# words at $E82000 in the same movem shape.
|
||||
pal_clk = 256 * blit[8] / (NPX // 2)
|
||||
|
||||
print("\n" + "=" * 78)
|
||||
print("AND THE PICTURE IS BETTER, WHICH NOBODY HAD ASKED\n")
|
||||
print(f""" PSNR against the 24-bit source, 18_text_plane_16col.py over the same
|
||||
120-frame window the whole tree is measured on:
|
||||
|
||||
{'shipping container (the codec, as it ships)':<48}{PSNR_SHIPPED:6.2f} dB
|
||||
{'256 colours, SCENE palette -- the codec CEILING':<48}{PSNR_SCENE_256:6.2f} dB
|
||||
{'256 colours, PER-FRAME palette':<48}{PSNR_FRAME_256:6.2f} dB
|
||||
|
||||
THE MIDDLE ROW IS A CEILING AND NOT A RIVAL. Every codeword the codec emits
|
||||
is an index INTO the scene palette, so no amount of bitrate takes it past
|
||||
{PSNR_SCENE_256:.2f} dB; it spends {codec_bpf*FPS/1024:.0f} KB/s to get within {PSNR_SCENE_256-PSNR_SHIPPED:.2f} dB of it.
|
||||
|
||||
A LITERAL FRAME HAS NO CODEBOOKS, so the scene palette is not forced on it,
|
||||
and the bottom row is what it simply IS -- {PSNR_FRAME_256-PSNR_SHIPPED:+.2f} dB on the shipping
|
||||
container and {PSNR_FRAME_256-PSNR_SCENE_256:+.2f} dB past the ceiling the codec cannot cross.
|
||||
|
||||
WHAT THE PER-FRAME PALETTE COSTS:
|
||||
on the wire {PAL_BYTES} B a frame -> {pal_bpf:,} B, {pal_bpf*FPS/1024:.1f} KB/s (+{100*PAL_BYTES/PACKED_BPF:.1f}%)
|
||||
in clocks ~{pal_clk:,.0f} ({100*pal_clk/FRAME_CLK:.2f}% of a frame) if the CPU writes it, DERIVED
|
||||
from V8's measured {blit[8]/(NPX//2):.3f} clk/word in the same movem shape
|
||||
in colours 254, not 256: the packed layout spends index 0 on the
|
||||
transparency key and puts black at 255 (47.2,
|
||||
prep_frame.py --pack-transparent), against --reserve-black's
|
||||
one entry. The tree has already measured a reserved entry at
|
||||
0.04 dB (60.3), so this is noise against {PSNR_FRAME_256-PSNR_SHIPPED:+.2f}.
|
||||
|
||||
NOT SETTLED, and it is the obvious next probe: whether a DMA CHANNEL can write
|
||||
the palette registers at $E82000, which would make the palette a 193rd chain
|
||||
entry and cost the CPU nothing at all. Untested; 61.2 only ever pointed a
|
||||
channel at GVRAM.
|
||||
|
||||
AND THE PSNR FIGURES ARE PIL's MEDIANCUT, not this project's own palette
|
||||
builder (vq.scene_palette / H.build). The DIRECTION is measured and the
|
||||
magnitude is about right; if the packed player gets built, re-derive the
|
||||
per-frame number against the builder that will actually ship it.""")
|
||||
|
||||
# --- the answer ----------------------------------------------------------
|
||||
w9 = 9.0
|
||||
free_packed_dma = PACKED_BPF * w9 + CHAIN_CLK + AUD_CLK
|
||||
free_packed_cpu = PACKED_BPF * w9 + blit[8] + AUD_CLK
|
||||
codec_9 = codec_bpf * w9 + codec_decode + AUD_CLK
|
||||
print("\n" + "=" * 78)
|
||||
print(f"""THE ANSWER, AT THE ONE RUNG THIS MACHINE CAN BE SHOWN TO RUN (W=9)
|
||||
|
||||
CODEC, gate container {100*codec_9/FRAME_CLK:6.1f}% of the frame -- DOES NOT FIT
|
||||
free / DMAC->GVRAM / PACKED {100*free_packed_dma/FRAME_CLK:6.1f}% -- FITS, with {100-100*free_packed_dma/FRAME_CLK:.0f}% to spare
|
||||
free / CPU-painted / PACKED {100*free_packed_cpu/FRAME_CLK:6.1f}% -- FITS, with {100-100*free_packed_cpu/FRAME_CLK:.0f}% to spare
|
||||
|
||||
THE DECODER-FREE PACKED PLAYER FITS THE CLOCK BUDGET THAT THE CODEC MISSES.
|
||||
That is not a small correction to 47.5, it is the reverse of the reason the
|
||||
codec exists. 44.7 said it in advance and on a different cost model: "the
|
||||
codec is not there to save CPU -- it is there to save the wire." The
|
||||
measured model agrees, and now says the CPU side is not merely affordable
|
||||
but strictly cheaper WITHOUT the codec: at the floor, decoding {codec_bpf:,.0f} bytes
|
||||
costs {100*(codec_bpf*w9+codec_decode)/FRAME_CLK:.0f}% of a frame and NOT decoding {PACKED_BPF:,} costs {100*(PACKED_BPF*w9+blit[8])/FRAME_CLK:.0f}%.
|
||||
|
||||
SO THE QUESTION IS ENTIRELY A MEDIUM QUESTION, and it has two halves:
|
||||
|
||||
1. {PACKED_BPF*FPS/1024:.0f} KB/s SUSTAINED, with no lever to pull. ROADMAP B1 is
|
||||
unmeasured; the 0.7-1.7 MB/s usually quoted for BlueSCSI on an X68000
|
||||
is folklore with no published benchmark behind it. {PACKED_BPF*FPS/1024:.0f} KB/s sits
|
||||
inside that range, which is exactly why the range has to be measured
|
||||
rather than cited. A codec at 327 KB/s survives a slower answer; a
|
||||
literal frame does not degrade, it drops.
|
||||
2. {PACKED_BPF*FPS*GAME_S/1e9:.2f} GB for the whole game, against the codec's {codec_bpf*FPS*GAME_S/1e9:.2f} GB at the gate
|
||||
recipe and ~{327*1024*GAME_S/1e9:.2f} GB at E7's target. That is a packaging fact (C3),
|
||||
not a performance one.
|
||||
|
||||
AND 47.4 STILL SITS OVER ALL OF IT. Everything above assumes the layer is
|
||||
visible while it is written. If it is not, the packed player is a {100*blit[8]/FRAME_CLK:.0f}% duty
|
||||
strobe at best and there is no version of it that is merely expensive.""")
|
||||
Reference in New Issue
Block a user