128 lines
4.7 KiB
Bash
Executable file
128 lines
4.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
# See LICENSE.md for license information.
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# verify-amiga-palette.sh - the Amiga's palette and SCB changes through the
|
|
# copper: fast, and right on the screen.
|
|
#
|
|
# The Amiga shows each SCB band's palette from one of two band lists of its
|
|
# own, which the screen's user copper list (merged by graphics.library once)
|
|
# jumps to: a present that changes palettes or the SCB writes the list not
|
|
# shown, and the VBL server switches to it with the planes (src/amiga/hal.c,
|
|
# bandListUpdate). Before, a band that moved rebuilt and merged the whole
|
|
# copper list (MrgCop, LoadView, then a WaitTOF) -- about three frames on an
|
|
# A500 -- and a palette change patched the shown list, a frame before its
|
|
# pixels. The stage hashes cannot see any of this: they hash the surface,
|
|
# not the copper.
|
|
#
|
|
# PALPROBE (examples/palprobe; make -f make/amiga.mk build/amiga/test/Palprobe)
|
|
# runs under FS-UAE on an A500. It logs how many palette changes, and SCB
|
|
# changes (bands moving), it presented a second, back to back: each waits
|
|
# for the VBL that shows the last, so both rates must be at least MIN_RATE,
|
|
# nearly the frame rate (a rebuild a change gave 16). Then it holds a screen
|
|
# of four bands after one palette change and one same-layout SCB change,
|
|
# and the screenshot's bands must be, top to bottom, red, green, yellow, red.
|
|
#
|
|
# Usage: scripts/verify-amiga-palette.sh
|
|
# Env: TIMEOUT seconds to wait for the probe (default 120)
|
|
# Exit: 0 PASS, 1 FAIL, 2 SKIP (FS-UAE or the Kickstart not installed)
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
joeyNeed FSUAE fsuae
|
|
source "$repo/scripts/lib/amigaGate.sh"
|
|
amigaMachine
|
|
timeout_s=${TIMEOUT:-120}
|
|
probe=$repo/build/amiga/test/Palprobe
|
|
label=verify-amiga-palette
|
|
# Changes presented a second, back to back: one a frame is 50 on the PAL
|
|
# A500 (16 when every change rebuilt the copper list).
|
|
MIN_RATE=45
|
|
|
|
make -s -f "$repo/make/amiga.mk" "$probe" || {
|
|
echo "$label: FAIL - building Palprobe failed" >&2
|
|
exit 1
|
|
}
|
|
|
|
work=$(mktemp -d -t joeylib-amiga-palette.XXXXXX)
|
|
mkdir -p "$work/s"
|
|
cp "$probe" "$work/"
|
|
printf 'stack 32768\n:Palprobe\n' > "$work/s/startup-sequence"
|
|
display=:$((RANDOM % 400 + 1300))
|
|
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp > /dev/null 2>&1 &
|
|
xvfb_pid=$!
|
|
fs_pid=
|
|
cleanup() {
|
|
[[ -n $fs_pid ]] && joeyStop "$fs_pid"
|
|
kill "$xvfb_pid" 2>/dev/null || true
|
|
wait 2>/dev/null || true
|
|
rm -rf "$work"
|
|
}
|
|
trap cleanup EXIT
|
|
sleep 0.5
|
|
|
|
DISPLAY=$display "$FSUAE" "${AMIGA_MACHINE_ARGS[@]}" --fast_memory=2048 \
|
|
--hard_drive_0="$work" --hard_drive_0_label=JOEYLIB \
|
|
--automatic_input_grab=0 \
|
|
--fullscreen=0 --fade_out_duration=0 > "$work/fsuae.log" 2>&1 &
|
|
fs_pid=$!
|
|
|
|
log=$work/joeylog.txt
|
|
for ((waited = 0; waited < timeout_s; waited++)); do
|
|
if grep -q "PALPROBE: ready" "$log" 2>/dev/null; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if ! grep -q "PALPROBE: ready" "$log" 2>/dev/null; then
|
|
echo "$label: FAIL - the probe never reached its held screen within ${timeout_s}s" >&2
|
|
exit 1
|
|
fi
|
|
# Two seconds into the hold: the copper has run the patched list.
|
|
sleep 2
|
|
DISPLAY=$display import -window root "$work/screen.png"
|
|
grep "PALPROBE:" "$log"
|
|
|
|
python3 - "$work/screen.png" "$log" "$MIN_RATE" "$label" <<'PY'
|
|
import re
|
|
import sys
|
|
|
|
from PIL import Image
|
|
|
|
png, log, minRate, label = sys.argv[1], sys.argv[2], int(sys.argv[3]), sys.argv[4]
|
|
rates = {}
|
|
for line in open(log, encoding="ascii", errors="replace"):
|
|
m = re.search(r"PALPROBE: (palette|scb)\+present: \d+ in \d+ ms = (\d+)/s", line)
|
|
if m:
|
|
rates[m.group(1)] = int(m.group(2))
|
|
if sorted(rates) != ["palette", "scb"]:
|
|
print(f"{label}: FAIL - the log does not have both rates ({rates})")
|
|
sys.exit(1)
|
|
|
|
# The bands down the middle column, top to bottom, black (the border)
|
|
# dropped: each run of one colour is a band.
|
|
img = Image.open(png).convert("RGB")
|
|
w, h = img.size
|
|
named = {(255, 0, 0): "red", (0, 255, 0): "green", (255, 255, 0): "yellow"}
|
|
bands = []
|
|
for y in range(h):
|
|
rgb = img.getpixel((w // 2, y))
|
|
if rgb == (0, 0, 0):
|
|
continue
|
|
name = named.get(rgb, "other " + str(rgb))
|
|
if not bands or bands[-1] != name:
|
|
bands.append(name)
|
|
want = ["red", "green", "yellow", "red"]
|
|
fails = []
|
|
for kind in ("palette", "scb"):
|
|
if rates[kind] < minRate:
|
|
fails.append(f"{kind} changes presented {rates[kind]}/s, want at least {minRate}")
|
|
if bands != want:
|
|
fails.append(f"bands {bands}, want {want}")
|
|
if fails:
|
|
print(f"{label}: FAIL - " + "; ".join(fails))
|
|
sys.exit(1)
|
|
print(f"{label}: PASS (palette changes {rates['palette']}/s, SCB changes {rates['scb']}/s; bands {', '.join(want)} after a palette change and a same-layout SCB change)")
|
|
PY
|