A non-likely conditional branch now exits to its target when taken, and
the block goes on with the not-taken path, up to 8 branches and 96
instructions. Loops written as a few blocks become one with one exit per
iteration, and code around branches gets optimized together. Branches
decided by their operands, like b, still end the block.
Not with a prefix pending, as blocks assume default prefixes on entry
and only the end of a block checks that it leaves them so.
10 emulated seconds, Vulkan, ARM64: cycles -4.6% in God of War, -3.5%
Killzone, -3.0% God Eater, -2.8% Daxter, -2.3% Tekken 6, -2.2% GTA,
-2.1% ATV, about -1% in the rest.
docs/cpu-equivalence.md gets the fuzzer's new classes and what it found
on this branch and in #22576.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The native IR JITs get the emulator thread's host PC sampled every 50 us (a
signal on macOS and Linux, SuspendThread on Windows), mapped to blocks, the
routines they call and host functions. The report has each hot block's MIPS,
IR and host code with the samples per host instruction. The IR interpreter's
block timing, previously behind IR_PROFILING, now runs on the same switch.
Both cost nothing when off.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The accurate dot products cost a few percent at most in the IR JIT, apart
from GTA (about 20% on a game that's cheap to run anyway). The fuzzer follows
the default too, unless PPSSPP_CPUFUZZ_ACCURATE_VDOT=0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Passes the PSP through from Windows with usbipd-win (sharing it through a
UAC prompt the first time), keeps it attached across PSPLink resets and
USB port changes, and starts usbhostfs_pc.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#12343
Killzone: Liberation's Chapter 5 DLC is an installer that the PSP turns
into ms0:/PSP/GAME/<id>/KZL.PRX, which the game loads with
sceKernelLoadModuleDNAS. That file is a PGD around a type 3 PRX (decrypt
mode 18), both keyed to the console that installed it. Its Fuse ID
gives everything needed:
- libkirk: the per-console key derivation, and KIRK commands 3, 5 and 8
built on it (KIRK ROM per uofw, key mesh per psdevwiki).
- PrxDecrypter: type 3, which is type 5 with the first step through
KIRK command 3 (JPCSP's PRX.java), and the DNAS PGD unwrap.
- sceKernelLoadModuleDNAS uses the new FuseID setting (or --fuse-id).
Without one, or with the wrong one, the game falls back to its stock
module and an OSD message says why.
- Headless --re-decrypt takes --re-decrypt-key and --fuse-id, to decrypt
such a file offline.
Verified with a patch installed on a real PSP: the v1.20 module loads,
starts and reads CHAPTER5.BIN. Type 5 output is unchanged (an NPJH50521
PBOOT.PBP decrypts byte-identically). How to install one over PSPLink is
in docs/dnas-patches.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The IR-based one replaces it. With that, every native JIT is IR-based, which the JIT viewer and
the settings' comments now assume. The ARM emitter test loses its part that drove the old FPU
register cache.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
./b.sh --armhf builds headless and the unit tests for 32-bit ARM, which run under qemu-arm the
way the loongarch64 and riscv64 builds do. setup-cross.sh learns the target (it also needs an
EGL stub), and the docs cover setting it up without root.
hash/hash dies with a bus error under qemu-arm on every CPU core, so it goes in a known-failures
list for the target.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Their IR-based replacements stay. The JIT core now creates the IR JIT on both, so "JIT using
IR" is the same as "JIT" on every architecture (ARM32 still uses its legacy JIT for both). The
settings no longer offer it, a saved JIT_IR loads as JIT, and the JIT harness and CPUEquivalence
fuzzer drop the duplicate run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Where the cores now agree, what the hardware tests answered, what's still
open (the NaN handling waits on the cpu-equiv-4 branch), and the ARM32 cores
as measured on a Galaxy S8+ and A21.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Fixes#7395
The GE has two return address registers, and a CALL beyond them does
nothing: the list carries on after it. A signal CALL is done by the
firmware, which saves and clears them, so it starts the count over,
and the signal RET restores it. Shown on a PSP by the new
gpu/callstack/depth test.
Soul Calibur: Broken Destiny turns lighting on for its weapon trails
from a third-level CALL, so on hardware the trails stay unlit and take
their colors from the vertices. We ran that CALL and drew them white.
Plain CALLs now store a 0 base address in their stack entry, which a
signal CALL never does, so the depth can be counted without changing
the savestate layout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fills in docs/cpu-equivalence.md with the answers from the new
pspautotests (shift_encodings, denormal, fcr31, signed_zero, nan_gen,
undefined), and how far each of the four cores is from them on ARM64
and on x86-64 under Rosetta.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/cpu-equivalence.md describes the CPUEquivalence fuzzer, what it
fixed, and the differences still open between the cores and between
x64 and ARM64 hosts, several of which need a test on a PSP first.
Co-Authored-By: Claude Opus 5.5 (1M context) <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>
Tested on an Apple TV 4K. The micro gamepad's dpad buttons flicker with every
wobble on the touch surface, so read the finger position instead: swipes step
once per fixed distance, and clicks are arrows at the edges and enter in the
middle. The UI scale now matches a 1080p Android TV (320 dpi). b-tvos.sh
can sign, install and launch on a device.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Returns the analyzed function's start, size and code hash at an address,
and the name of the hardcodedHashes entry already matching it, if any.
Replaces patching a printf into HashFunctions() in the docs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the GL view controller gone, PPSSPPViewControllerMetal was the only
subclass of PPSSPPBaseViewController. They're now one class,
PPSSPPViewController, in ios/ViewController.mm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Queues loading a savestate file, for scripting repros that load a state partway
through a run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
6.60 and 6.61 crashed in vsh_module's alarm-task scan on a count nothing
initialized, and a data patch zeroed it. With the shell's modules started
in order, both reach the XMB, show icons and launch homebrew without it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kernel starts modules one by one, each module_start returning before
the next starts. We started them all as threads side by side, and
vsh_module's start ran before paf's. Its 12MB request for game_plugin's
surface pools then went to the 8.3MB heap we pre-filled scePaf's pool
slot with, failed, and game_plugin threw away every decoded ICON0 and
PIC1 (white tiles on 3.80-5.05, grey placeholders from 5.50).
heaparea1, vshbridge, paf, common_gui and common_util now start in order,
and vsh_module waits for the last. The kd/ drivers still start side by
side, since syscon's start blocks on its hardware interrupt. paf fills
its own pool slot now, so the pre-fill is gone. Icons show on every
version from 1.50 to 6.61, and homebrew launches on the same ones as
before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Written while testing the activity lifecycle work on a Galaxy S8 and a Quest 2.
Mostly about ways the tooling answers a question you didn't ask.
The expensive one: versionCode is the git commit count, so installing a feature
branch over a longer branch fails with INSTALL_FAILED_VERSION_DOWNGRADE, which
-r does not override. The device keeps running the previous build, and if the
install output went through a pipe the failure is never seen. An A/B where both
installs failed that way agrees with itself perfectly and means nothing.
Also: dumpsys meminfo counts activities that are garbage but not yet collected,
so the obvious reading looks like a leak whether or not there is one - trim and
re-read until it settles; how to force an activity recreate over adb when the
device won't rotate on command; why screencap is black on a Quest and what to
read instead; and how to tell whether a device exercises the content-URI path at
all before concluding a run covered it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
Stage 5 of the JavaGL removal. Now that the GL path owns its own EGL context and
render thread, Android's two lifecycle branches and the Win32 one in
MainThreadFunc were the same code three times over, so this moves the branch into
one place - RunGraphicsLoop() in Core/EmuThread.cpp. It picks the thread
arrangement the context asks for: either this thread becomes the render thread
and spawns the emu thread (OpenGL), or it just runs the main loop because the
backend spawns its own render thread (Vulkan, D3D11). Android, Windows and
headless all go through it now, which is what the "TODO: Merge with the Win32
EmuThread and so on" in app-android.cpp was asking for.
Falls out of having one implementation instead of three:
- MainThreadFunc called graphicsContext->ThreadStart() right after
EmuThread_Start(), which already calls it, and ThreadEnd() right after
EmuThread_Join(), which already calls that too. So Windows and headless GL ran
CreateDeviceObjects()/DestroyDeviceObjects() twice per context, leaking a VAO
on the way in and double-deleting it on the way out. Dates back to 16206d18ab,
which moved those calls into EmuThread_Start/Join without removing the
originals.
- RECREATE_VIEWS is gone. Only the GL path ever posted it; Vulkan goes through
the same destroy/recreate without it and always has. Verified on device: views
survive pause/resume, and both a real rotation and a density change - each one
an activity destroy and recreate - lay them out again by themselves.
- The non-separate path is now named EmuThread rather than MainThread on
Windows. It's the thread that runs NativeFrame(), which is what
DebuggerThreading.md calls the emu thread, and the real main thread there is
the GUI message pump.
The loop keeps an explicit exit flag, as a shouldExit callback, rather than
relying on ThreadFrame() returning false. In VR we skip ThreadFrame() entirely
while the XR session is idle, and the session going idle is exactly what
backgrounding the app does - so the exit would never be observed, the render
thread would sit in that sleep forever, and the UI thread joining it would hang.
That is not hypothetical: it deadlocked a Quest 2 on the first try.
Also drops a dead EmuThreadState enum and an unused JNIEnv local in
app-android.cpp, both orphaned when the JavaGL JNI entry points went away.
Tested on a nubia Z2464N (Mali-G57, Android 15) on both backends: cold start,
booting a homebrew, pause/resume in the menu, in a settings screen and in-game,
rotation back and forth, a density-change activity recreate with the game live,
the pause menu, and exit to menu. ThreadStart/ThreadEnd log exactly once per
context, and every one of those cycles tears the EGL context down and builds a
new one.
Also tested on a Quest 2 with the vr flavor, which is what shook out the deadlock
above: cold start into a focused XR session at 72fps, backgrounding (render
thread joins in 27ms where it used to never return), resuming back into VR with
a fresh EGL context, and exiting from in-app. xrCreateSession runs on the render
loop thread, which is where the EGL context is current - VRBase.cpp builds its
graphics binding from eglGetCurrentDisplay/eglGetCurrentContext, so that ordering
matters.
Windows x64 debug and the Android gradle builds are clean.
Not converged, and not a small change: NeedsSeparateEmuThread() itself. GL needs
its API calls on the thread its context is current on, so somebody has to pump
ThreadFrame() there, whereas VulkanRenderManager owns its thread outright.
Getting rid of the virtual means teaching GLRenderManager to spawn and own its
own render thread, for every GL platform at once.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
A pixel counts as different only if it's outside the range of the other
image's 3x3 neighborhood (widened by a tolerance) and enough of its neighbors
are too, so edges a pixel off, filtering rounding and speckle drop out.
8x8 block averages catch broad shifts separately, and heatmaps show where.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
--depth-save converts their float depth (z / 65536) to the PSP's 16-bit
values, or writes the unrounded values when the path ends in .f32.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
headless_bench.py compares the speed of headless binaries on savestates
or frame dumps. It alternates the binaries between rounds and reports
medians of wall time, CPU time, and instructions and cycles where the OS
counts them. Instructions vary by well under 0.1% between identical runs,
where wall time swings by several percent. It checks for mains power
first, and stops on a run that failed or didn't get where it should.
dump_compare.py replays every frame dump in a directory with two binaries
and reports the dumps that render differently, or differently from run
to run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Nine tests of GE probe jobs that the software renderer reproduces bit for
bit (transform, planes, coverage, lines, texture, lighting, curves, nearclip,
pixel), recorded on a PSP.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bounded PSP liveness checks and the usbhostfs restart, stuck utility dialogs,
debugging replay hangs and recording a dump through wsdbg, syntax-checking the
RISC-V/LoongArch JITs and the no-SIMD build without a cross toolchain,
non-deterministic games in screenshot comparisons, and --dump-file.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lets a dump replay stopped with --replay-end be compared against the PSP on
an offscreen buffer. The framebuffer commands after the draw limit are
dropped too, so it shows the buffer the last drawn primitive went to.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Document pspautotests' ppdmp-playback and its new run.py, which replays a
frame dump over PSPLink and compares it with PPSSPPHeadless.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Under --sync, input.buttons.press while stepping waited out the whole timeout,
and so did a cpu.resume issued to get it going. With :nowait the press is sent
and the next line (a cpu.runUntilTime) runs the CPU. Unanswered tickets are
now tracked in one place, so a reply that arrives during a later wait still
counts for :quit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PreferredToolArchitecture has no effect; the MSBuild.exe that runs decides
the compiler host. The earlier note claimed otherwise, from a build that
passed by luck.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
:screenshot saves gpu.buffer.screenshot as a PNG without dumping the data
URI into the output. The docs claimed nested parameters need a raw JSON
line, which gets no ticket; a single-quoted JSON value in key=value form
works and keeps it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rebuilding one test with the current SDK while its neighbours keep old
binaries hides toolchain differences until someone happens to touch
them, like the tests/intr module manager stubs that did nothing. So the
policy is now to rebuild every .prx in the directory and re-record them
all, diffing each .expected against the old one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When sceAudioOutputBlocking has to wait and the wait fails at once
(interrupts or dispatch disabled, inside an interrupt), the firmware
returns the error but leaves the channel's waiting flag set, and the
channel can never be used or released again. Keep the error, drop the
rest: whether the channel was busy at that moment is timing, and a
small difference in ours could lose a channel for the rest of a game
where hardware wouldn't. No game can depend on losing one.
Savestates made while this was emulated have the flag cleared on load.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>