Commit Graph
49307 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 9dd6d2de10 Headless: Always initialize networking
Without WSAStartup, every sceNetInet socket call failed on Windows with
WSANOTINITIALISED, so no inet test could run there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 08:59:03 +02:00
Henrik Rydgård cf85e91e62 Merge pull request #22670 from hrydgard/dostate-review
SaveState: Fixes from a review of the remaining DoStates
2026-10-20 08:04:37 +02:00
Henrik RydgårdandClaude Opus 5.5 30cab94840 SaveState: Fixes from a review of the kernel savestates
- A partition memory block restored its allocator through
  BlockAllocatorFromID, whose privilege check asked the thread current
  before the load, already deleted with the kernel objects. Loading a
  kernel-partition block under a user thread left it without an allocator,
  crashing when it was freed. The saved id maps straight back now, and the
  current thread is cleared along with the objects.
- The thread LoadExec leaves waiting (with no timeout) for plugins and VSH
  modules to start wasn't saved, so a state saved while one was starting
  hung the game thread after loading. The VSH modules still to start
  weren't saved either, nor cleared.
- A state without the exit callback id left the previous session's.
- From a broken or crafted state: thread ids that GetFast trusts (current,
  idle and ready), interrupt handler numbers, FPL and TLSPL indices, and a
  MIPS call or action slot that isn't there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:20:24 +02:00
Henrik RydgårdandClaude Opus 5.5 d40137a007 SaveState: Fixes from a review of the IO and dialog savestates
- Savedata list entries lost their directory across a load. Confirming a
  delete in a list dialog loaded from a state failed with "delete failed"
  and deleted nothing. LISTALLDELETE can't rebuild it from the names, so
  it's saved now, with the broken flag.
- Registry values the game wrote (CLANNAD's font plugin registers its font
  there) weren't saved, and a state without a sceReg section wiped them.
- Slots sharing the "no save" icon came back as separate images of one
  texture, which Clear() then freed once per slot.
- Old directory states left a file opened with TRUNCATE due for truncation,
  so it read as empty and was truncated on close. Old async IO states were
  loaded on top of this session's results.
- From a broken or crafted state: OSK keyboard and Korean composition
  indices, gamedata install file counts, font ownership modes (an internal
  font could be deleted) and FontLib's per-font vector sizes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:20:24 +02:00
Henrik RydgårdandClaude Opus 5.5 43f7674f9c SaveState: Check the audio and media modules' values from a state
A review of their savestates found nothing wrong with a normal save and
load, but a broken or crafted state could crash or write out of bounds:

- Atrac2's context pointer was dereferenced and its SAS buffer index used
  unchecked. Atrac v1's file size sized an allocation before it was known
  to be in the file, and its first buffer could be larger than the file.
- MediaEngine's MPEG header size and read position index a fixed array,
  and its ringbuffer size sizes buffers.
- AuCtx's PCM buffer must hold two frames, as the game side requires. And
  AuDecode compared a decoded frame with the whole PCM buffer instead of
  the half it fills, so a frame between the two zero-filled a negative
  size.
- BufferQueue's fill level, VAG block positions (now ending the voice,
  like the other bad VAG state), sceAudiocodec's context count, the SRC
  mix frequency (divided by), and old sceMpeg states' ringbuffer address.
- SAS decoding with an atrac id that has no atrac crashed after warning.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:20:24 +02:00
Henrik Rydgård 8fdadf74bc Merge pull request #22668 from hrydgard/pause-menu-load
Savestate loads: keep JIT blocks and textures, pause menu waits for loads, review fixes
2026-10-20 00:15:56 +02:00
Henrik Rydgård 19869834b2 Merge pull request #22671 from hrydgard/alarm-vtimer-pending-delete
Kernel: Keep alarm and vtimer queues in step with their pending interrupts
2026-10-20 00:10:58 +02:00
Henrik RydgårdandClaude Opus 5.5 d42f34d3de Kernel: Keep alarm and vtimer queues in step with their pending interrupts
Each due alarm or vtimer queues its id next to a pending interrupt, which
pops it when it runs. An alarm cancelled with its interrupt pending was
never popped, so every later alarm interrupt found the cancelled one at
the front and was dropped: no alarm ran again. Deleting a vtimer erased
its id instead, leaving the last pending interrupt with an empty list.

Both now pop a timer that's gone when its interrupt comes, as
threads/alarm/pending and threads/vtimers/pending show the PSP still runs
the rest, in order.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 21:41:48 +02:00
Henrik RydgårdandClaude Opus 5.5 7202904f60 libretro: Include SaveState.h for SaveStart, now declared there
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 21:32:43 +02:00
Henrik RydgårdandClaude Opus 5.5 28686cf428 sceGe: Check the pending interrupt before finishing it
A broken savestate can have a GE interrupt running with nothing pending,
or a pending entry for an invalid list. Finishing it read past the queue.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:54:29 +02:00
Henrik RydgårdandClaude Opus 5.5 ca0e40bb0a GPU: Don't load the host's window-minimized bit from a savestate
skipDrawReason was saved whole, including the bit that says the host
window is minimized. A state saved while minimized (a rewind snapshot)
kept it set after loading in a normal window, and the other way round.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:54:29 +02:00
Henrik RydgårdandClaude Opus 5.5 130ea9ced9 GPU: Save the next display list id, and restart frame dumps on load
- Which list slot and id the next sceGeListEnQueue gets wasn't saved, so
  after a load it depended on the session before it, and replays and
  rewinds diverged. Older states start it at 0, and also get the default
  EDRAM translation and visible matrices they don't carry, rather than the
  previous session's.
- A frame dump recording during a load replayed the loaded state's
  commands on the registers from before it. It now starts over from the
  loaded state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:54:29 +02:00
Henrik RydgårdandClaude Opus 5.5 814593ee4d Framebuffers: Forget the depth shift tracking with the framebuffers
DestroyAllFBOs (on a savestate load) kept depthShiftedFor_ and
lastRenderAddress_, so a new framebuffer at the same address as the old
one skipped the depth shift for a frame.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:54:29 +02:00
Henrik RydgårdandClaude Opus 5.5 fa3eff96fb Display: Don't read past memory presenting a framebuffer from RAM
The PSP accepts display strides that run past the end of VRAM, and
negative ones (setframebuf.expected has -64 and 2048), and presenting
straight from memory read all 272 rows of whatever stride was set.
Without a matching framebuffer, that's a read far outside PSP memory.
Now the whole image has to be in memory, or the display shows black, on
the hardware backends and the software renderer.

A savestate's display framebuffer with an address outside RAM and VRAM,
or an invalid format, turns the display off instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:54:29 +02:00
Henrik RydgårdandClaude Opus 5.5 ef8961e17a Freeze frame: Keep away from GE stepping, and keep other loads
Freeze-frame saves and loads its own snapshot outside SaveState::Process,
so it missed the check for a display list stopped in the GE debugger:
stepping a draw in a frozen frame reloaded the state under the list.

And a slot load or rewind while frozen was undone by the next frame's
freeze reload, while saying "Loaded State". Now it becomes the frozen
state, and is a full load (the GPU only skips its resets for the freeze's
own reloads).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:54:29 +02:00
Henrik RydgårdandClaude Opus 5.5 7e4127ae69 Pause menu: Don't close on a state load until the state is in place
Loading a state from the pause menu closed it right away, and the game
ran a few frames from where it was (seen and heard) before the state
was applied. Now savestate operations run while the game is paused under
the menu, and the menu closes once the load (or rewind, or load undo) is
done. Rewind snapshots aren't taken while paused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:42:14 +02:00
Henrik RydgårdandClaude Opus 5.5 6e16f46445 GPU: Keep textures across a savestate load, rehashing them first
A load cleared the texture cache, so every texture the game drew next
was decoded and uploaded again. Now each one is rehashed against its
memory the next time it's used, and only decoded again if it changed. In
the three seconds after a load, Ridge Racer decodes no textures instead
of 109, Force Unleashed none instead of 43. Rendered frames are the same.

Framebuffers are still all destroyed on load, since loading a state is a
known way out of some framebuffer problems.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:30:02 +02:00
Henrik RydgårdandClaude Opus 5.5 26d9e54059 SaveState: Don't fold a save into one from before a load
A save to a path with a save still being written counted as a repeated
key press, even with a load applied in between, so save, load, save
didn't save the loaded state. Now a repeat only joins the earlier save if
no load has been applied or queued since its snapshot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:18:47 +02:00
Henrik RydgårdandClaude Opus 5.5 3cc61d3f8e SaveState: Report failed writes, and call back on network refusals
- A failed final flush (a full disk) or a failed rename into place was
  reported as "Saved State". A failed load undo rename still pointed load
  undo at the current game, while the file was another game's state.
- Refusing a save or load for being online didn't call the callback,
  unlike every other refusal.
- A rewind dropped after its decode started stayed active, and stopped
  rewind snapshots until the next clear.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:18:46 +02:00
Henrik RydgårdandClaude Opus 5.5 f45f4d5c85 SaveState: Only rehash functions in code a load changed
Every load hashed all known functions again, for the replacement hooks:
2.4-6 ms, about half the load. Functions in module code the load left as
it was keep their hash, so only the rest is hashed now (0.5-1 ms). The
code is compared on every load for this, not only with a JIT.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:18:46 +02:00
Henrik RydgårdandClaude Opus 5.5 a62aa69986 libretro: Use the real SaveStart
libretro declared its own, which no longer matched once SaveStart got a
member, so serializing read an uninitialized byte. It's in SaveState.h
now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:18:46 +02:00
Henrik RydgårdandClaude Opus 5.5 198652d9b3 SaveState: Don't keep JIT blocks across a memory remap or a failed load
A load that changes the memory size remaps RAM, so there's nothing to
compare with, and the JIT's dispatcher points at the old mapping: it has
to start over. A load that fails partway may have replaced RAM without
the JIT being told, so it keeps nothing either.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:18:46 +02:00
Henrik RydgårdandClaude Opus 5.5 eec6afaeca SaveState: Reject implausible event counts, ids and CPU speed from a state
They came straight from the file: a CPU frequency of 0 divided by zero,
and counts or ids sized tables of up to tens of gigabytes, or tripped an
assert. A file like that now fails to load like any other broken state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:17:47 +02:00
Henrik RydgårdandClaude Opus 5.5 79d340f4fe SaveState: Keep JIT blocks in module code a load doesn't change
Loading a state started the JIT over, so every block the game runs had to
be compiled again, which stutters. Now the loaded modules' code is
compared with the state's as RAM is loaded (a chunk that already matches
isn't copied either), and blocks entirely inside unchanged code stay.
Everything else is invalidated as before, and without a JIT, or with no
unchanged code, the JIT still starts over.

The JIT throws its blocks away if the loaded state needs code they weren't
compiled for (rounding mode switches, or no default prefixes at block
start). The other way round it keeps them, along with the flags they were
compiled under: before, a loaded hasSetRounding of false, or the reset of
lastSetRounding, had the next compile rebuild everything anyway.

In the three seconds after loading a state, Ridge Racer compiles 28 blocks
instead of 4823, Force Unleashed 31 instead of 7727. Frames rendered after
the load are identical either way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 19:47:05 +02:00
Henrik Rydgård 3ac908345c Merge pull request #22667 from hrydgard/async-savestates
Async savestates: compress and write on a worker thread
2026-10-19 19:25:24 +02:00
Henrik Rydgård 6015ff06a7 Merge pull request #22616 from LunaMoo/native-cso-pack
Add built-in CSO packing functionality
2026-10-19 18:53:00 +02:00
Henrik RydgårdandClaude Opus 5.5 86e420b9ce EmuScreen: Show the loading spinner while a savestate saves or loads
The boot spinner, in the same corner without the background, while a
save, load or rewind is queued or in progress. Only after a quarter of a
second, so it doesn't flash up on every quick save; on desktop that's
rarely reached, but slow devices get some feedback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 18:51:10 +02:00
Henrik Rydgård e2e0e0c748 Merge pull request #22666 from hrydgard/savestate-fixes
Savestate fixes: rewind ring, slot operations, load failure messages
2026-10-19 18:46:20 +02:00
Henrik RydgårdandClaude Opus 5.5 5bbeb6254b Debugger: Savestate slots in game.state.save/load, and async savestate docs
game.state.save and game.state.load take a slot of the running game
instead of a path, going through the same code as the save and load keys
(undo rotation, load undo, repeats), so that can be tested headless.

docs/async-savestates.md now describes what was built, with the results.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 18:43:27 +02:00
Henrik RydgårdandClaude Opus 5.5 821fd301e6 SaveState: Compress and write savestates on a worker thread
Saving and loading stalled the emu thread for 30-75 ms, long enough to
glitch audio. Now only the snapshot and the apply run there; compression
and file I/O run on a savestate worker thread, in order.

- Save: snapshot in Process(), then compress, write and (for a slot) move
  the old state to its undo name on the worker. The callback runs once the
  file is written. Saving again to a path with a save pending is a repeat
  key press, and gets that save's result without a message.
- Load: read and decompress on the worker while the game runs, then apply.
  A slot load keeps the state it replaces for load undo at that point,
  rather than saving it in full before starting.
- Rewind: compression and decoding are worker jobs, and the deltas get
  zstd level 1 on top, which makes them about a third of the size.
- At most two save snapshots are in memory at once, and buffers are freed
  when their operation is done.
- Shutdown waits for pending writes, and drops callbacks as before.

On desktop, a save now costs the emu thread 2-3 ms, a load or rewind
12-15 ms.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 18:43:27 +02:00
Henrik RydgårdandClaude Opus 5.5 5d8566ee1e Serializer: Stream zstd when saving and loading state files
Saving compressed into a ZSTD_compressBound() buffer, about the size of
the whole state, and loading read the whole compressed file before
decompressing it. Now saving compresses into a buffer that grows with its
output and frees the uncompressed state before writing, and loading
decompresses the file in chunks. The file format doesn't change.

Saving also compresses before opening the file, which truncates it, and
drops the unused snappy path. LoadFile and SaveFile are public, for doing
the file part away from the state itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 18:43:27 +02:00
Henrik RydgårdandClaude Opus 5.5 d8df483fc4 docs: Design for async savestates
Move compression and file I/O off the emu thread, leaving it only the
snapshot and the apply, without wasting memory along the way. Not
implemented yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 18:17:52 +02:00
Henrik Rydgård 33a9d3072b Merge pull request #22665 from hrydgard/faster-firmware-install
Firmware install: Faster through parallelism
2026-10-19 18:02:05 +02:00
Henrik RydgårdandClaude Opus 5.5 2fbb67f207 lang: A savestate that fails to load is from a newer version, not an older one
States from older versions load fine. The string is now shown when a
section of the state fails to load, which in practice means a newer
build saved it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 17:58:36 +02:00
Henrik RydgårdandClaude Opus 5.5 ca72577702 Rewind: Keep the restored state, and measure "seconds ago" in game time
A rewind used up the state it restored, so rewinding, playing on a bit and
rewinding again went further back than the state you just came from. Now
the state stays, and only a rewind within 0.8 seconds of the last one
steps back past it.

"Seconds ago" was wall-clock time since the snapshot, which counted
pauses, fast-forward and time spent in states rewound to earlier. It's now
emulated time from where the rewinding started.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 17:58:36 +02:00
Henrik RydgårdandClaude Opus 5.5 b75f487dc5 SaveState: Report a state that fails to load as being from another version
When the file reads fine but a section doesn't, that's almost always a
state from another version, so say that (LoadStateWrongVersion), with the
version that saved it and the failing section on the second line.

LoadPtr() now returns just the section title, for callers to phrase.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 17:50:48 +02:00
Henrik RydgårdandClaude Opus 5.5 79c2b9b0fd Debugger: Add game.state.rewind and game.state.verify
Same as game.state.load/save, and the wait response now carries the
metadata string (for a rewind, how long ago the snapshot was taken).

Also fixes Broadcast() erasing a waiting entry while holding its mutex,
which could free the mutex while locked (the debug CRT aborts on it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 17:29:01 +02:00
Henrik RydgårdandClaude Opus 5.5 e3916c0acc SaveState: Fix rewind ring and savestate slot bugs
Rewind:
- first_ never wrapped, so after about 40 snapshots rewinding past the
  oldest state jumped back to the newest ones. Track a count instead.
- States whose base had been replaced stayed in the ring, and each one
  failed a rewind with an empty error. Drop them when the base goes.
- Rewinding after pausing for longer than the snapshot interval restored a
  snapshot taken just before the rewind ran.
- Rewind snapshots (and verification) counted as saves for IsStale().

Slots:
- Loading an empty slot replaced the load undo backup before failing.
- A refused save (hardcore mode) rotated the slot's screenshots first.
- Refused or dropped operations never called their callback, so auto-load
  didn't notice it had failed.
- The slot listing went stale after a save until something rescanned.

Also: Verify always failed (its write pass never set the buffer start),
and corrupt or truncated files were reported as being from an older
version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 17:29:01 +02:00
Henrik RydgårdandClaude Opus 5.5 b9a79b2375 Screenshot: Fix use-after-free when a savestate screenshot retries
The failure callback ran from inside g_pendingScreenshot. The savestate
one schedules a retry, which overwrote g_pendingScreenshot (destroying the
running callback and the path it was reading), and the caller then wiped
the retry anyway, so the screenshot never got taken. Move the pending
screenshot out before calling back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 17:29:01 +02:00
Henrik RydgårdandClaude Opus 5.5 6163bf5911 SaveState: Show which PPSSPP version saved a state when loading it fails
The version comes from the file header and goes in the failure message's
metadata line. A state from another version is the usual reason a load
fails. Headless prints it too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 16:28:47 +02:00
Henrik RydgårdandClaude Opus 5.5 9ddfcca670 Firmware install: Fewer and parallel file operations into the staging directory
Through Android's storage framework, every exists check, create and open is a round trip to the
storage provider, and writing each file took four of them (a check of its directory, a check of
the file, the create, the open). That made a 6.61 install take around 30 s on a phone, against a
fraction of a second of decrypting and inflating.

The install unpacks into a staging directory it has just cleared, so nothing in it can exist yet.
With the new outputDirIsEmpty option, each directory is created once and each file is created and
opened without checking for it first (File::WriteDataToNewFile), on the IO threads while decoding
goes on. A name that turns up twice still goes the careful way, overwriting the first. The swap
into place at the end is unchanged.

Headless --unpack-updater uses the same path when the output directory doesn't exist yet. Its
output is identical either way. With SIMULATE_SLOW_IO, unpacking the fonts went from 17 s to 7.5 s,
most of what's left being the fixed cost of opening the updater.

The UI's install already runs on a thread pool IO thread, so writes queued on the same pool
could stall behind other long IO tasks, and the pool drops queued tasks unrun at teardown,
which would have left the install waiting forever on its in-flight count. Eight threads owned by
the writer (attached for JNI) avoid both, and headless no longer has to start the pool for it.

Also compare file names case-insensitively when looking for repeats, since the storage usually
is case-insensitive and a second create there makes a renamed copy.

Checked with ThreadSanitizer, and on a read-only destination (every file fails, no hang).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 16:06:47 +02:00
Claude 363d816789 Game screen: Compress game as CSO
"Compress game" in the game screen's ... menu, for ISO, CSO, CHD and PSN
EBOOT.PBP games. It opens a list of the CSO presets (and Extract to ISO
for anything that's not an ISO already):

- Best: libdeflate level 12, 64K blocks
- PSP Compatible: zlib level 9, 2K blocks, readable by custom firmware

These match maxcso's presets, and the CSOs come out byte-identical to its.
The writer, Core/FileSystems/CSOCompress, reads the input through PPSSPP's
own block devices, so recompressing is a single pass with no temporary
file.

The original is deleted when done, unless "Keep the original file" is
checked (off by default). When the new name is taken, by a kept original
or another file, a number is added to it.

The work runs on a thread using every core, with a progress bar and time
left at the bottom of the game screen. It keeps going when the screen is
left, and the ... menu offers Cancel meanwhile. On the game list the
game's icon is covered by "Compressing...", and clicking it opens the
game screen instead of playing. When done, recents follow the new file
and the game screen and lists refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCxekYqPnzNr5d8Tp1q7aZ
2026-10-19 16:01:53 +02:00
Claude 4ab1066ade ext: Add libdeflate's deflate encoder
Only the compressor (deflate_compress.c, utils.c and their headers) from
libdeflate 1.7, the version LunaMoo/maxcso vendors, for writing CSO files.
Built as part of Core on every platform. MIT license.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCxekYqPnzNr5d8Tp1q7aZ
2026-10-19 15:58:54 +02:00
Henrik Rydgård a5275067b6 Merge pull request #22662 from hrydgard/atrac-codec-details
sceAudiocodec: Implement the latest discovered details, enable more tests
2026-10-19 15:08:16 +02:00
Henrik Rydgård cbbfb4a528 Merge pull request #22661 from hrydgard/ios-timer
Better monotonic clock on Apple platforms, fix the generic time fallback
2026-10-19 14:49:01 +02:00
Henrik Rydgård f8bc2d9095 Merge pull request #22660 from hrydgard/ui-fixes
UI: Fix input-thread races on held keys, wheel release and the F9 hack
2026-10-19 14:18:57 +02:00
Henrik RydgårdandClaude Opus 5.5 19b25a2e42 Common: Use CLOCK_UPTIME_RAW for the monotonic clock on Apple platforms
- Fixes #16823

CLOCK_UPTIME_RAW is mach_absolute_time() in nanoseconds (the same value, not
just the same rate). That's the clock MoltenVK reports VK_GOOGLE_display_timing
present times in, which time_now_raw() is meant to match. Apple's
CLOCK_MONOTONIC counts time asleep, unlike Linux's, so on a Mac that had
slept since boot, from_time_raw() of a present time was off by all of it.

It's also cheaper to read: ~19ns vs ~30ns on M1. mach_absolute_time() itself
is ~8ns, but returns ticks, and the timebase on Apple Silicon is 125/3.

Timing risk: time_now_d() no longer advances while the machine sleeps, so a
sleep/wake shows up as a small delta rather than a huge one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 14:17:52 +02:00
Henrik RydgårdandClaude Opus 5.5 79ccfa8fc7 Common: Share one non-Windows time implementation, fix the fallback
The fallback (Switch, BSDs, anything not Windows/Linux/Android/Apple) mixed
clocks: time_now_d() read the wall clock with gettimeofday, time_now_raw()
and Instant a monotonic one, from_time_raw() didn't subtract the start time,
and time_to_unix_utc() added the timestamp to the current time instead of
the start time.

Now every non-Windows platform shares the same code, built on time_now_raw()
and time_now_unix_utc(). Those two use clock_gettime where we know it's there
and std::chrono's steady_clock/system_clock elsewhere. Instant stores a
single nanosecond timestamp, and time_to_unix_utc() (which was missing on
Linux/Android/Apple) is start time plus timestamp, like on Windows.

The time origin is now set in TimeInit() rather than lazily on the first
time_now_d(), which raced between threads. iOS never called TimeInit(), so
it does now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 14:17:52 +02:00
Henrik RydgårdandClaude Opus 5.5 c293c38055 Add a bunch of hardware tests
Add the audio/atrac/headerfields and audio/audiocodec/at3errors tests
Add the audio/audiocodec/context test
Add the utility/savedata/noupdate and initstatus tests

headerfields already passes: SetData's end sample limit and a sample
offset of 0 counting as a frame match hardware.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 14:14:34 +02:00
Henrik RydgårdandClaude Opus 5.5 7a23b4d071 sceAudiocodec: Fill in the context the way the PSP does
Worked out by dumping the context after every call on hardware.
SceAudiocodecCodec now names all of them.

Atrac3+: CheckNeedMem and Init parse the format bytes (sample rate,
channels, frame size) into the context, and neededMem goes by the channel
count. Init also writes the bitrate and the output channels, checks the
rate (44100 or 48000), channels and frame size, and fails unless inited is
exactly 1. InitMono refuses stereo, and decode honours the output channels.

MP3: GetInfo, and every decode, read the whole frame header into the
context. Without a frame GetInfo fails and fills in -1, where we guessed.

AAC: Init takes the ME's list of sample rates (not 12000) and fails with
either flag byte set. GetOutputBytes works for AAC, and goes by the
context for Atrac3+ and MP3.

GetEDRAM gives every codec the same address, and ReleaseEDRAM clears the
allocation at 0x68 (pspautotests audio/audiocodec/context).

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