Commit Graph
48992 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 0edfd93a3a Depth raster: Stop a row once it has left the triangle
The triangle is convex, so once a row has been entered and left, the
rest of it is outside. This cut cycles by 5% in Wipeout Pure, where depth
raster is on for the lens flares.

The test checks the covered pixels against a per-pixel edge test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 10:14:54 +02:00
Henrik RydgårdandClaude Opus 5.5 83639a73e8 GPU: Cull-test draws four vertices at a time
TestBoundingBoxFast transformed and tested one vertex per iteration,
with a scalar divide and a min/max chain each vertex had to wait on.
It now transposes four vertices into lanes and does it all as vectors,
with the same operations in the same order, so the results don't change.
It was 16.6% of the emulator thread in Wipeout Pure (with depth raster
off) and 5% in GTA: Vice City Stories.

Per vertex, it's 1.8x faster on an M1, 2x on x86-64, 1.7x on a Galaxy S8+
and 10% faster in 32-bit mode there.

The test compares it with the old version on random draws in every
position and index format.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 10:14:51 +02:00
Henrik RydgårdandClaude Opus 5.5 50ef294c97 CrossSIMD: Load 8-bit vertex components without assuming alignment
LoadS8Norm and friends read four bytes through a u32 pointer. On ARM32
the compiler turns that into a NEON load that requires 4-byte alignment,
and 8-bit positions can sit at any byte offset in a vertex, so
TestBoundingBoxFast could crash with SIGBUS (seen in a unit test on an
S8+ in 32-bit mode).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 10:14:43 +02:00
Henrik Rydgård 9458e8e8ac Merge pull request #22573 from hrydgard/remove-fakejit
Remove FakeJit and the pre-IR JitBlockCache
2026-10-15 09:47:41 +02:00
Henrik RydgårdandClaude Opus 5.5 2813c8117c AGENTS.md: Update submodules when starting work
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 02:30:51 +02:00
Henrik RydgårdandClaude Opus 5.5 f3c69fc5d1 Remove FakeJit and the pre-IR JitBlockCache
FakeJit was the native JIT for architectures without one, but the config
already forces those onto the IR interpreter, so CreateNativeJit() now
returns that instead. The vertex decoder fallback no longer needs
FakeEmitter.

JitBlockCache was the block cache of the pre-IR JITs. Its header keeps
the debug interface types the IR block caches use. Also removes the
JitInterface link hooks only it called, and the JitState fields tied to
it. This fixes a null dereference in the ImDebugger memory section,
which read GetBlockCache(), always null with the IR JITs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 02:24:25 +02:00
Henrik Rydgård f6aea4c85c Merge pull request #22572 from hrydgard/ci-alpine-cache
CI: Make the Alpine job's caches save
2026-10-15 01:18:26 +02:00
Henrik Rydgård f672916c77 Merge pull request #22568 from hrydgard/accurate-vdot-ir
Accurate VFPU dot products in every IR backend, on by default
2026-10-15 00:38:49 +02:00
Henrik Rydgård cfbc5cdef9 Merge pull request #22571 from hrydgard/vrnds-seed-from-vs
VFPU: vrnds takes its seed from the vs field
2026-10-15 00:38:00 +02:00
Henrik RydgårdandClaude Opus 5.5 679026bf53 CI: Make the Alpine job's caches save
BusyBox tar rejects the --posix that actions/cache passes it, so neither
the ccache nor the musl FFmpeg build was ever saved, and every run built
both from scratch (ccache hit 3 of 1005 calls). Install GNU tar and zstd,
and give the ccache a key of its own like the other jobs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 00:35:50 +02:00
Henrik Rydgård 831d8f4654 Merge pull request #22569 from hrydgard/readback-shadow
Add a ReadbackShadow compat flag, for games that read many pieces of a buffer
2026-10-15 00:10:06 +02:00
Henrik RydgårdandClaude Opus 5.5 961726dc88 VFPU: vrnds takes its seed from the vs field
The toolchain encodes vrnds's operand in vs and leaves vd zero, so we
always seeded from S000. Resistance: Retribution reseeds per particle
from S620, which made every particle in its smoke and explosions take
new random values each frame. They flickered, and some never drew.

Hardware test: cpu/vfpu/vrnd (vs and vd fields).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 00:09:25 +02:00
Henrik Rydgård 3bc602033e Merge pull request #22570 from hrydgard/depth-raster-region-clip
Depth raster: Clip to the drawing region as well as the scissor
2026-10-14 23:44:17 +02:00
Henrik RydgårdandClaude Opus 5.5 fe61574a02 D3D11: Include CommonWindows.h before the Windows SDK headers
DrawEngineD3D11.cpp started with <wrl/client.h>, which brought in windows.h
without NOMINMAX, so its min macro broke the std::min in an inline function
of FramebufferManagerCommon.h. D3D11Util and ShaderManagerD3D11 had the same
order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:36:45 +02:00
Henrik RydgårdandClaude Opus 5.5 6757fdc128 x86-64 IR JIT: Address the dot routine's operands from CTXREG's offset
CTXREG points at mips->f[0] in the x86-64 IR JIT, 128 bytes into MIPSState,
but the dot routine's inf and NaN path stored its operands at
offsetof(MIPSState, vfpuDotArgs) from it. It read them back from the same
place, so the results were right, but it wrote over invalidateAll_ and 16
bytes past the end of mipsr4k. On Alpine that crashed the unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:10:59 +02:00
Henrik RydgårdandClaude Opus 5.5 5d0873d95d Depth raster: Clip to the drawing region as well as the scissor
Resistance: Retribution sets its scissor to end at 480,272, one past its
drawing region. The CPU depth rasterizer (SoftwareRasterDepth) only
clipped to the scissor, so it wrote row 272 of the depth buffer, which is
where the game keeps its logo textures. The startup logos got a garbage
strip on hardware backends and failed to fade out.

SoftGPU already clips to the minimum of the two.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 23:10:54 +02:00
Henrik RydgårdandClaude Opus 5.5 20530d35c0 Add a ReadbackShadow compat flag, for games that read many pieces of a buffer
Since the sequential copies heuristic went (50696c5b1f), every partial readback
waits for the GPU. Motorstorm reads parts of the screen about 18 times a frame,
and ran about 75% slower than 1.20.4: 5403 blocking readbacks in 10 emulated
seconds, against 156.

With the flag, the first partial color readback of a buffer reads all of it into
a copy outside PSP memory, and the pieces until the next render to the buffer
are copied from there. Unlike the old heuristic, only what the game asked for is
written to PSP memory. Motorstorm is back to 153 blocking readbacks and 1.20.4's
speed, with identical output. Grand Knights History, which the heuristic was
added for, gets the flag too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:56:32 +02:00
Henrik RydgårdandClaude Opus 5.5 136e6ca927 IR: Include prefix constants past the vector size in the accurate dot products
On the PSP, lanes past the vector size where both the S and T prefixes are
constants add their product to vdot, and an S constant there is added in by vfad
and vavg (cpu/vfpu/prefix_lanes). Without AccurateVdot, the IR leaves those to
the interpreter; the accurate dot products now include them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:37 +02:00
Henrik RydgårdandClaude Opus 5.5 6207ef9ec7 x86-64 IR JIT: Call the dot routine with the operands in registers
The operands go in xmm0 and xmm1 and the result comes back in edi. The routine
saves nothing: the call site flushes whatever guest state is in the registers it
clobbers, the xmms allocated last and rax, rcx, rdx, rsi and rdi, the last two
never allocated outside Windows. 32-bit x86 keeps going through the IR
interpreter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:37 +02:00
Henrik RydgårdandClaude Opus 5.5 bc7afa8845 ARM32 IR JIT: Call a NEON dot routine for the accurate dot products
After clang's ARMv7 output for vfpu_dot_neon(), in Q8-Q15, which the register
cache leaves alone. The end is done in 32-bit integers instead of 64-bit ones,
checked bit-exact against vfpu_dot_finish(). The call site gathers the lanes into
Q8 and Q9 and flushes whatever guest state is in R2, R3, R12 and LR, usually
nothing. Inf and NaN go through a routine that calls vfpu_dot_reference().
Without NEON, Vec4VfpuDot still goes through the IR interpreter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:37 +02:00
Henrik RydgårdandClaude Opus 5.5 bd54c3e659 Compat: Replace MoreAccurateVMMUL with ForceAccurateVdot
The games that needed vmmul's accurate dot products (Tekken 6, Warriors Orochi,
Tomb Raider Anniversary) now get AccurateVdot for every dot product, even with
the setting turned off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:37 +02:00
Henrik RydgårdandClaude Opus 5.5 0e77a2d1cd VFPU: Make AccurateVdot the default
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>
2026-10-14 22:30:37 +02:00
Henrik RydgårdandClaude Opus 5.5 0b4b70e67e ARM64 IR JIT: Call the dot routine with the operands in registers
The operands go in q0 and q1 and the result comes back in w1. The routine saves
nothing: the call site flushes whatever guest state is in the few registers it
clobbers (q4-q6, w0-w3), which come late in the allocation order, so usually
there's nothing to do. That's about 20 bytes a call, with nothing going through
memory, and as fast as having the whole thing inline, which made a vmmul.q about
5 KB of code. Inf and NaN go through a routine that calls vfpu_dot_reference().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:37 +02:00
Henrik RydgårdandClaude Opus 5.5 27ef4e3c09 x86-64 JITs: Make the VFPU dot routine SSE2, after vfpu_dot_sse2
Inf and NaN still go to vfpu_dot_reference().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 605057864e ARM64 emitter: Add vector BIC, and use it for the dot routine's normal mask
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 3e1198bf22 ARM64 JITs: Make the VFPU dot routine NEON, after clang's vfpu_dot_neon
It was integer code, about four times the work. It also clobbers q0-q3 and
x16/x17 now, which both ARM64 JITs keep free, instead of saving them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 bebe527e48 ARM64 emitter: Add vector ADD/SUB, UMAXP, USHL, UMULL/UMULL2, ADDV and USRA
The disassembler now decodes the across-lanes and three-different groups,
which it only printed as raw words. The encodings match clang's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 fa25caf905 x86-64 IR JIT: Run Vec4VfpuDot through a shared routine
GenerateX64VfpuDot() is the x86-64 counterpart of GenerateArm64VfpuDot(): the
same integer version of vfpu_dot(), preserving every register but the flags,
with inf and NaN operands going to vfpu_dot_reference(). A dest lane inside a
source Vec4 still takes the generic fallback, like Vec4Dot does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 8ac395c566 ARM64 IR JIT: Run Vec4VfpuDot through a shared routine
GenerateArm64VfpuDot() generates vfpu_dot() as integer code, once per JIT, to
be called with BL. The operands and result go through mips->vfpuDotArgs and
vfpuDotResult, and the routine preserves every register but x30, so a call
site is two stores, the BL and a load, with nothing flushed. Inf and NaN
operands go to vfpu_dot_reference(), with the caller-saved registers saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 dc417ca7cc CPUEquivalence: Add PPSSPP_CPUFUZZ_ACCURATE_VDOT
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 bbe2d79bea IR: Add Vec4VfpuDot, the VFPU's own dot product, and use it with AccurateVdot
The frontend lowers vdot, vhdp, vfad, vavg, vdet, vcrsp/vqmul, vtfm/vhtfm and
vmmul to it then, with the operands built from the same lanes, constants and
negations the interpreter feeds vfpu_dot(). BuildDotOperands() puts them in
Vec4 regs, using the free temps where they aren't one already, and the ops
fall back to the interpreter when there aren't enough. The IR interpreter runs
it through vfpu_dot(), and the native backends through their generic fallback
for now. vmmul takes the same path for the MoreAccurateVMMUL compat flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 23fd6b4546 Interpreter: Use the accurate VFPU dot product when AccurateVdot is set
vdot, vhdp, vfad, vavg, vdet, vtfm/vhtfm, vcrsp/vqmul and vmmul compute
through vfpu_dot() then, as the PSP does. vmmul still also takes it from the
MoreAccurateVMMUL compat flag. With it, signed_zero matches the PSP, and
fcr31, nan_gen and specials get closer. What's left there is the single-lane
ops' NaNs and the VFPU's rounding.

vcrsp.t no longer zeroes infinities before its last dot: inf * 0 gives the
VFPU's NaN there on the PSP (cpu/vfpu/nan_gen). The NaN and denormal fixups in
vadd, vsub, vmul and vdiv shared the flag but aren't dot products, so they
get their own (still off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik RydgårdandClaude Opus 5.5 1952d20805 Config: Add AccurateVdot, and --accurate-vdot to set it
Not wired to anything yet. It will make every core compute the VFPU's dot
products (vdot, vmmul, vtfm and the rest) bit-exactly like the PSP, through
vfpu_dot().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:36 +02:00
Henrik Rydgård 441e0b7a56 Merge pull request #22567 from hrydgard/cpu-generic-fixes
CPU: Generic fixes and cleanup
2026-10-14 22:30:08 +02:00
Henrik RydgårdandClaude Opus 5.5 5633263580 x86-64 IR JIT: Keep NaNs through vsat0 and the [0:1] prefix
FSat0_1's MAXSS had the known 0 second, so a NaN came out as 0, unlike the
interpreter and the other backends, and the PSP (cpu/vfpu/prefix_sat, nan_gen).
-0.0 is now cleared by a compare and mask rather than by adding 0.0, which would
quiet a signaling NaN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:05:21 +02:00
Henrik Rydgård 0b557116c2 Merge pull request #22561 from eraycep/fix-sdl-input-pixel-density
SDL: Use window pixel density for input coordinates
2026-10-14 21:46:36 +02:00
Henrik RydgårdandClaude Opus 5.5 124f86d7d0 VFPU: Decode prefixes with shared helpers
The prefix fields were decoded by hand in the interpreter, the IR frontend,
JitState, MIPSState and the disassembler, with three copies of the constant
table. They now share small decoders and one table, in MIPS.h. The backends'
Vec4Shuffle lane decoding gets a helper of its own in IRInst.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:34:05 +02:00
Henrik RydgårdandClaude Opus 5.5 66b26d08c1 IR: Leave the dot products with prefix constants past the vector size to the interpreter
On the PSP, lanes past the vector size where both the S and T prefixes are
constants add their product to vdot, and an S constant there is added in by vfad
and vavg (cpu/vfpu/prefix_lanes). The interpreter does that; the IR dropped those
lanes. Such prefixes are rare, so the IR leaves them to the interpreter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:11:03 +02:00
Henrik RydgårdandClaude Opus 5.5 e660fd7d02 ArmDisasm test: Expect the ARMv7 sequences on ARMv7
ANDI2R and ORI2R use UBFX and MOVW/MOVT there, and the NopAlignCode16 check
now starts from a fixed offset, since the code before it is shorter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:06:14 +02:00
Henrik RydgårdandClaude Opus 5.5 a495a915b6 ARM emitter: Let VMOV_imm load ones-filled I_32 immediates
Like 0x7FF and 0x7FFFFF (cmode 1100 and 1101). They were turned away, which
only asserts in debug builds and emits nothing in release ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:06:14 +02:00
Henrik RydgårdandClaude Opus 5.5 d59aa01341 CPU fuzzer: Add an instruction filter, and a script to run it in parallel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:06:14 +02:00
Henrik RydgårdandClaude Opus 5.5 d0266580d9 CMake: List the headers that were missing
Each next to its .cpp, or with its neighbours in the unconditional list for
its directory. They're only for IDEs, so this changes nothing in the build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:05:52 +02:00
Henrik Rydgård 4c2bb35630 Merge pull request #22564 from hrydgard/arm32-ir-jit
ARM32: Replace the legacy JIT with an IR-based one
2026-10-14 19:18:54 +02:00
Henrik Rydgård a15e0caa31 Merge pull request #22566 from hrydgard/fix-armv7-anycomparebits
CrossSIMD: Fix AnyCompareBitsSet on ARMv7
2026-10-14 18:55:14 +02:00
Henrik Rydgård b05f21f5be Merge pull request #22565 from hrydgard/dnas-decrypt
Load game patches installed on a PSP, given its Fuse ID (Killzone: Liberation DLC)
2026-10-14 18:42:41 +02:00
Henrik RydgårdandClaude Opus 5.5 e920666d72 ARM32 IR JIT: Pass float helper arguments the softfp way where needed
Android's armeabi-v7a uses the softfp calling convention, where floats travel in R0/R1, not S0/S1.
The math helper calls (vfpu_sin and the rest) only did the hard float version, so on Android they
got garbage and returned it (broken 3D in Virtua Tennis: World Tour). Like the old ArmJit, check
__ARM_PCS_VFP. With an armel softfp build under qemu, the fuzzer found 41 of 3000 cases where the
JIT differed from the IR interpreter before this, and none after.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 18:23:00 +02:00
Henrik RydgårdandClaude Opus 5.5 8cefdef5a0 CrossSIMD: Fix AnyCompareBitsSet on ARMv7
The 32-bit NEON fallback ANDed the lanes together like AllCompareBitsSet, so it only reported a
set lane when all of them were set. The software renderer's clip checks (GEMath.h) use it to ask
whether any vertex is outside, so on ARMv7 partly off-screen triangles skipped clipping and drew
as long spikes (Virtua Tennis: World Tour in-game, with any CPU core). Also fixes the CrossSIMD,
GEMath and SplineTessellation unit tests there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 18:18:58 +02:00
Henrik RydgårdandClaude Opus 5.5 634b817869 Tools: Script to connect PSPLink to WSL
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>
2026-10-14 18:10:08 +02:00
Henrik RydgårdandClaude Opus 5.5 edef36d97e Load game patches installed on a PSP, given its Fuse ID
- 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>
2026-10-14 18:10:08 +02:00
Henrik RydgårdandClaude Opus 5.5 121b67604c Implement sceKernelLoadModuleDNAS
See #12343

Killzone: Liberation's bootloader loads its DLC patch from
ms0:/PSP/GAME/<id>/KZL.PRX through this, and falls back to the disc's
KZL.PRX on any negative result. The stub returned 0, which the game took
as a module ID and crashed starting it.

The installed file is keyed to the PSP that installed it (KIRK command 2,
whose key isn't known), so we can't decrypt one copied from a PSP. Unwrap a
PGD we can open with the caller's key and load a module that's already
decrypted; otherwise fail with an OSD note, and the game runs without the
patch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 18:09:58 +02:00