Headless used ~/.ppsspp, which nothing else uses, while the app uses
~/.config/ppsspp (or $XDG_CONFIG_HOME/ppsspp on Linux). The derivation
is now shared, and the app's default is unchanged. Windows and iOS keep
their own.
test.py and frametests.py pass a memory stick next to the binary, as
Windows headless already defaulted to, so the tests still don't write
into the user's memory stick or see its firmware.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
iOS only runs app bundles, so with HEADLESS=ON the iOS build makes
PPSSPPHeadless an app (org.ppsspp.headless) that starts UIKit and runs
the headless main on a thread with the arguments devicectl launched it
with. Vulkan only, and the IR interpreter since there's no JIT.
frametests.py --ios installs it, copies over the dumps the device lacks,
and compares each render with the host's Vulkan one like --adb does.
Each devicectl call costs a second or two, so the new --screenshot-dir
lets one launch render a batch of dumps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Android headless build only did software rendering. Vulkan takes the
offscreen path desktop headless already uses, and OpenGL ES renders into an
EGL pbuffer, so both run from adb shell with no window. Assets are found next
to the executable, like on desktop, instead of in an APK path that no longer
exists.
frametests.py --adb pushes the binary, assets and dumps to a device, runs each
dump there with Vulkan and GLES, and compares the screenshots with the host's
Vulkan render using Tools/image_compare.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A long headless run on Vulkan dies in VulkanPushPool::CreateBlock. Watching the
allocator, it makes a fresh 8MB block roughly twice a second and garbage
collects none of them - about 13MB a second of device memory, which runs out
after a minute or two.
Nothing is leaking as such. The push buffers are recycled by BeginFrame, which
walks the blocks belonging to the current frame index and marks them unused.
Headless called draw->BeginFrame() once before the run loop and draw->EndFrame()
once after, so that recycling pass ran exactly once for the whole run and every
allocation after the first had to take a new block.
This is the same mistake one level up from the host frame, which already turns
over per emulated frame for the same reason - the comment there says a single
host frame spanning the run meant the texture cache and framebuffer manager
never decayed anything. The draw context needs the same treatment, nested the
way the app nests them: draw frame outside, host frame inside.
Verified on a two-minute Tekken 6 run: new blocks created goes from around 200
to zero, and the run ends on its timeout instead of asserting. Framedump
rendering tests are unchanged - the same 23 of 30 fail before and after, which
is a separate pre-existing matter on this platform.
Also taught frametests.py to look for an ARM64 build, gated on the machine's own
architecture the way test.py already does. It was picking a stale x64 Debug
binary, which is exactly the trap that makes a rendering comparison meaningless.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The found binary path could be relative (e.g. 'PPSSPPHeadless' from the
repo root in CI), but tests run with the output dir as cwd, so launching
failed with FileNotFoundError. Resolve the path against the script's cwd,
and turn launch failures into per-test ERROR results instead of crashing
the whole run.
AGENTS.md: never push without asking first.
Variants can now declare a compare-suffix (defaulting to the variant's own
suffix) to compare against another variant's reference images, enabling
shared references across e.g. opengl/vulkan when their output matches.
Reference images are only generated for variants whose suffix equals their
compare-suffix; other variants fail clearly if the shared reference is
missing. Rename the default output directory from frametest-out to out.
- Add frametests.py: walks a dump tree, renders each dump per config variant
through PPSSPPHeadless, generates reference images when missing and compares
MSE when present, and writes a self-contained HTML report. The JSON config
(which lives with the test set, not in the repo) points at the data tree
and defines variants as suffix -> CLI args, e.g. 'soft': '--graphics=software'.
- Headless: --screenshot-save saves PNG when the path ends in .png; new
--screenshot-diff always writes a visual comparison when comparing;
screenshot comparison failures (mismatch or unloadable reference) now fail
the test instead of passing silently.
- Read back framebuffers top-down, flipping only for BMP output/input
(fixes upside-down PNG references). Sync libretro copy accordingly.
- Document the system in docs/frametest.md; add AGENTS.md reference.