Commit Graph
100 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 9d3022642b SoftGPU: Fix bin queue bookkeeping and dependency tracking
- Flush() on an empty queue now trims the state and CLUT rings, since its
  callers flush because one is full and push right after.
- BinQueue::Full() uses >=, so an overshoot can't go unnoticed.
- IsExactSelfRender compares against the target the queued draws were
  binned for, not gstate, which already has the next one during a flush.
- A depth test without depth writes marks the depth buffer as read.
- The DarkStalkers untextured sprite recomputes the binner state around it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 13:16:40 -06:00
Henrik RydgårdandClaude Opus 5.5 74553bfa1a D3D11: Note that the state object caches are deliberately never trimmed
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 12:43:12 -06:00
Henrik Rydgård 1b8553956f Merge pull request #22383 from hrydgard/preemptible-syscalls
Fixes after hardware scheduling tests by Claude
2026-09-29 12:35:27 -06:00
Henrik RydgårdandClaude Opus 5.5 5865a560eb ARM JITs: Mask what ctc1 writes to fcr31
Only the rounding mode, flags, enables, cause, FCC and FS bits can be
written (0x0181FFFF, pspautotests cpu/fpu/fcr), as the interpreter, IR
and x86 already have it. Both ARM JITs stored the whole value.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 11:09:47 -06:00
Henrik RydgårdandClaude Opus 5.5 178186ef4e sceVaudio: Reserving the channel waits about 250us
On hardware sceVaudioChReserve takes ~260us once it gets past the busy
check, succeed or fail, and worse threads can run meanwhile. Releasing
takes ~25us and doesn't wait. We returned at once, which is what
audio/sceaudio/reserve's [r] markers showed; it now passes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 11:09:47 -06:00
Henrik RydgårdandClaude Opus 5.5 69e10728c9 test.py: Add gpu/primitives/indices32 to the to-do list
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 11:09:47 -06:00
Henrik RydgårdandClaude Opus 5.5 af8088b790 Threads: A delay can end before it starts waiting
A delay's deadline is now + usec, and the clock is read again when the
alarm is set. If the deadline has passed by then, the call returns 0 at
once without giving up the CPU. On hardware that makes
sceKernelDelayThread(0) return at once about 60% of the time. On a thread's
first wait after it starts, a delay of 1 does so about two times in three
as well (pspautotests threads/scheduling/delayzero). We always waited at
least 210us.

The choice is pseudo-random off the tick count, not the tick phase, since
our cycle counts are regular enough for a polling loop to lock into never
yielding. Threads remember whether they've waited since starting (Thread
savestate section version 6).

Also moves threads/vpl/create into the passing tests: re-recorded on 6.61,
it agrees with what we do for partitions 8 and 9.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 11:09:47 -06:00
Henrik RydgårdandClaude Opus 5.5 13dee85375 sceIo: host0: behaves like usbhostfs with dispatch suspended
With dispatch suspended, IO fails in the driver when it tries to wait. The
memory stick driver returns SCE_KERNEL_ERROR_CAN_NOT_WAIT, while usbhostfs,
which serves host0: under PSPLink, returns -1. host0: is mostly what
homebrew developers run from, so it now does the same.

Also from threads/scheduling/dispatch, which now passes:
- sceIoRead reports an async operation still in progress before failing
  on suspended dispatch.
- A write to stdout or stderr doesn't give up the CPU.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 11:09:47 -06:00
Henrik RydgårdandClaude Opus 5.5 f2eba8f711 sceKernelLoadModule: Charge for reading the file and loading it
On hardware a load costs an open and a read of the file, plus about 1ms
and 30us per KB of loader work (pspautotests threads/scheduling/callcosts),
with the caller waiting throughout. It now charges sceIoOpen's and
sceIoRead's estimates for the file plus that, instead of a flat 500us.

Also notes why sceKernelLoadModuleByID fails from a game's own fd on ms0:
or host0: on hardware, which we don't emulate.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 11:09:47 -06:00
Henrik Rydgård 806618f9a3 Merge pull request #22382 from hrydgard/gpu-lifecycle-fixes-2
Claude code review: GPU fixes 2
2026-09-29 11:04:38 -06:00
Henrik RydgårdandClaude Opus 5.5 93f57a4f38 GPU: Delete copy operations on classes that own resources
These own GPU objects, memory or refcounts in their destructors (or assert
there that they were torn down), so a copy would double-free. Nothing copies
them today; this keeps it that way. The manager base classes cover every
backend's subclass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:33:32 -06:00
Henrik RydgårdandClaude Opus 5.5 39049a67fd GLES: Free textures on device lost, and unsubmitted step data at exit
- The texture and fragment test caches dropped their GLRTexture objects on
  DeviceLost without queueing them for deletion, leaking them on every
  Android background/resume. The deleter already skips the GL calls when
  the context is gone.
- GLRenderManager::ThreadEnd cleared unsubmitted init and render steps
  without freeing the data they own. Run them through the dry run instead,
  which now also frees stereo matrices and shader code.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:33:32 -06:00
Henrik RydgårdandClaude Opus 5.5 b72927bbeb Texture replacement: Plug leaks on error paths
- Release the file reference when loading a level fails or finds nothing,
  since only a loaded level takes ownership of it.
- Free the PNG image when the size changed since the header was read.
- Delete the VFS when a pack without an ini has no hash-named textures.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:33:32 -06:00
Henrik RydgårdandClaude Opus 5.5 e108e41675 GPU: Fix assorted leaks, null derefs and small rendering bugs
- Put the anisotropy level in the sampler key, so changing it applies on
  Vulkan and D3D11.
- Release CLUT textures at shutdown on GLES and D3D11.
- Fix the depth readback viewport, which squeezed the image whenever the
  read rectangle was smaller than the fbo.
- Test the computed depth, not the unset result, in the equal-depth clear
  check.
- Don't read back a CLUT from a framebuffer without an fbo.
- Tolerate null entries when releasing post-shader objects and CLUT
  textures after a failed creation.
- ImGe: Don't crash on a framebuffer without an fbo, or on GetVFB under the
  software renderer.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:33:32 -06:00
Henrik RydgårdandClaude Opus 5.5 71bc3187db Vulkan: Reload the shader cache after a device restore
DeviceLost saves the cache and clears everything, and the next save wrote
back only what was drawn since, so each Android background/resume cycle
shrank the cache.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:33:32 -06:00
Henrik RydgårdandClaude Opus 5.5 71210f3fa2 GE recorder: Don't let a second request hijack a recording
RecordNextFrame now refuses while a recording is active and during frame
dump playback (which asserted on the next replay). The callback handoff to
the CPU thread is locked, and a failed file open no longer crashes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:33:32 -06:00
Henrik RydgårdandClaude Opus 5.5 d6f8615d2a SoftGPU: Fix block transfer and self-render overlap tracking
- The block transfer overlap check passed the stride in pixels where bytes
  are expected, so it only covered part of the rectangle.
- A selfrender/selfdepth flush in UpdateState dropped the current draw's
  pending writes and reads, so later transfers didn't wait for it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:33:32 -06:00
Henrik RydgårdandClaude Opus 5.5 507bb0801b GE: Complete lists dropped on error, flush before immediate draws
- A list dropped for a bad pc or a GE error stayed RUNNING: its ID was never
  freed and sceGeListSync on it never returned. Complete it like a finished
  one.
- FlushImm switches to through mode and another vertex decoder, so flush the
  queued draws first even when the immediate flags match.
- Clear leftover temporary GE breakpoints when setting or clearing the next
  break, so a step that never got there doesn't trip later.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:33:32 -06:00
Henrik RydgårdandClaude Opus 5.5 16ea2cbf88 GPU: Fix draw engine buffer overruns and stale vertex data
- Flush before the queued draws would decode more than VERTEX_BUFFER_MAX
  vertices. The batch was limited by index count, which doesn't bound a
  sparse index range, and DecodeVerts silently stopped while DecodeInds
  still emitted indices for the undecoded draws.
- Give TestBoundingBox its own scratch buffer. It used offsets in decoded_,
  which can hold decoded vertices that aren't flushed yet.
- Read 32-bit indices the way the PSP does, ignoring the upper 16 bits.
  IndexConverter and the fast bounding box test used all 32, so a game
  setting them indexed far past the decoded vertices.
- D3D11: Flush in FinishDeferred like the other backends, since indices
  are still read from PSP memory at flush time (#10095).
- Don't JIT new vertex decoders once the code space is full.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:33:32 -06:00
Henrik Rydgård c40cce5173 Merge pull request #22381 from hrydgard/gpu-lifecycle-fixes
Claude code review: GPU lifecycle fixes
2026-09-29 10:24:27 -06:00
Henrik RydgårdandClaude Opus 5.5 4ba981b5b3 sceAtrac: Charge setting data and decoding what the ME takes
Setting data decodes and throws away the frames before the first sample,
so on hardware it costs a decoder setup plus that decode, with the caller
waiting: ~900us for mono Atrac3 and ~3.5ms for stereo Atrac3+, whatever the
buffer size (pspautotests threads/scheduling/callcosts). It was charged
100us.

Decoding a frame now costs what the same frame costs through
sceAudiocodecDecode, through the shared ME queue, instead of a flat
2300us. That's about the same for stereo Atrac3+ and less for Atrac3
(685us mono, ~1100us stereo). The first Atrac3+ frames after setup still
come out ~500us short.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 10:17:46 -06:00
Henrik Rydgård 52a16ea365 Make TempImage act a bit more robustly. 2026-09-29 10:16:08 -06:00
Henrik RydgårdandClaude Opus 5.5 c1e70c9686 Unloading a module is busy work, sceKernelVolatileMemTryLock is quick
From pspautotests threads/scheduling/syscallkinds:

- sceKernelUnloadModule takes ~400us that better threads can preempt
  and worse ones don't get in on, so it uses the busy delay rather than
  a wait.
- A successful sceKernelVolatileMemTryLock takes under 100us. It ate
  500000 cycles as a hack for Crash Tag Team Racing, which has since
  moved to (and no longer needs) the DrawSyncEatCycles compat flag.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:56:28 -06:00
Henrik RydgårdandClaude Opus 5.5 8301c35d7d Threads: Let better threads preempt a long sceKernelCreateThread
On hardware the kernel fills a new thread's stack with interrupts on, so
a better thread that wakes during it runs before the call returns, the
time it takes doesn't count towards the call, and worse threads get
nothing (pspautotests threads/scheduling/preemptsyscall). PPSSPP ate the
whole cost at once and only rescheduled at the end.

__KernelBusyDelayResult() models such a syscall: the caller waits, an
idle thread stands in for it while nothing better wants the CPU, and its
remaining cycles only count down while that's the case. When done it
goes back ahead of threads of its own priority, having never given up
the CPU. sceKernelCreateThread uses it for the stack fill, unless a
thread event handler is about to run.

Booting 75 games against master shows no difference.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:56:28 -06:00
Henrik Rydgård 16135e4e2f Merge pull request #22378 from hrydgard/intr-waits
Claude hardware testing: Interrupt waits
2026-09-29 09:56:08 -06:00
Henrik RydgårdandClaude Opus 5.5 24017a1ba4 GE debugger: Fix the stepping request handshake
- Wait on actionComplete instead of a bare condition variable wait, which
  could miss the wakeup and hang until resume.
- Serialize requesters, so two debuggers can't overwrite each other's
  action, and make SetCmdValue/FlushDrawing wait too.
- Give up and withdraw the request when stepping ends, instead of waiting
  forever (this deadlocked game shutdown against the Win32 GE debugger).
- Run requests during CPU stepping, which already accepted them.
- Clear the stepping state on Core_Resume from GE stepping and on
  GPU_Shutdown.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:49:10 -06:00
Henrik RydgårdandClaude Opus 5.5 24b71386ff Document that shader cache key changes need a CACHE_VERSION bump
The OpenGL and Vulkan shader caches store raw shader IDs (and, for Vulkan,
pipeline keys) on disk. Add the rule to AGENTS.md and point to it from the
persisted types.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:35:17 -06:00
Henrik RydgårdandClaude Opus 5.5 a9eacff1e1 SoftGPU: Keep the compile flushes on builds without the JIT
Skipping them is only a speed-up, but it changes how states get batched and
optimized, which changes the rendered output (NBA 2K13 and Virtua Tennis
frame dumps). Keep the old behaviour until that's understood.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:35:17 -06:00
Henrik RydgårdandClaude Opus 5.5 056fd05231 UnitTest: Make BlockAllocator run in about a second
Validating after every churn step was quadratic in the block count. Check
every 64 steps and at the end, and do fewer iterations.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:35:17 -06:00
Henrik RydgårdandClaude Opus 5.5 a9c782bfcf SoftGPU: Recompute the raster state after a JIT cache clear
A code space clear frees the functions the current state points to, but
the state was kept as long as the GE registers didn't change. Track the
clear generations and recompute, also when a compile during the state
computation clears the caches.

Also skip the binner flush for compiles on builds without the software
JIT, where Compile() does nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:35:17 -06:00
Henrik RydgårdandClaude Opus 5.5 45d997c3ea Texture replacement: Fix DDS mip double free and VFS lifetime
- DDS files with mips stored level 0's file reference in level 1 too, so it
  was freed twice.
- The VFS was deleted on config changes (and ini reloads) while load tasks
  still used it. Now each cached texture waits for its task and releases its
  file references through the old VFS first, and reloads afterwards.
- A failed ini reload turns replacement off instead of leaving it on without
  a VFS.
- Release file references on purge.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:35:17 -06:00
Henrik RydgårdandClaude Opus 5.5 4df1b9c2c3 Vulkan: Clear pipelines before shaders when use flags change
The pipeline deletion callbacks block on in-flight compiles, which use the
shader module promises that the shaders' deletion callbacks free. Queueing
the shaders first freed the promises under a pending compile.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:35:17 -06:00
Henrik RydgårdandClaude Opus 5.5 b912b5823b GPU: Fix texture cache and framebuffer cache lifetime bugs
- Delete TexCacheEntry objects dropped on rehash instead of leaking them.
- Don't leave a released null entry in cache_ when the framebuffer match
  returns before the slot is refilled.
- Reset clutRenderAddress_ in Clear(), which releases the dynamic CLUT FBOs.
- Don't cache a null texture in drawPixelsCache_ when creation fails.
- Fix the reversed subtraction in the failed-FBO retry check.
- Remove the never-taken buffered-rendering early-out in UpdateRenderSize.
  Taking it would leave existing VFBs without an fbo.
- Include smoothedDepal in the depal shader cache key, and print/parse the
  debug IDs as 64-bit.
- Release depal pipelines through Draw2DPipeline::Release so the shader
  source isn't leaked, and make that null-safe.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:35:17 -06:00
Henrik RydgårdandClaude Opus 5.5 5be18675e0 docs: Rebuild and re-record a whole pspautotests directory at a time
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) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 8e291b8e12 Interrupts: Interrupt 8 has no handler on 6.61
intr/registersub, re-recorded on a 6.61 PSP with every test in the
directory rebuilt, finds no handler on interrupt 8 where the old
recording found one that didn't take user sub-interrupts. The old one was
probably made on an earlier firmware, whose drivers hooked it. Follow
6.61, the firmware PPSSPP models.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 8ed2d170f5 Audio: Don't emulate a failed blocking wait leaving the channel busy for good
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) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 68dbbb253d sceDisplay: Vblank lasts 770us, not 731.5us
Measured with pspautotests display/vblanklen: 730-770us from
sceDisplayWaitVblankStart returning to the end of vblank, with an hcount
of up to 14 inside it. The old value dated from the first source drop
and left the highest hcount at 13. display/hcount now passes (with the
test fixed not to depend on where a line boundary falls).

Booting 75 games against master shows no difference.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 0928202310 scePower: CPU clock can't exceed the PLL, float frequency to the bit
scePowerSetCpuClockFrequency refuses a CPU clock above the PLL's, and
scePowerGetCpuClockFrequencyFloat computes pll * n / 511 in single
precision like the firmware, instead of converting whole Hz, which was
off in the last digit. power/freq now passes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 960da1596c Threads: Charge for filling the stack on create, and for delete
From pspautotests threads/scheduling/costs: sceKernelCreateThread takes
about 150us plus roughly a cycle per byte of stack, which the kernel fills
with 0xFF (1.3ms for 256KB), and sceKernelDeleteThread 50-100us whatever
the stack size. Brings threads/scheduling/scheduling a good deal closer;
what's left needs a thread that wakes during a long syscall to preempt it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 436db85cf5 Reschedule on IO completion, drop the reschedule in time queries
The time queries rescheduled since 2013, so that a game spinning on the
clock would let a thread that a timing event had woken run (it fixed
audio in Crimson Gem Saga and Where Is My Heart?). In 2014 audio and
delay wakeups started rescheduling themselves, but IO completion never
did, and a movie reader thread in Driver 76 was only getting in through
the time queries. Now IO completion dispatches like any other wakeup.

The PSP doesn't dispatch in a time query, and doing so let a thread
that a terminate woke run too early. threads/threads/terminate now
passes.

Checked by booting 75 games against master: the same in all of them,
with Asphalt Urban GT2 getting further in the same time.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 638da935ea Threads: sceKernelStartThread hands the CPU straight to a better thread
When the new thread outranks the caller, the firmware switches to it
directly, even if a thread of still better priority is ready but hasn't
been dispatched (one that a sceKernelTerminateThread woke, say). Verified
against the new pspautotests threads/threads/termsuspended.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 23fc0cd422 Interrupts: Refuse sub-interrupt handlers where the firmware does
Only the GE and vblank interrupts take user sub-interrupt handlers, and
vblank only in slots 0-15, with some of the rest already held by the
kernel. The errors follow interruptman.prx's checks, and which interrupts
have handlers at all is read back from pspautotests intr/registersub and
intr/releasesub, which now pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 a74882b013 Audio/VolatileMem: Match hardware when a blocking call can't wait
From pspautotests intr/waits:

- sceAudioOutputBlocking sets the channel's waiting flag before its
  event flag wait, and when that wait fails at once (interrupts or
  dispatch disabled, or inside an interrupt) it returns the error
  without clearing the flag. The channel stays busy from then on, and
  can't be released.
- The SRC blocking output fails the same way even when a completion is
  already there, leaving the buffer armed.
- After a block that had samples in it, the mixer DMA is still playing
  it out, so a buffer arriving then isn't read early or restarts it.
- sceKernelVolatileMemLock only writes the fake address and size
  through pointers that are there, instead of faulting on NULL.

intr/waits now runs to the end; one scheduling marker still differs, from
async IO timing.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik RydgårdandClaude Opus 5.5 5674c789ef sceUmd: Match hardware's parameter checks and wait timeouts
- A timeout of 0 to sceUmdWaitDriveStatWithTimer/CB means no timeout,
  not a tiny one (or 8ms for the CB version).
- Timeouts round like the event flag wait does.
- A wait with no timeout no longer times out right after a callback.
- sceUmdRegisterUMDCallBack only accepts callbacks.
- sceUmdActivate requires the name to be exactly "disc0:", and it and
  sceUmdDeactivate/sceUmdGetDiscInfo reject kernel pointers.
- sceUmdDeactivate needs a name in mode 2.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 09:19:32 -06:00
Henrik Rydgård b04d03615b Merge pull request #22375 from hrydgard/startup-caching
Startup caching
2026-09-28 21:29:06 -06:00
Henrik Rydgård eab0b53a7a Merge pull request #22371 from hrydgard/audiocodec-fixes
sceAudiocodec and sceVideocodec timing and Atrac3+ fixes
2026-09-28 17:26:04 -06:00
Henrik Rydgård d3ab52b4b3 Buildfix 2026-09-28 17:05:44 -06:00
Henrik RydgårdandClaude Opus 5.5 023ad93ed3 sceVideocodec: Don't hold the ME for Init and Delete
They take tens of milliseconds for the caller, but queueing that time on
the shared ME timeline made the SAS mix wait behind them. In Jak and
Daxter that held up the sound threads at the end of the first clip, so
video_sound_thread got its last wake only after the game had deleted it
(NOT_DORMANT), and the orphaned thread then read a freed context.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 16:57:41 -06:00
Henrik RydgårdandClaude Opus 5.5 c2bc2d9308 ME: Charge measured times for sceVideocodec calls and the rest of sceAudiocodec
Measured on a PSP (pspautotests video/mp4/mp4timing, audio/audiocodec/timing):

- sceVideocodec Open, GetEDRAM, GetVersion and ReleaseEDRAM take ~70-150us,
  Init ~26.6ms (sceMpegCreate is 27-28ms), Delete ~21ms (was 2ms), and
  Stop 132us with nothing held back. All go through the ME queue now.
- Decodes that return no picture take as long as those that do; they
  were free.
- Open reports the EDRAM the decoder needs (0x3c2c) at ctx+0x18, which
  mpeg.prx passes on to GetEDRAM.
- sceAudiocodec: failed decodes (214/142/169us) and mono Atrac3+ init (524us).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 16:57:41 -06:00
Henrik RydgårdandClaude Opus 5.5 c162eb3d74 sceAudiocodec: Match hardware setup, framing, errors and timing; fix Atrac3 polarity
Checked against pspautotests audio/audiocodec, recorded on a PSP.

- Atrac3+: at3Related selects headered (mpeg.prx) or raw (libatrac3plus)
  frames, instead of sniffing for the sync word. The header's size field
  is 10 bits, as the context's. Header errors 0x211/0x213, bad frames
  0x20a, all returning SCE_AVCODEC_ERROR_INVALID_DATA with nothing read.
- The first successfully decoded Atrac3+ frame, and the first two AAC
  frames, produce no output. Checked sample-for-sample against hardware.
- Atrac3: the parameter at 0x28 selects the frame layout, as
  libatrac3plus.prx's table maps it. We used to read its low bit as a
  joint-stereo flag, which decoded mono (0x0F) streams as stereo garbage.
  AtracCtx2 had the table's fields swapped the same way.
- at3_standalone's Atrac3 output was inverted relative to the PSP's
  (sceAtrac too). Negate the IMDCT scale.
- CheckNeedMem sizes (AAC is 0x658c), codec 0x1004/0x1005, Init
  validation (AAC sample rate, Atrac3 parameter, Atrac3+ channels), and
  ReleaseEDRAM clearing edramAddr.
- Every call that reaches the ME now blocks for its measured time, and
  decode time is modelled per codec and frame size.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 16:57:41 -06:00
Henrik Rydgård 0f4bb62fa7 Merge pull request #22377 from hrydgard/callback-test-fixes
PSP kernel: Callback fixes
2026-09-28 16:24:06 -06:00
Henrik Rydgård fb17e58fe3 Merge pull request #22376 from hrydgard/mac-memory-fault-handler
Mac memory fault handler, remove remains of support for Win32 ARM
2026-09-28 16:20:54 -06:00
Henrik RydgårdandClaude Opus 5.5 8b4e93021e Vulkan: Cache compiled SPIR-V so shaders skip glslang on later runs
GLSLtoSPV takes an optional SPIRVCache, keyed on a 32-bit hash of the
source, stage and variant, plus the source length. A changed shader
simply misses. thin3d's shaders and the other fixed ones use a global
cache in PSP/SYSTEM/CACHE/vulkan_spirv.cache, loaded on first use and
saved after graphics init, when a game's cache is saved, and at
shutdown; it's flushed once it reaches 32 entries, about twice what a
session compiles, so outdated ones don't pile up. Game shaders keep
theirs in the .vkshadercache, ahead of the shader IDs so that the
compiles on load find it (version 60), and only what the session used
is saved.

A cold glslang costs about 40ms before its first shader here, and
0.3-0.9ms per shader after that.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 16:03:52 -06:00
Henrik RydgårdandClaude Opus 5.5 3970fa5c62 UIAtlas: Read and decode the font atlas once
Like the UI atlas, keep the decoded image around and only recreate the
texture when a new UIContext asks for it, rather than reading the
metadata and decoding the ZIM from disk each time.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 16:03:52 -06:00
Henrik RydgårdandClaude Opus 5.5 c11e46aeea LwMutex: Take the lock after a callback if it was released during it
The end callback checked the kernel object's lockThread, which for an
lwmutex is only refreshed by sceKernelReferLwMutexStatus. The lock state
lives in the workarea, so an unlock during the callback left the waiter
waiting forever. Verified against pspautotests threads/lwmutex/callbacks.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 15:59:25 -06:00
Henrik RydgårdandClaude Opus 5.5 eda92c4ef2 Callbacks: Track callback nesting per thread, one level deep
Replaces the global in-callback counter with each thread's own mipscall
chain, so several threads can be inside callbacks at once, and other
threads' callbacks (better priority ones right away) run while one is.
Verified against pspautotests threads/callbacks/otherthread, recursion
and intrnotify:

- A callback nests only one level: a CB wait that would go deeper never
  returns on hardware, so the callback is left pending instead.
- A non-CB wait inside a callback no longer runs callbacks because of
  the CB wait the callback interrupted.
- Callbacks for a waiting thread are only taken when it beats both the
  running thread and every ready one. After an interrupt (which runs on
  the idle thread) that's the thread about to resume, not idle.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 15:59:25 -06:00
Henrik RydgårdandClaude Opus 5.5 24b41d3f23 Kernel waits: Fix VPL/FPL and msgpipe waits around callbacks, report timeout left
Verified against pspautotests threads/callbacks/waittypes:

- __KernelThreadingInit() cleared the wait type callback table after
  __KernelMemoryInit() had registered VPL and FPL in it, so a VPL or FPL
  wait interrupted by a callback was never paused or resumed, and could
  hang forever.
- A msgpipe deleted during a callback left its waiter waiting, instead
  of waking it with WAIT_DELETE.
- A wait that got its object during a callback reported no time left;
  put the timer back before trying to unlock, so the unlock writes what
  remains.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 15:59:25 -06:00
Henrik RydgårdandClaude Opus 5.5 acd2738b7a Callbacks: Deliver to other threads by priority, fix sceKernelCancelCallback
Verified against pspautotests threads/callbacks/delivery:

- Notifying the callback of a better priority thread in a CB wait runs
  it right away. Callbacks of other waiting threads stay pending until
  those threads would get to run, rather than being taken at any
  reschedule, so they can still be counted or canceled.
- sceKernelCancelCallback clears the notify count, not just the arg.

threads/callbacks/cancel, count and umd/wait/wait now pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 15:59:25 -06:00
Henrik RydgårdandClaude Opus 5.5 3bf8b5b5c9 Callbacks: Run nested callbacks from CB waits, match hardware ordering
Verified against new pspautotests threads/callbacks/afterwait and nested:

- A thread inside a callback runs its own pending callbacks (even the
  same one again) nested, when it enters a CB wait. Waits paused by a
  nested callback are keyed by the outer callback's id.
- sceKernelCheckCallback inside a callback returns ILLEGAL_CONTEXT
  without running anything.
- sceKernelSleepThreadCB with a queued wakeup runs pending callbacks
  before consuming it.
- A thread whose wait ended during a callback keeps the CPU, instead of
  queueing behind threads of the same priority.

threads/callbacks/notify now passes too.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 15:59:25 -06:00
Henrik RydgårdandClaude Opus 5.5 e7c23bb381 Don't run wait callback hooks for a thread that isn't waiting
A thread whose wait ends without a context switch (for example when a
callback run from the wait satisfies it) keeps its old waitType. If it
later ran a callback, from sceUmdWaitDriveStatCB with the drive already
ready for instance, the stale wait's begin/end hooks ran, couldn't find
the paused wait, and resumed the thread with SCE_KERNEL_ERROR_WAIT_DELETE,
overwriting the HLE call's return value.

Should fix "sceUmdWaitDriveStatCB: error 0x800201b5" dialog in
Maru Goukaku TOEIC Test Portable (#7576).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 15:58:47 -06:00
Henrik RydgårdandClaude Opus 5.5 c70fc68261 Remove leftovers of 32-bit ARM Windows support
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 13:58:29 -06:00
Henrik RydgårdandClaude Opus 5.5 aad83c044f docs: Native debuggers vs. the memory fault handler
How to keep lldb and gdb from stopping on the faults the handler is meant
to catch, and the crash_*.prx tests as a quick check that it works.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 13:58:29 -06:00
Henrik RydgårdandClaude Opus 5.5 c4884040ee Mac/iOS: Hook up the memory fault handler, including on ARM64
Apple platforms now use the same SIGSEGV/SIGBUS handler as Linux instead of
a Mach exception port. The Mach port was only set on the installing thread,
which is the loader thread, not the one running JIT code. ARM64 had no
context definition at all.

Also pass the thread state (not the mcontext) to the handler, accept SIGBUS
fault codes, and fix restoring a disabled altstack on Darwin.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 13:54:23 -06:00
Henrik Rydgård 91a34056d0 Merge pull request #22373 from hrydgard/video-texture-clamp
Clamp sampling of video textures and direct-displayed video to the 480x272 frame
2026-09-28 13:46:00 -06:00
Henrik Rydgård 6cdfbe2c0d Merge pull request #22372 from hrydgard/search-raw-filename
Game browser search: also match the filename, minus extension
2026-09-28 13:36:33 -06:00
Henrik Rydgård 2ec92c10a3 Merge pull request #22370 from hrydgard/more-savestate-fixes
Claude code review: More savestate fixes
2026-09-28 12:12:40 -06:00
Henrik RydgårdandClaude Opus 5.5 ce3d8d37c5 Game browser search: also match the filename, minus extension
Works before the game's metadata has loaded.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:50:28 -06:00
Henrik RydgårdandClaude Opus 5.5 a4ce937f81 Savestate: Bounds-check BufferQueue and Atrac pending output, fix dummy JIT prefix
An interpreter state always claimed an uneaten VFPU prefix, which made a
JIT loading it run in unknown-prefix mode for the rest of the session.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:32:07 -06:00
Henrik RydgårdandClaude Opus 5.5 fc32d79596 Savestate: Fix reopening files on load
Drop an ISO handle whose file isn't in the loaded image instead of keeping
a null file, and don't reopen a directory file with exclusive create.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:32:07 -06:00
Henrik RydgårdandClaude Opus 5.5 cd94e414cd Savestate: Reset what older states lack instead of keeping pre-load values
Missing sections (videocodec, audiocodec, aac, mp3) and old-version
branches (impose, io, umd, gps, mic, display, font) left the session
before the load in place.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:32:07 -06:00
Henrik RydgårdandClaude Opus 5.5 8663ce2a33 Savestate: Save state that was missing from several modules
sceMpeg's AVC resource flag, whether the VSH is running, sceReg's handle
counter, sceNet's pending apctl events and product code block, and the
save dialog's copy of the original request (without which the first
Update after a load reloaded the request and lost its results).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:32:07 -06:00
Henrik RydgårdandClaude Opus 5.5 52299a5e17 Savestate: Save sceDmac and sceUsbCam, which were never called, and NpDrm
__DmacDoState and __UsbCamDoState existed but weren't in the module list,
so the memcpy deadline and camera state carried over from before a load.
The NpDrm licensee key wasn't saved or reset at all, so EDATA opened after
a load in a fresh session couldn't be decrypted.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:32:07 -06:00
Henrik RydgårdandClaude Opus 5.5 dd9adfdf60 HLE: Resolve imports of states from before HLE v3 with the flags of then
Without saved flags, those states were resolved against today's defaults,
and everything graduated since (sceMpeg, sceFont, the leaf libraries) ended
up on unresolved stubs.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:32:07 -06:00
Henrik RydgårdandClaude Opus 5.5 bc88337a6a CoreTiming: Give events a state lacks the first id after the state's own
The first one got n-1, which is the state's last event, so that one moved
to a new id while its queued occurrences fired the newcomer. n-1 dates
from before RestoreRegisterEvent could fall back when out of range. Also
recompute a debugger run-until deadline against the loaded clock.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:32:07 -06:00
Henrik Rydgård 274c89d7f3 Merge pull request #22367 from hrydgard/savestate-fixes
Claude code review: Savestate fixes
2026-09-28 11:31:56 -06:00
Henrik Rydgård 49f92ebcb6 Merge pull request #22368 from hrydgard/remove-minimp3
Remove minimp3
2026-09-28 11:31:40 -06:00
Henrik Rydgård 3b10a9525c Merge pull request #22369 from hrydgard/compact-gpu-test-output
pspautotests: More compact gpu test output, increase timeout in debug builds
2026-09-28 11:31:18 -06:00
Henrik RydgårdandClaude Opus 5.5 3f44da709b Clamp sampling of video textures and direct-displayed video to the 480x272 frame
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:22:01 -06:00
Henrik RydgårdandClaude Opus 5.5 44d0c401d5 test.py: Give Debug builds three times the wall-clock timeout
gpu/clut/offset and gpu/commands/material take over 1s each in a Debug
build, and every so often pushed past the 5s limit mid-run, which reads
as a failure with truncated output.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:03:53 -06:00
Henrik RydgårdandClaude Opus 5.5 0c70d77573 AGENTS.md: Keep headless game runs short and fast
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 10:57:21 -06:00
Henrik RydgårdandClaude Opus 5.5 e0767e30a4 Savestate: Give sceFont's newer action types new ids in older states
Same as the exit callback: states from before them numbered the action
types without them, so the boot-time ids can belong to other types.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 10:57:21 -06:00
Henrik RydgårdandClaude Opus 5.5 58bae839e2 Savestate: Don't let AtracOutput take another event's id in older states
A state from before the event kept its boot-time id, which Atrac restores
first, so the event the state had under that id moved to a new one while
its queued occurrences kept firing AtracOutput. In Outrun 2006 that was
the vblank, and the game waited for it forever.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 10:57:21 -06:00
Henrik RydgårdandClaude Opus 5.5 761e2f3706 Remove minimp3
MP3 emulation already went through FFmpeg, leaving MiniMp3Audio dead.
The one live user was loading MP3 UI sound effects (custom achievement
sounds), which now splits the file into frames and decodes them with
the FFmpeg MP3 decoder.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 10:57:02 -06:00
Henrik RydgårdandClaude Opus 5.5 9f182a2a1b Update pspautotests: compact rendertarget test output
gpu/rendertarget/copy no longer prints a million pixels one at a time,
and runs in 0.17s under the interpreter rather than ~4.5s.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 10:32:42 -06:00
Henrik RydgårdandClaude Opus 5.5 bfd3633493 test.py: Give Debug builds three times the wall-clock timeout
gpu/clut/offset and gpu/commands/material take over 1s each in a Debug
build, and every so often pushed past the 5s limit mid-run, which reads
as a failure with truncated output.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 10:26:42 -06:00
Henrik RydgårdandClaude Opus 5.5 40a70b004c Savestate: Reset video frame tracking and ME busy time on load
Neither is serialized, and both went stale on load. The ME busy time was
measured against the pre-load clock, so loading an earlier state made the
next SAS/codec job wait until the old time came around, freezing the game.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:36:48 -06:00
Henrik RydgårdandClaude Opus 5.5 8d62ae04dd Savestate: Make verify compare memory without emuhacks, and not count
The write pass stores memory with the JIT's emuhacks cleared, but the
verify pass compared against memory that still had them, so it would
report a mismatch under a JIT. Only EMULATOR_DEVCTL__VERIFY_STATE runs it,
and nothing currently does. It also counted as a save in the generation.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:36:47 -06:00
Henrik RydgårdandClaude Opus 5.5 f96a58fb3e Savestate: Don't change the running game's state when saving
Some DoState code meant for after a load ran on every save:
- scePower reset the bus frequency a game set (and with a locked CPU
  speed, applied the current setting to the clock).
- sceDisplay reset the lag sync baseline, and could schedule lag sync in
  the measuring pass only, which failed the save.
- GPUState dirtied the texture, sceUmd notified the UI, and sceMpeg
  dropped a pending ringbuffer fix-up for an old state.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 2e5edcd298 Savestate: Don't break event callbacks when a save fails
CoreTiming::DoState replaced every event's callback with the anti-crash
one in every mode, relying on each module's restore to put it back. A save
that failed partway never got to those, and left the running game with
events that break into the debugger. The missing-section fallbacks then
also ran on the save: cheats and the mic re-registered events into the
wrong slots, and achievements reset the runtime.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 9140bc9548 Serialize: Report the first bad section, and start with no title
SetError overwrote the first bad section with whichever section a later
error came from, and an error before any section read an uninitialized
curTitle_.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 11e30e9456 Serialize: Don't read past a savestate title without a terminator
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 7ad9a64cf4 Serialize: Clear pointer maps and sets right after deleting their values
DoMap and DoSet cleared them, but only once the count had been read. A
state truncated right there left the deleted pointers in place, to be
freed again when the failed load reset the game.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 937c100af8 Savestate: Defer operations while the GE debugger holds a list
The list belongs to the sceGe call still in progress, whose end would have
run on the loaded CPU state. Also stop the camera and GPS when a state has
them off, don't restart capture when saving, and fix a double free of the
pmp frame queue (it only holds the media engine's own frame).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 0a687b9435 Savestate: Bounds-check sizes from the file, and plug leaks on load
Reject sizes past the end of the state before allocating (FPL, PGF,
achievements, SAS grain, savedata list, the memory fast path), fail
instead of desyncing on a SAS voice count mismatch, and free what old
states' paths and shrinking pointer containers dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 7fa6be6a25 Serialize: Don't dereference unfilled pointers after a load fails
Containers of pointers are filled with nullptr and then DoClass'd, and
once an error switches the load to MODE_NOOP, every remaining element
called DoState on null.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 d6c74156b1 Savestate: Fix file handle reuse and leaks when reopening files
DirectoryFileSystem reused one entry across files, so a failed reopen
could seek another file's handle. VirtualDiscFileSystem leaked every open
handle on each load. MemoryStick ignored the saved free space basis.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 a4e169893f sceAudiocodec: Recreate decoders from the context on load
They were recreated without block size or extradata, which Atrac3 needs,
so it stayed silent after a load. Also drop the old decoders when the
state has none, and don't overflow on v1 states.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 490f08d220 Savestate: Fix action and exit callback handling on load
Delete the old HLE mips call actions instead of leaking them or keeping
stale ones, fail the load on an unknown action type instead of crashing,
and derive the exit-callback-pending flag from the loaded state.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 06e11fb176 Savestate: Rehash analyzed functions before writing replacements on load
The function list is from before the load, where other code (an overlay
module) may have been. Hashing it again from the loaded memory keeps the
hooks to code that actually matches.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00
Henrik RydgårdandClaude Opus 5.5 639e57c2bd Savestate: Keep pending adhocctl requests, so a waiting Init survives a load
With WLAN on and no server reachable, sceNetAdhocctlInit keeps its thread
waiting for the login. The load dropped the request, and the wait ended in
BUSY, which Init can't retry. Splinter Cell then ran its failure path with
a deleted event flag. Also stop freeing matching event buffers into the
restored allocator, and take the event lock when clearing.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 09:34:06 -06:00