The debris smoke trails add a node on every update, so at 60fps the smoke was twice as dense
as at 30fps and spawned twice as many trails. Keep the nodes 1/30 of the effect's clock apart
instead. In the angel boss explosion the display list now peaks at 0x483ec bytes without the
overflow guard's help, against 0x637f4 before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#19755
The game adds smoke trail nodes per update but expires them by time, so the faster we run, the
more trails are alive. After the angel boss in Bonn, the debris trails grow the display list
past its 0x5DC40 byte buffer at 60fps. The overflow lands in the music stream's Atrac buffer
(the "Invalid start bit" errors), corrupts textures, and crashes the game. Locking the CPU to
222MHz, the known workaround, slows the frame rate enough to avoid it.
Hook the trail draw and skip a trail when the current list buffer, whose limit the game keeps
next to its write pointer, is nearly full.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A module the game loaded before the state was saved, but that this run never loaded, was never
scanned, so loading such a state left all of its code without function replacements and hooks
(and without functions in the debugger). Scan any loaded module with no analyzed functions in its
text range before rehashing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JIT memory is now mapped with MAP_JIT, and pthread_jit_write_protect_np()
switches the calling thread's view of it, in place of two mprotect() calls
for every block compiled and every exit linked. This makes compiling
cheaper, which matters mostly in the first seconds after a game or state
loads. A hardened runtime without the JIT entitlement refuses the mapping,
and then it's mprotect() as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As on hardware (cpu/cpu_alu/branch_link_ra), with ra as rs the branch
sees its old value, not the return address. Every core linked first.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With a D prefix the result goes through temps, and a lane the condition
doesn't move kept whatever the temp held. They now start as d. That
exposed OptimizeFPMoves renaming the src3 of FCmovVfpuCC, which is also
its destination, so it no longer replaces a register an op reads as src3
and writes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A store at a negative offset whose end wraps past zero, like 16 bytes at
-16, counted as ending before a load at -12, so the load reused an older
value from before the store.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FlushNativeReg left a register caching MIPS zero mapped, as there's
nothing to store, but its callers flush to clobber the register. After
the dot product routine's call, a store of zero stored its result.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They wrote each lane straight to the destination, so with a destination
that holds a later lane's source (a wrapped row over a column, say) that
lane read the converted value.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Like vmscl, they flipped the transposed bit to get Vec4s, which names
other registers for a matrix that wraps around. Taking the elements in
transposed order does it without.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
OptimizeFPMoves replaced a temp read by vs2i (from a copy of a lane of
its own destination) with the original register, which the ARM64 JIT
then lost by mapping the destination without loading it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They read one lane, the first of the source vector at the size in the
instruction, as the interpreter does, which for the pair and quad
encodings is another register than the single the IR took.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As on hardware (cpu/vfpu/vfim), a denormal half becomes a signed zero,
and inf and NaN keep their mantissa bits in place. The interpreter got
denormals half their value, the IR the IEEE one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both were turned into their untransposed forms to get Vec4s, but for a
matrix whose offset makes it wrap around, flipping the transposed bit
names other registers. Taking the elements of both in transposed order
gets the same Vec4s without that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As on hardware (cpu/vfpu/prefix_load), lv.q, lvl.q and lvr.q write every
lane whatever mask a pending D prefix has, and leave the prefix pending,
like lv.s and mtv already did. The IR cores fall back to the interpreter
for lvl.q and lvr.q, so they get this too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Game runs in headless measured the C++ vertex decoder, which the app only
uses where it can't JIT. pspautotests and the frametests pass with the
JIT decoders. --vertex-jit=false brings back the C++ decoder.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>