Commit Graph
47841 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 74c5cbf503 Savedata: Leave GetSize's needed strings alone when nothing is needed
Matches hardware; moves utility/savedata/getsize to tests_good.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:25:24 -06:00
Henrik RydgårdandClaude Opus 5.5 20970d5fad Dialog: Follow the firmware's thread priorities in init/shutdown
On a PSP, dialog init and shutdown happen partly at the accessThread
priority and partly at the graphicsThread priority, one phase after the
other. Model that with one helper thread that switches priority per phase,
and let starting it reschedule normally instead of disabling interrupts.

A caller with worse priority than both now sees shutdown complete inside
ShutdownStart, as on hardware. NFL Street 3 (graphics 17, access 19,
caller 111) calls the next InitStart right after ShutdownStart and used to
loop forever on 'A save request is already running' (#19957). The utility
pspautotests, where the caller has better priority, are unchanged.

Also logs the dialog thread priorities at debug level.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:25:24 -06:00
Henrik RydgårdandClaude Opus 5.5 24e44cb5dd CmdLine: Exit with an error on a bad option value
An invalid value (e.g. --debugger-run swallowing the next flag) printed an
error but returned Exit, so the process exited 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:25:24 -06:00
Henrik RydgårdandClaude Opus 5.5 a293221fe2 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) <noreply@anthropic.com>
2026-09-23 19:25:24 -06:00
Henrik Rydgård 09cf031f2b Merge pull request #22342 from hrydgard/vdot-neon
Accurate vdot: NEON and SSE2 implementations
2026-09-23 19:24:16 -06:00
Henrik Rydgård f65598c60f Merge pull request #22341 from sum2012/gpu-readback
Add ForceEnableGPUReadback compat By Gemini
2026-09-23 09:02:45 -06:00
Henrik RydgårdandClaude Opus 5.5 0ac12b7bf3 VFPUDot: Cover sums that round into the next power of two
A sum just below a power of two can round up into the next exponent, and
at the top of the range into inf; random inputs almost never land there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:18:25 -06:00
Henrik RydgårdandClaude Opus 5.5 cd005b9041 VFPU: SSE2 version of the exact vdot
Same shape as the NEON one, which now shares its rounding tail. SSE2 has
no per-lane shift, so the alignment shift is a multiply by a power of two
built from float bits and converted by truncation; the unsigned maxima
use the 16-bit instructions, since every value involved fits in 15 bits.
Nothing depends on the host rounding mode or flush-to-zero.

Checked against the reference by VFPUDot and on 300M more inputs offline,
also with MXCSR set to round toward zero with FTZ and DAZ.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:18:25 -06:00
Henrik RydgårdandClaude Opus 5.5 649561764d VFPU: NEON version of the exact vdot
The four lanes are computed together: exponents, 24x24-bit products with
round-to-odd, alignment by truncation and a signed horizontal sum. One
pairwise maximum finds both the alignment exponent and any inf or NaN,
which go to the reference. The final rounding is branch-free and in
integers, since the host rounding mode may be the game's.

About three times the throughput of the reference on Apple M-series
(5.2 vs 15.2 ns per call). VFPUDot checks it against the reference on
four million inputs picked to cover cancellation, ties, subnormals and
the overflow edges; a billion more matched offline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:18:25 -06:00
sum2012 a8b85eec8b Add ForceEnableGPUReadback compat
To solve gpu readback issue
2026-09-23 22:06:28 +08:00
Henrik Rydgård fa583d1d33 Merge pull request #22340 from sashwatpuri/fix/audio-file-duration-limit
Limit achievement sound effect duration
2026-09-22 21:39:19 -06:00
Sashwat Puri Sachde 805b9032e3 Limit achievement sound effect duration 2026-09-23 04:40:49 +05:30
Henrik Rydgård 7c18ad3d1e Merge pull request #22338 from hrydgard/video-texture-hash
Video texture tracking and avoiding hashing
2026-09-22 16:50:34 -06:00
Henrik Rydgård 818c08be4c Merge pull request #22337 from hrydgard/cpu-test-holes
Claude with pspautotests: CPU test holes
2026-09-22 16:19:17 -06:00
Henrik RydgårdandClaude Opus 5 772f4f6e7a AGENTS.md: note that headless needs the app's memstick for firmware runs
Without --memstick, a library whose HLE the config has disabled has no firmware
to resolve against, and the game dies on unresolved imports rather than falling
back to our HLE. The run still logs happily for its whole timeout, so the only
tell is zero flips.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:10:52 -06:00
Henrik RydgårdandClaude Opus 5 4aef060293 Carry "this is video" across block copies
videos_ only learns about the CSC output, so the texture we actually draw is
just an ordinary 512x512 8888 texture whose contents happen to be different
every frame: hash, miss, throw it in the secondary cache, rebuild, forever.

So track the copy. NotifyVideoCopy marks the destination as video when the
source is, and the copy funnels call it: sceDmacMemcpy, sceKernelMemcpy, and
the four replaced memcpy/memmove variants. It sits outside their "is either
side VRAM" gate, since a RAM-to-RAM copy of a frame is still a frame.

Being a video texture also gets it forced linear filtering and keeps it out of
texture upscaling, which is what you want for a video either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:10:28 -06:00
Henrik RydgårdandClaude Opus 5 aa7b1de28c Drop the texture replacer's "video" option
A `video = true` in textures.ini opted a pack into replacing and dumping video
textures. Both halves are a bad deal. Dumping writes a file per decoded frame,
which fills a disk rather than producing anything a pack can use, and replacing
means a hash lookup on content that is different every frame and will never be
found twice.

It was also the only reason the texture cache still hashed video textures at
all, so it cost every game that has ever played a cutscene, not just the packs
that set it.

Video textures are now never replaced and never dumped, and skipHash is simply
isVideo. An existing ini keeping the key is harmless - unknown options are
ignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:10:16 -06:00
Henrik RydgårdandClaude Opus 5 14bfd332dc Stop stacking duplicate video entries, and stop hashing video textures
NotifyWriteFormattedFromMemory appends to videos_ unconditionally. A game
blitting its decoded frame to the display buffer does that every displayed
frame while it waits for the next one to decode, so the same two or three
addresses come back over and over: Death Jr pushes 733 entries where there are
two distinct buffers, Tekken 6 around 53,000 where there are three. IsVideo()
walks that vector linearly on every texture. Refresh the matching entry instead
of appending a new one - Death Jr now holds 2 entries and Tekken 6 holds 18,
peak size 2 and 3.

The other half is the two TODOs that were already sitting there. A video
texture is new every frame by definition, so re-hashing it only confirms what
the VIDEO flag already said, and the secondary cache has nothing to offer a
frame that will never recur. Skip both, and with them the secondary lookup that
would otherwise key off a hash we no longer compute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:10:00 -06:00
Henrik RydgårdandClaude Fable 5.1 43255afe9f test.py: cpu/fpu/roundmode is a known failure on riscv64
The ISA returns the canonical NaN from every operation, so a negative or
signaling NaN operand loses its sign and payload where the PSP keeps them.
Not worth a check per FP op in the JIT.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 15:04:05 -06:00
Henrik RydgårdandClaude Fable 5.1 5d35425acd Add cpu/fpu/roundmode, fpu_branch and cpu/lsu/llsc to tests_good
fpu_branch_hazard (the compare-to-branch hazard), cacheop (the write-back
data cache seen through the uncached mirror) and fpu_nan (which NaN 0/0
makes, host dependent on x86) go to tests_next. cpu/fpu/fpu is re-recorded
from a binary built with the current toolchain, which prints -nan.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:25:42 -06:00
Henrik RydgårdandClaude Fable 5.1 2d14cad065 FPU: sqrt.s of a negative gives the PSP's positive NaN
x86 (SQRTSS and libm alike) returns 0xffc00000 for it, the PSP 0x7fc00000.
-0 stays -0 and a NaN input comes through as it is, so only a negative
input needs the sign cleared: a compare and an xor in the x64 JITs, a
branch in the interpreters. cpu/fpu/roundmode.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:25:41 -06:00
Henrik RydgårdandClaude Fable 5.1 fc9dbf5ff5 FPU: saturate float-to-int in the interpreters
The C cast is undefined past the int32 range, and x86 makes it INT_MIN, so
round/trunc/ceil/floor/cvt.w.s of anything from 2^31 up gave 0x80000000 on
x86 hosts while the PSP saturates to 0x7fffffff (cpu/fpu/roundmode). Route
all of them through SaturatedFloatToInt, which also covers NaN and inf, and
drop the special cases that did.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:25:41 -06:00
Henrik RydgårdandClaude Fable 5.1 dc983bb1c5 Add cpu/vfpu/specials and overlap_vcrsp to tests_next, update overlap
specials stays in tests_next for vcmp on denormals and the NaN
canonicalization and denormal flush in vbfy/vocp/vavg/vfad/vsocp, which
overlap the USE_VFPU_DOT accuracy switch. overlap_vcrsp is vcrsp with an
overlapping destination, which the assembler refuses and the hardware
doesn't read-before-write for.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik RydgårdandClaude Fable 5.1 613422c15f IR: vi2s/vi2us with the destination inside the source
The second pack read a lane the first one had just written
(vi2s.q C002, C000). Pack into temps when the outputs overlap the inputs.
Found by the corrected cpu/vfpu/overlap.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik RydgårdandClaude Fable 5.1 7d69ba76f2 x64 IR JIT: fix vcmp EN/NN, NI and TR
EN/NN compared the source against an uninitialized temp, usually the
previous lane's all-ones mask (a NaN), so every lane came out as NaN. NI
used a less-than compare, which is false for a NaN. TR set the bit and
then replaced it with a garbage bit from the temp. Found by
cpu/vfpu/specials.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik RydgårdandClaude Fable 5.1 b16cd9a734 VFPU: vsgn of a denormal is zero
In the interpreter, the IR interpreter, every backend's FSign and the x86
JIT's own vsgn. Recorded in cpu/vfpu/specials.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik RydgårdandClaude Fable 5.1 2c801b8a31 VFPU: vsrt1-4 order NaN, denormals and signed zero like the hardware
The same ordering as vmin/vmax (sign and magnitude, denormals compare as
zero), and on a tie vsrt1/2 keep the lower lane of each pair, vsrt3/4 the
upper one. Recorded in cpu/vfpu/specials.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik RydgårdandClaude Fable 5.1 ed6d13e15f VFPU: bit-exact vh2f and vf2h
vf2h truncates the mantissa (no rounding), gives a signed zero below 2^-14
and a signed inf from 65536 up, and keeps the low ten mantissa bits of a
NaN, so one with those clear becomes inf. vh2f flushes a subnormal half to
a signed zero and keeps inf/NaN mantissa bits unshifted. The x86 JIT's own
vh2f gets the subnormal flush; the other backends go through the
interpreter. Recorded in cpu/vfpu/specials.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik RydgårdandClaude Fable 5.1 5564ef3ec3 Add cpu/vfpu/overlap to tests_good
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik RydgårdandClaude Fable 5.1 a6e29f6972 Add cpu/vfpu/vrnd to tests_good
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik RydgårdandClaude Fable 5.1 55672ebd99 Add cpu/vfpu/vbranch to tests_good and vbranch_hazard to tests_next
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik RydgårdandClaude Fable 5.1 ae81a20c74 arm64 JIT: mtvc to the VFPU condition register keeps six bits
The other control registers were masked already; the CC path wasn't, so
a value with bits 6 or 7 set made bvt 6 and bvt 7 branch. Hardware never
sets those (cpu/vfpu/vbranch), and the IR masks them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:10:22 -06:00
Henrik Rydgård ffc32d02fe Merge pull request #22336 from hrydgard/misc-work
Code cleanup, add a dismissable reminder about the 32-bit version on a 64-bit OS.
2026-09-22 13:15:12 -06:00
Henrik Rydgård c14455ef11 Merge pull request #22334 from hrydgard/div-overflow-hi
Fix issues with div overflow and VFPU prefixes
2026-09-22 13:14:43 -06:00
Henrik RydgårdandClaude Fable 5.1 5e5ecc5389 x64 IR JIT: A NaN in the second operand of vmin/vmax took the fast path
The NaN check compared src1 with itself, so only a NaN in src1 reached
the slow path with the hardware's integer-compare rule. A NaN in src2 went
to MINSS/MAXSS, which just return it. Compare src1 with src2, as the arm64
backend does. Caught by cpu/vfpu/minmax.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 12:49:06 -06:00
Henrik RydgårdandClaude Fable 5.1 9f003f7773 x86 JIT: Gate the VFPU ops with odd prefix rules the way the IR does, and mask mtvc
The same set of gates the arm64 JIT got: vabs/vneg, vsat1, the vrcp
group, vzero/vone/vidt, vdiv, vscl, vfad/vavg, vrot, vuc2i, and now also
vcrs/vcrsp/vqmul, vbfy and vtfm, which the IR frontend hands to the
interpreter as well. Swizzles naming a lane past the op's size go there
too. mtvc now keeps only the bits the hardware does (six for CC, the low
20 of a prefix, 0x3F8 on top of the RNG state). vcst's SIMD path skipped
the D prefix.

Fixes cpu/vfpu/prefix_ctrl on Windows CI. Verified on an x86_64 build
under Rosetta: prefix_ctrl and the full tests_good pass, prefix_consume
is down to the same three ops that differ on every backend.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 12:49:06 -06:00
Henrik RydgårdandClaude Fable 5.1 8d94b245ad Headless: Key the no-SDL path off HEADLESS_CROSS, not the architecture
The riscv64/loongarch64 cross builds have no SDL, and Headless.cpp had a
matching "no window" path gated on those two architectures. Gate it on
the option instead (a HEADLESS_NO_SDL define), so any headless-only build
without a usable SDL gets it - an x86_64 build on an arm64 Mac, say, where
Homebrew's SDL3 is arm64 only. That's how the x86 JIT fixes here were
tested, under Rosetta.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 12:49:06 -06:00
Henrik RydgårdandClaude Fable 5.1 783892ed25 VFPU: Writing an RNG state register keeps 0x3F8 in the top bits
mtvc to RCX0-7 keeps the low 20 bits of the value and forces the top
twelve to 0x3F8 (cpu/vfpu/vrnd), which is also the form vrnds and vrndi
leave them in. We masked with 0x3FFFFFFF. The generator itself matches
hardware for every seed and state in that test.

Interpreter, IR and the arm64 JIT. The x86 and ARM32 JITs don't mask
control register writes at all and are left as they were.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 12:49:06 -06:00
Henrik Rydgård 85ed46d97e Add new translation string 2026-09-22 12:42:05 -06:00
Henrik RydgårdandClaude Fable 5.1 ba491301ae Add cpu/vfpu/prefix_unpack to tests_next
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:45:25 -06:00
Henrik RydgårdandClaude Fable 5.1 4e0319ffd9 test.py: cpu/vfpu/prefix_ctrl passes now
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:37:49 -06:00
Henrik RydgårdandClaude Fable 5.1 960514d63a arm64 JIT: Gate the VFPU ops with odd prefix rules the way the IR does
The arm64 JIT applied the plain prefix rules to every op. Several ops
don't follow them, and the interpreter and IR frontend know which:

- vabs and vneg are vmov with the abs/negate bit forced, so a negate in
  the S prefix doesn't negate twice.
- vsat1 doesn't apply the D saturation.
- vrcp, vrsq, vsqrt, vnrcp (and the rest of that group) apply S and D to
  the last lane only, from prefix position 0.
- vzero, vone and vidt are changed by a pending S prefix.
- vdiv, vscl, vfad/vavg and vrot each have their own rule.
- vuc2i and friends with an S prefix.

Hand these to the interpreter, exactly where the IR frontend does. Also
mask what mtvc writes to a control register (the low 20 bits of a prefix,
12 of the D prefix), as the IR already did.

Goes from 19 ops differing from hardware in cpu/vfpu/prefix_consume to
the 3 that differ on every core, and clears cpu/vfpu/prefix_ctrl.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:37:48 -06:00
Henrik RydgårdandClaude Fable 5.1 591731cb8e IR: A swizzle naming a lane past the op's size isn't "within size"
IsPrefixWithinSize only looked at prefix positions past the size, not at
in-size positions naming a lane past it, so vadd.p with [z, w, ...] was
compiled with whatever the prefix code substitutes. Hardware writes those
result lanes as zero (cpu/vfpu/prefix_ctrl), which the interpreter does,
so send them there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:37:48 -06:00
Henrik RydgårdandClaude Fable 5.1 0af831b5d8 VFPU: An invalid swizzle on the one-lane ops zeroes the result lane
vrcp, vrsq, vsin, vcos, vexp2, vlog2, vsqrt, vasin, vnrcp, vnsin, vrexp2
and vdiv apply the S prefix to their last lane only, taking it from prefix
position 0. When that names a lane other than x, we fed the op an
infinity, which happens to give the right answer for vrcp and vexp2 and
the wrong one for the rest. Hardware writes the lane as zero regardless
of the op (cpu/vfpu/prefix_sat, where vlog2 and vsqrt give zero too),
which is also what RetainInvalidSwizzleST already does for the other ops.

Covers the IR path as well, which hands these cases to the interpreter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:32:42 -06:00
Henrik RydgårdandClaude Fable 5.1 de884aba02 Add cpu/vfpu/prefix_sat to tests_next
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:30:38 -06:00
Henrik RydgårdandClaude Fable 5.1 acb81a049e Add the VFPU prefix tests to tests_next
prefix_branch passes on every core. prefix_ctrl fails on the IR path
(out-of-size swizzle lanes) and the arm64 JIT (that, plus mtvc not
masking). prefix_consume fails everywhere: the interpreter and IR in lane
w of nine ops where the T prefix holds a constant 0, the arm64 JIT on
nineteen ops.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:21:16 -06:00
Henrik RydgårdandClaude Fable 5.1 a5c96e054e Add cpu/vfpu/minmax_tie to tests_next
vmin/vmax return the second operand on a -0/+0 tie. The classic
interpreter does that; the IR interpreter and the JITs return the first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:09:47 -06:00
Henrik RydgårdandClaude Fable 5.1 6e44931f5b test.py: cpu/cpu_alu/cpu_div passes now
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:02:35 -06:00
Henrik RydgårdandClaude Fable 5.1 9f1f1f23c9 arm64 IR JIT: divu by zero compared the numerator signed
The rule is LO = numerator <= 0xFFFF ? 0xFFFF : -1, unsigned. The IR
backend used CC_LE, so any numerator with the top bit set counted as
small and got 0xFFFF. The other backends compare unsigned. Also caught by
cpu/cpu_alu/cpu_div.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:02:20 -06:00
Henrik RydgårdandClaude Fable 5.1 e0f09c55d5 MIPS: INT_MIN / -1 leaves HI at zero, like the hardware
Every path special-cased the one signed division overflow and set the
remainder to -1. cpu/cpu_alu/cpu_div, recorded on a PSP, says it's 0.
The classic arm64 JIT was the only one that got it right, by not
special-casing it at all.

arm64, RISC-V and LoongArch all produce INT_MIN with remainder 0 natively,
so their fixup blocks go away. x86 has to keep the check (IDIV traps) but
sets HI to 0 now, and so do both interpreters.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:02:20 -06:00