More performance work

This commit is contained in:
Scott Duensing 2026-07-06 18:00:16 -05:00
parent 9b95c79c20
commit 41f33b0dfe
37 changed files with 319 additions and 24 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
generated/iigs/font.tbk Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

160
scripts/bench-staxi-title.sh Executable file
View file

@ -0,0 +1,160 @@
#!/usr/bin/env bash
# bench-staxi-title.sh - Space Taxi title-screen logo cadence measurement
# (the PERF-AUDIT #3 acceptance test: the logo repaints via 103
# jlTilePasteMono calls, intended every 4th frame -- stRender.c
# titleAnimateLogo's `& 3u` divider mirroring the C64 original).
#
# Boots GS/OS under headless MAME with a WORK COPY of joey.2mg (must
# contain STAXI + its DATA tree; build with
# JOEY_DISK_EXAMPLES="STAXI" scripts/make-iigs-disk.sh),
# launches STAXI via the standard Finder keystroke timeline, then
# samples 8 known logo tile cells from the SHR framebuffer EVERY frame.
# Each frame the sampled 256 bytes fold into a rolling hash; a hash
# change means the logo visibly changed on screen (the flip-book tile
# differs on every 4-frame step, so a healthy title changes every 4
# frames; a slow repaint stretches the observed period).
#
# Emits "STAXI-CHG frame=N sum=XXXXXX" per change, heartbeats, and a
# final verify-style summary. Post-process the change frames into
# periods with the analysis snippet in the acceptance report.
#
# Usage: scripts/bench-staxi-title.sh [label]
# MAME_ROMPATH override ROM dir (default ~/.mame/roms)
# STAXI_MAX_FRAMES frame cap (default 15000)
# STAXI_MAX_CHANGES stop after this many logo changes (default 80)
set -euo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
: "${LLVM816_ROOT:?bench-staxi-title.sh: source toolchains/env.sh first}"
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
data_disk=$repo/build/iigs/bin/joey.2mg
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
maxFrames="${STAXI_MAX_FRAMES:-15000}"
maxChanges="${STAXI_MAX_CHANGES:-80}"
label="${1:-run}"
for f in "$sys_disk" "$data_disk"; do
[ -f "$f" ] || { echo "bench-staxi-title: missing $f" >&2; exit 2; }
done
if ! "$CADIUS" CATALOG "$data_disk" 2>/dev/null | grep -qE '^ STAXI +S16'; then
echo "bench-staxi-title: STAXI not on joey.2mg (JOEY_DISK_EXAMPLES=\"STAXI\" scripts/make-iigs-disk.sh)" >&2
exit 2
fi
work=$(mktemp -d -t joeylib-staxi.XXXXXX)
trap 'rm -rf "$work"' EXIT
cp "$sys_disk" "$work/boot.po"
cp "$data_disk" "$work/joey.2mg"
cat > "$work/staxi.lua" <<LUA
local cpu = manager.machine.devices[":maincpu"]
local mem = cpu.spaces["program"]
local nat = manager.machine.natkeyboard
local frame = 0
local idx = 1
local function field(port, name)
local p = manager.machine.ioport.ports[port]
if p == nil then return nil end
return p.fields[name]
end
local key_cmd = field(":macadb:KEY3", "Command / Open Apple")
local function press(f) if f then f:set_value(1) end end
local function release(f) if f then f:set_value(0) end end
-- 8 logo tile cells (bx,by) chosen from title.dat's 0x84 map: byte
-- offset in SHR = 0xE12000 + (by*8 + r)*160 + bx*4, 4 bytes per row.
-- Every repaint pastes a different flip tile into these cells, so
-- their content changes on every animation step.
local cells = { {6,1},{11,1},{24,1},{29,1},{12,7},{16,8},{12,9},{15,10} }
local function logoSum()
local sum = 0
for _, c in ipairs(cells) do
local base = 0xE12000 + (c[2] * 8) * 160 + c[1] * 4
for r = 0, 7 do
local off = base + r * 160
for b = 0, 3 do
sum = (sum * 33 + mem:read_u8(off + b)) % 16777213
end
end
end
return sum
end
local steps = {
{3000, function() nat:post("J") end},
{3120, function() press(key_cmd) end},
{3126, function() nat:post("o") end},
{3180, function() release(key_cmd) end},
{3540, function() nat:post("STAXI") end},
{3660, function() press(key_cmd) end},
{3666, function() nat:post("o") end},
{3720, function() release(key_cmd) end},
}
local prevSum = -1
local nChanges = 0
local function summary(tag)
local distinct = {}
local nonzero = 0
local checksum = 0
for i = 0, 31999 do
local b = mem:read_u8(0xE12000 + i)
if b ~= 0 then nonzero = nonzero + 1 end
distinct[b & 0x0F] = true
distinct[(b >> 4) & 0x0F] = true
checksum = (checksum + b) & 0xFFFFFF
end
local n = 0
for _ in pairs(distinct) do n = n + 1 end
io.write(string.format("STAXI-%s label=$label frame=%d changes=%d distinctNibbles=%d nonZeroBytes=%d checksum=%06X\n",
tag, frame, nChanges, n, nonzero, checksum))
io.flush()
end
emu.register_frame_done(function()
frame = frame + 1
while idx <= #steps and frame >= steps[idx][1] do
steps[idx][2]()
idx = idx + 1
end
if frame >= 3800 then
local s = logoSum()
if s ~= prevSum then
io.write(string.format("STAXI-CHG frame=%d sum=%06X\n", frame, s))
io.flush()
prevSum = s
nChanges = nChanges + 1
if nChanges >= $maxChanges then
summary("DONE")
manager.machine:exit()
end
end
end
if frame % 1500 == 0 then
io.write(string.format("STAXI-HEART frame=%d changes=%d\n", frame, nChanges))
io.flush()
end
if frame >= $maxFrames then
summary("TIMEOUT")
manager.machine:exit()
end
end)
LUA
echo "bench-staxi-title[$label]: running STAXI headless under MAME (cap $maxFrames frames, $maxChanges changes)..." >&2
cd "$work"
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout "${STAXI_WALL_TIMEOUT:-900}" mame apple2gs \
-rompath "$rompath" \
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
-video none -sound none -nothrottle \
-autoboot_script "$work/staxi.lua" </dev/null 2>&1) || true
echo "$out" | grep -E '^STAXI-(CHG|HEART|DONE|TIMEOUT)' > "$repo/build/iigs/bin/staxi-title-$label.log" || true
tail -3 "$repo/build/iigs/bin/staxi-title-$label.log" >&2
lines=$(grep -c '^STAXI-CHG' "$repo/build/iigs/bin/staxi-title-$label.log" || true)
echo "bench-staxi-title[$label]: $lines change events -> build/iigs/bin/staxi-title-$label.log" >&2

143
scripts/diag-staxi-screen.sh Executable file
View file

@ -0,0 +1,143 @@
#!/usr/bin/env bash
# diag-staxi-screen.sh - one-off diagnostic for the STAXI title test:
# boot + launch STAXI, dump the full SHR framebuffer + palettes as hex
# at a target frame, then extract JOEYLOG.TXT from the work disk so the
# game's own init/asset-load log is visible. Outputs land in
# build/iigs/bin/ (staxi-shr.hex, staxi-joeylog.txt).
set -euo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
: "${LLVM816_ROOT:?diag-staxi-screen.sh: source toolchains/env.sh first}"
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
data_disk=$repo/build/iigs/bin/joey.2mg
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
dumpFrame="${STAXI_DUMP_FRAME:-9000}"
work=$(mktemp -d -t joeylib-staxidiag.XXXXXX)
trap 'rm -rf "$work"' EXIT
cp "$sys_disk" "$work/boot.po"
cp "$data_disk" "$work/joey.2mg"
cat > "$work/diag.lua" <<LUA
local cpu = manager.machine.devices[":maincpu"]
local mem = cpu.spaces["program"]
local nat = manager.machine.natkeyboard
local frame = 0
local idx = 1
local function field(port, name)
local p = manager.machine.ioport.ports[port]
if p == nil then return nil end
return p.fields[name]
end
local key_cmd = field(":macadb:KEY3", "Command / Open Apple")
local function press(f) if f then f:set_value(1) end end
local function release(f) if f then f:set_value(0) end end
local steps = {
{3000, function() nat:post("J") end},
{3120, function() press(key_cmd) end},
{3126, function() nat:post("o") end},
{3180, function() release(key_cmd) end},
{3540, function() nat:post("STAXI") end},
{3660, function() press(key_cmd) end},
{3666, function() nat:post("o") end},
{3720, function() release(key_cmd) end},
}
local pcs = {}
emu.register_frame_done(function()
frame = frame + 1
while idx <= #steps and frame >= steps[idx][1] do
steps[idx][2]()
idx = idx + 1
end
-- Hang localization: sample the PC over 60 frames before the dump,
-- and probe NEWVIDEO (0xE0C029: bit7 = SHR on -> jlpInit ran).
if frame == $dumpFrame - 120 then
io.write(string.format("SHRPROBE newvideo=%02X shadow=%02X\n",
mem:read_u8(0xE0C029), mem:read_u8(0xE0C035)))
io.flush()
end
if frame >= $dumpFrame - 100 and frame < $dumpFrame - 40 then
local pc = cpu.state["PC"].value
local k = string.format("%06X", pc)
pcs[k] = (pcs[k] or 0) + 1
end
if frame == $dumpFrame - 40 then
for k, v in pairs(pcs) do
io.write(string.format("SHRPC %s count=%d\n", k, v))
end
io.flush()
end
if frame == $dumpFrame - 20 then
-- Locate the jlLog RAM ring without UBER's mailbox: bulk-read every
-- RAM bank and emit it as hex; the host greps for the build-stamp
-- text. read_range failures are reported, not swallowed.
local banks = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,0xE0,0xE1}
for _, bank in ipairs(banks) do
local base = bank * 65536
local ok, buf = pcall(function() return mem:read_range(base, base + 65535, 8) end)
if ok and buf ~= nil then
for off = 0, 65535, 256 do
local chunk = string.sub(buf, off + 1, off + 256)
local hex = {}
for i = 1, #chunk do
hex[#hex + 1] = string.format("%02X", string.byte(chunk, i))
end
io.write(string.format("SHRMEM %02X%04X %s\n", bank, off, table.concat(hex)))
end
else
io.write(string.format("SHRMEM-FAIL bank=%02X err=%s\n", bank, tostring(buf)))
end
end
io.flush()
end
if frame == $dumpFrame then
-- SHR pixel dump: 200 rows x 160 bytes as hex lines.
for row = 0, 199 do
local parts = {}
for b = 0, 159 do
parts[#parts + 1] = string.format("%02X", mem:read_u8(0xE12000 + row * 160 + b))
end
io.write("SHRROW " .. table.concat(parts) .. "\n")
end
-- SCBs + palettes.
local scb = {}
for i = 0, 15 do scb[#scb + 1] = string.format("%02X", mem:read_u8(0xE19D00 + i)) end
io.write("SHRSCB " .. table.concat(scb, " ") .. "\n")
local pal = {}
for i = 0, 31 do pal[#pal + 1] = string.format("%02X", mem:read_u8(0xE19E00 + i)) end
io.write("SHRPAL " .. table.concat(pal, " ") .. "\n")
io.flush()
manager.machine:exit()
end
end)
LUA
echo "diag-staxi: dumping SHR at frame $dumpFrame..." >&2
cd "$work"
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout 900 mame apple2gs \
-rompath "$rompath" \
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
-video none -sound none -nothrottle \
-autoboot_script "$work/diag.lua" </dev/null 2>&1) || true
echo "$out" | grep -E '^SHR(PROBE|PC)|^SHRMEM-FAIL' | sed 's/^/diag-staxi: /' >&2
echo "$out" | grep -E '^SHRMEM ' > "$repo/build/iigs/bin/staxi-mem.hex" || true
echo "$out" | grep -E '^SHR(ROW|SCB|PAL)' > "$repo/build/iigs/bin/staxi-shr.hex"
wc -l "$repo/build/iigs/bin/staxi-shr.hex" >&2
# The game flushed its log to the volume root; pull it off the WORK copy.
rm -f "$repo/build/iigs/bin/staxi-joeylog.txt" "$work/JOEYLOG.TXT"* 2>/dev/null || true
"$CADIUS" EXTRACTFILE "$work/joey.2mg" "/JOEYLIB/JOEYLOG.TXT" "$work/" >/dev/null 2>&1 || true
found=$(ls "$work"/JOEYLOG.TXT* 2>/dev/null | head -1)
if [ -n "$found" ]; then
tr -d '\r' < "$found" > "$repo/build/iigs/bin/staxi-joeylog.txt"
echo "diag-staxi: extracted JOEYLOG.TXT:" >&2
else
echo "diag-staxi: no JOEYLOG.TXT on work disk (flush never ran?)" >&2
fi

View file

@ -1139,42 +1139,34 @@ static void destroySprites(void) {
}
// The 1000-glyph font streams straight onto the font surface via
// jlTileBankLoadToSurface -- no 32KB staging buffer. The old
// malloc(32000) was lethal on IIgs (the clang libc heap is ~665
// bytes and malloc returns 1, not NULL, at exhaustion -- LLVM816-ASKS
// item 4 -- so jlTileBankLoad fread the payload through pointer 1 and
// shredded bank 0 before the first present), and a 32KB static blows
// the bank-0 BSS ceiling. The streaming loader exists for exactly
// this case; its block grid matches ST_FONT_COLS row-major layout.
static bool loadFontSheet(jlSurfaceT *stage) {
jlTileT *fontTiles;
uint16_t count;
uint16_t i;
uint16_t palette[16];
(void)stage;
// Load font.tbk into a temp tile array, then paste each glyph
// into a regular surface that jlDrawText can sample. ~32 KB of
// temp memory for the 1000-tile font; freed before returning.
fontTiles = (jlTileT *)malloc(sizeof(jlTileT) * ST_FONT_TILES_MAX);
if (fontTiles == NULL) {
return false;
}
count = jlTileBankLoad(ST_FONT_PATH, fontTiles, ST_FONT_TILES_MAX,
NULL, palette);
if (count == 0u) {
free(fontTiles);
return false;
}
gRender.fontSurface = jlSurfaceCreate();
if (gRender.fontSurface == NULL) {
free(fontTiles);
return false;
}
count = jlTileBankLoadToSurface(ST_FONT_PATH, gRender.fontSurface, palette);
if (count == 0u) {
jlSurfaceDestroy(gRender.fontSurface);
gRender.fontSurface = NULL;
return false;
}
// Font palette is authoritative for the font surface so jlDrawText
// reads back the authored colors. Stage palette is untouched.
jlPaletteSet(gRender.fontSurface, 0u, palette);
jlScbSetRange(gRender.fontSurface, 0, SURFACE_HEIGHT - 1, 0);
for (i = 0u; i < count; i++) {
uint8_t bx = (uint8_t)(i % ST_FONT_COLS);
uint8_t by = (uint8_t)(i / ST_FONT_COLS);
jlTilePaste(gRender.fontSurface, bx, by, &fontTiles[i]);
}
gRender.fontReady = true;
free(fontTiles);
return true;
}