diff --git a/AGENTS.md b/AGENTS.md index bf5fbe3b6a..3c14834a9f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ for it: | [docs/building.md](docs/building.md) | Build commands for every target (VS/MSBuild, CMake, UWP, legacy Android NDK, libretro), unit tests, pspautotests | | [docs/android-device-testing.md](docs/android-device-testing.md) | Running a build on a real Android device or a Quest over adb: installing the build you think you are, forcing activity recreates, measuring retained activities, VR, running the C++ unit tests on a device (`Tools/android_unittest.py`), starting the app with options (`Tools/android_run.py`), and the storage tests that exercise content URIs in the app (`Tools/android_storage_test.py`) | | [docs/android-todo.md](docs/android-todo.md) | Known Android Java issues found in reviews and not fixed yet - check before reviewing or changing that code | -| [docs/debugging.md](docs/debugging.md) | Driving the WebSocket debugger and PPSSPPHeadless from a script, measuring a commercial game with headless, comparing binaries' speed (`Tools/headless_bench.py`), finding a game's hot JIT blocks (`--jit-profile`), breakpoint reliability per CPU backend, debugging a game that works on hardware, finding and hashing a game function (like its own `memcpy`) for `ReplaceTables` when hardware backends miss CPU writes to VRAM | +| [docs/debugging.md](docs/debugging.md) | Driving the WebSocket debugger and PPSSPPHeadless from a script, measuring a commercial game with headless, comparing binaries' speed (`Tools/headless_bench.py`), finding a game's hot JIT blocks (`--jit-profile`), finding what makes frames stall (`--profile-log`, `Tools/profile_report.py`), breakpoint reliability per CPU backend, debugging a game that works on hardware, finding and hashing a game function (like its own `memcpy`) for `ReplaceTables` when hardware backends miss CPU writes to VRAM | | [docs/DebuggerThreading.md](docs/DebuggerThreading.md) | `Core_RunOnCPUThread` / `g_frameMutex` / shutdown-lock rules - required reading before touching debugger code | | [docs/HLEModules.md](docs/HLEModules.md) | Adding an HLE module or function, and the seven build files a new source file goes in | | [docs/translations.md](docs/translations.md) | Translating UI strings with Tools/langtool | diff --git a/docs/adhoc-multiplayer-testing.md b/docs/adhoc-multiplayer-testing.md index 0117a5c084..c2662276c2 100644 --- a/docs/adhoc-multiplayer-testing.md +++ b/docs/adhoc-multiplayer-testing.md @@ -73,6 +73,32 @@ with `crop SHOT.png X,Y,W,H OUT.png` from a screenshot of the screen you want, a script: paths in a script are relative to it. A title screen left alone too long may start an attract demo, so don't wait for one without pressing. +A screen that only shows on some runs (a profile menu that needs a save, say) takes a branch: `until` +also stops on any `--or REF@X,Y`, and a line `if REF --box X,Y : COMMAND ; COMMAND` runs the commands +only when the screen shows the reference right then: + +``` +until select_profile.png --box 100,14 --or create_profile.png@100,14 --press start --every 4 +if select_profile.png --box 100,14 : press cross +if create_profile.png --box 100,14 : press cross ; wait 2 ; press cross +``` + +### Driving the app, or a phone + +`attach` makes a session of one PPSSPP that's already running with `--debugger=PORT` (default 45678), +and the other commands then drive it. With `--adb`, that's the app on an Android device: the port is +forwarded, and screenshots come from `screencap`, cut to the game's image (`--rect X,Y,W,H` in the +device's pixels; `0,249,1080,612` on a Galaxy S8 in portrait) and scaled to 480x272, so a script and its +references work the same on a phone as headless. `screencap` doesn't pause emulation, unlike the +debugger's screenshot, which matters when the run is being profiled +([debugging.md](debugging.md#finding-stalls-with-the-frame-profiler)). + +```bash +python Tools/android_run.py -- --debugger=45678 /sdcard/PSP/GAME.iso +python3 Tools/adhoc_session.py attach --adb --rect 0,249,1080,612 +python3 Tools/adhoc_session.py run game_ingame.txt +``` + To see what a game does, `log sceNet debug` turns a log channel up, and `watch SECONDS NAME` collects each instance's log messages meanwhile into `NAME_N.log` (the debugger only sends log messages to clients that are connected). That works without `--log`, which slows the instances down enough to diff --git a/docs/debugging.md b/docs/debugging.md index 359a3c8a1c..590cf93903 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -295,6 +295,32 @@ shows as emitter and IR pass functions; profile 20 emulated seconds or more to s To compare two builds by where the time goes, profile one game at a time: several at once wait on each other's GPU work, which moves the shares. +### Finding stalls with the frame profiler + +The frame profiler (`Common/Profiler/Profiler.h`) is compiled into everything but Android release +builds, iOS, UWP and libretro, and off until something turns it on. `--profile-log=FILE` (app and +headless) does, and writes a JSON line per frame with each thread's time per scope category, plus, for +each slow frame, every scope of at least `--profile-span-ms` (default 1) that ended in it, with its +label: which syscall, CoreTiming event, module file or pipeline. A frame is slow when it takes at least +`--profile-hitch-ms` (default 25) and 1.5 times the median of the frames before it; each one also gets a +`Profiler: Frame` line in the log naming its longest scopes. + +```bash +python Tools/profile_report.py profile.jsonl # summary, causes, the worst frames' scopes +python Tools/profile_report.py profile.jsonl --frame 1234 # one frame +``` + +The report only counts a slow frame as a stall if it fell behind the emulated time it covered (each +vblank in it is 16.7 ms on a PSP): a game that doesn't flip for ten vblanks while loading shows a 167 ms +frame on hardware too. It sums the stalls' time by thread and category from the exact per-frame totals, +for the emulator thread and the render thread (a worker's time matters where something waits for it, +which shows as `wait_*`), and lists the labelled scopes. + +To find the stalls a player sees, measure a cold first run: `--shader-cache=false`, and 1x rendering +resolution so the GPU matters less. Getting into the game from boot takes a script (see "Driving the +app, or a phone" in [adhoc-multiplayer-testing.md](adhoc-multiplayer-testing.md)): pressing buttons at +random gets stuck in profile and save menus. + ## Debugging and breakpoint considerations It might be worth trying the interpreter - all types of breakpoints are the most reliable with this CPU backend.