Files
prosolis 1be428c270 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
2026-08-25 06:54:27 -07:00

183 lines
7.2 KiB
Lua

-- Time the full-frame GVRAM blit (tools/bench/blit.s) on the emulated 68000.
--
-- This is the first measurement in the project where 68000 instructions, not
-- Lua, put the pixels on screen. It validates (or kills) the 38% full-frame
-- blit estimate the whole CPU budget rests on.
--
-- MEASUREMENT SCOPE. MAME's gvram_w/gvram_r (x68k_crtc.cpp:501,595) contain
-- no timing whatsoever -- no wait states, no icount adjustment. So what is
-- measured here is pure 68000 instruction cycles against zero-wait-state
-- memory. Real X68000 GVRAM stalls the CPU; every number below is therefore
-- a LOWER BOUND, not a prediction. Interrupts are masked (SR=$2700) so the
-- IPL's timer and VBL handlers cannot steal cycles into the measurement.
--
-- Timing resolution is one video frame (1/56.69 s = 17.64 ms -- MAME's, not
-- the hardware's 55.46; see crtc_mode.lua), because Lua
-- gets no cycle counter -- luaengine.cpp exposes machine.time and nothing
-- from device_execute_interface. Each variant therefore loops enough times
-- to run ~4 emulated seconds, putting the granularity error near 0.4%.
M = manager.machine
SP = M.devices[":maincpu"].spaces["program"]
local function load_mode()
for _,p in ipairs{"../tools/bench/crtc_mode.lua","tools/bench/crtc_mode.lua","crtc_mode.lua"} do
local f = loadfile(p); if f then return f() end
end
error("crtc_mode.lua not found")
end
local MODE = load_mode()
local FLAG, VAR, ITER = 0x18000, 0x18004, 0x18008
local SRCW, SRCB, SRCP = 0x60000, 0x80000, 0x90000
local GVRAM, GPAL = 0xC00000, 0xE82000
local CPUHZ = 10000000 -- x68k.cpp:1133, 40_MHz_XTAL/4
local FRAME12 = CPUHZ / 12 -- 833333 cycles at 12 fps
-- Iteration counts sized so every variant runs ~4 emulated seconds.
local PLAN = {
{var=1, iter=100, name="V1 movem.l blit from word-expanded RAM (96KB read + 96KB write)"},
{var=2, iter= 50, name="V2 naive byte-source expansion (move.b/move.w per pixel)"},
{var=3, iter=200, name="V3 write-only floor (no source read at all)"},
{var=4, iter= 60, name="V4 same 96KB of writes, issued in 4x4 BLOCK order (decoder access pattern)"},
-- V8 is V1 with R20 bit 11's packing: 48KB read + 48KB write for the SAME
-- 49,152 pixels. It is the per-frame work of a decoder-free packed player
-- (FINDINGS 44.7 / 46.6 / 47.5), and 47.6.1 filed its `movem` shape as an
-- ASSUMPTION -- this is the measurement that assumption was standing in for.
{var=8, iter=200, name="V8 PACKED movem.l blit (48KB read + 48KB write, same 49,152 pixels as V1)"},
-- V9/V10 are the two ways a BLOCK decoder could survive the packed layout
-- (47.6.4, open since session 16): sixteen move.b at stride 2 per block, or
-- pair the blocks 128 columns apart in the encoder and get V4's movem back.
{var= 9, iter= 40, name="V9 PACKED block order, 16 move.b at stride 2 per 4x4 block"},
{var=10, iter=120, name="V10 PACKED block order, blocks PAIRED so a movem writes whole words"},
}
local code do
local f = io.open("blit.bin","rb"); code = f:read("a"); f:close()
end
local frame do
local f = io.open("frame256.bin","rb"); frame = f:read("a"); f:close()
end
local function B(i) return string.byte(frame,i) end
local W, H = B(5)*256+B(6), B(7)*256+B(8)
local PAL0, PIX0 = 9, 9+256*3
local YOFF = (MODE.height - H) // 2
-- Identical packing to show_frame256.lua: shared LSB I chosen per entry.
local function pal6(v) return ((v<<2)|(v>>4)) & 0xff end
local function pack(r,g,b)
local f = {r>>3, g>>3, b>>3}
local best, bestI = nil, 1
for I = 0,1 do
local e = 0
for c = 1,3 do
local want = ({r,g,b})[c]
local d = pal6((f[c]<<1)|I) - want
e = e + d*d
end
if best == nil or e < best then best, bestI = e, I end
end
return (f[2]<<11)|(f[1]<<6)|(f[3]<<1)|bestI
end
local function T() local t = M.time; return t.seconds + t.attoseconds/1e18 end
local function P(s) print("[BLIT] "..s) end
local function setup()
MODE.apply(SP)
-- letterbox rows: GVRAM holds IPL leftovers, not zeros
for y = 0, MODE.height-1 do
if y < YOFF or y >= YOFF+H then
local base = GVRAM + y*1024
for x = 0, MODE.width-1 do SP:write_u16(base + x*2, 0) end
end
end
for c = 0, 255 do
local o = PAL0 + c*3
SP:write_u16(GPAL + c*2, pack(B(o), B(o+1), B(o+2)))
end
-- Source frames in main RAM. SRCW holds one pixel per WORD with the index
-- in the low byte; the high byte is left as-is because gvram_w masks it off.
for y = 0, H-1 do
local row = PIX0 + y*W
for x = 0, W-1 do
local px = B(row+x)
SP:write_u16(SRCW + y*512 + x*2, px)
SP:write_u8 (SRCB + y*256 + x, px)
end
end
-- SRCP: the PACKED frame, interleaved the way tools/bench/show_frame256_packed.lua
-- lays it out -- word i of a row is (column i+128) << 8 | (column i), because
-- page 0 is the low byte at screen column i and page 1 the high byte at i+128.
-- Only V8 reads it, and only its SIZE (128 words a row) affects the timing;
-- the interleave is written correctly so the buffer is the real artefact and
-- not a same-sized stand-in.
for y = 0, H-1 do
local row = PIX0 + y*W
for i = 0, (W//2)-1 do
SP:write_u16(SRCP + y*(W//2)*2 + i*2, (B(row+i+W//2) << 8) | B(row+i))
end
end
for i = 1, #code do SP:write_u8(0x10000+i-1, string.byte(code,i)) end
P(string.format("loaded blit.bin=%d bytes, source frame %dx%d at yoff=%d", #code, W, H, YOFF))
end
local step, st, t0, snapped = 0, "boot", nil, false
local results = {}
local function launch(p)
SP:write_u32(FLAG, 0)
SP:write_u32(VAR, p.var)
SP:write_u32(ITER, p.iter)
local cpu = M.devices[":maincpu"]
cpu.state["SR"].value = 0x2700 -- supervisor, ALL interrupts masked
cpu.state["SP"].value = 0x8000
cpu.state["PC"].value = 0x10000
st, t0 = "running", nil
end
local function report(p, dt)
local cyc = dt * CPUHZ / p.iter
local pct = 100 * cyc / FRAME12
results[#results+1] = {p=p, cyc=cyc, pct=pct}
P(string.format("%s", p.name))
P(string.format(" %d iterations in %.4f s -> %.0f cycles/frame = %.1f%% of a 12fps frame",
p.iter, dt, cyc, pct))
end
SUB = emu.add_machine_frame_notifier(function()
local ok, err = pcall(function()
local t = T()
if st == "boot" then
if t < 3.0 then return end
setup(); step = 1; launch(PLAN[1]); return
end
if st == "running" then
local fl = SP:read_u32(FLAG)
if fl == 1 and not t0 then t0 = t; return end
if fl == 0xFF then
report(PLAN[step], t - (t0 or t))
if step == 1 and not snapped then st, snapped = "snap", true; return end
step = step + 1
if PLAN[step] then launch(PLAN[step]) else st = "finish" end
return
end
if t > 60 then P("TIMEOUT flag="..string.format("%08X",fl)); M:exit() end
return
end
if st == "snap" then
M.video:snapshot(); P("snapshot taken after V1 -- 68000-drawn frame")
step = step + 1; launch(PLAN[step]); return
end
if st == "finish" then
P("---- summary (instruction cycles only; real GVRAM adds wait states) ----")
for _,r in ipairs(results) do
P(string.format(" V%d %8.0f cyc %5.1f%% of 12fps frame", r.p.var, r.cyc, r.pct))
end
M:exit()
end
end)
if not ok then print("[BLIT] LUA ERROR: "..tostring(err)); M:exit() end
end)