Headless: Default to software rendering again

Since 551e4cd0ab, headless without --graphics silently used the OpenGL
backend, since bSoftwareRendering defaulted to false. Under Mesa llvmpipe
on Linux/WSL, that hangs games early in boot (and test.py, which passes no
--graphics). The README already documents software as the default.

Also documents the traps hit while chasing this in docs/debugging.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-23 19:25:24 -06:00
1 parent 09cf031f2b
commit a293221fe2
2 files changed
+34 -1

No files matched your search

+30
View File
@@ -68,6 +68,13 @@ A working invocation, and the traps around it:
- **Prefer `--debugger=0` and scrape `Listening on port N` from that run's own log** over hardcoding a port. Also
`taskkill //F //IM PPSSPPHeadless.exe` between runs for hygiene (Git Bash here has no `pkill`) - leftover
instances are easy to accumulate when a script leaves the CPU stopped at a breakpoint.
- **On Linux, kill leftovers with `pkill -x PPSSPPHeadless`, never `pkill -f PPSSPPHeadless`.** `-f` matches full
command lines, and the shell running your `pkill` has "PPSSPPHeadless" in its own command line, so it kills
itself (exit 144) and the emulator instances you meant to clear survive into the next run.
- **Attaching gdb to a running instance fails under WSL/Ubuntu** (`ptrace_scope`: "Could not attach to process"),
so a hang has to be caught by starting the run under gdb. Interrupting a batch-mode gdb from another shell with
`pkill -INT` never got as far as the backtrace in the 2026-09-22 attempts. Before reaching for gdb, first rule out
the cheap explanations below (backend, flags) by diffing a good and a bad command line.
Some history, because it silently produced a round of bogus results before it was fixed: `Common/Net/HTTPServer.cpp`
used to set `SO_REUSEADDR`, which on Winsock means "allow binding a port someone else is already listening on"
@@ -171,6 +178,29 @@ produced a round of bogus results here:
The last three compound: the fix is to treat the run's exit code and a positive "we got here" counter as
preconditions, and only then believe the error counts.
**If headless goes silent early in a game's boot, check which GPU backend it's actually using.** From
551e4cd0ab (2026-08-04) until 2026-09-22, headless without `--graphics` quietly ran the OpenGL backend instead of
the software renderer the README promises (`bSoftwareRendering` defaulted to false). Under Mesa llvmpipe on
Linux/WSL, OpenGL hung every commercial game tried early in boot, e.g. AI Go right after its
`sceKernelCreateCallback`, with no CPU use. Neither `--timeout-wall` nor `--timeout-emulated` fired, because both
are checked only when the emulation loop comes back around, so a blocked host thread defeats them. `test.py`
passes no `--graphics` either, so it stalled the same way. The default is software again; the OpenGL hang itself
isn't fixed.
That cost a lot of time because the first theories were confounded. Runs "worked in the background and hung in
the foreground" only because the background ones happened to have `--graphics=software` added. Change one variable
at a time, and diff the full command lines of a good and a bad run before theorising about the environment. If a
run is still silent a few seconds in, it isn't going to recover, so don't wait out the full timeout. Other things
that made it worse:
- `docs/debugging.md` wasn't read first, so the `timeout`-wrapper and full-`--log` warnings above were missed.
- The runs were wrapped in `timeout`, and a tool-call limit shorter than that sent them to the background. The
follow-up `pkill -f` then killed its own shell rather than the emulators, so hung instances piled up across runs.
- `--memstick` was blamed first. It matters for LLE firmware modules, but a game that needs none (AI Go) stalled
just the same without it.
- Pass `--graphics=software` whenever rendering doesn't matter, even though it's the default, so a copied
command line doesn't depend on the default.
For the silent-fallback half of this, headless refuses the run rather than substituting: an explicit
`--disable-hle=` whose firmware module isn't there names the module, prints the `flash0:/kd` and memory stick
it looked in (usually enough to spot that it's the one beside the exe), and exits 1. Only an *explicit*