Commit Graph
50675 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 ec36fad065 Savedata manager: Make the highlight outline show up
An outline with a zero radius isn't drawn at all, so use square corners
instead. Medium width, so it doesn't look like the gaps between rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:14:46 +01:00
Henrik RydgårdandClaude Opus 5.5 95e7f2d686 Savedata manager: Export a game's saves from the popup of one of them
The page-wide button depended on the game the screen was opened for, which
the screen doesn't show. The popup knows the game from the save itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:14:46 +01:00
Henrik RydgårdandClaude Opus 5.5 9be62bb7d5 Frame dumps: Share them as zips where folders can't be shown
Where the file can't be shown in its folder (Android, iOS), "Create frame
dump" in the dev menu now zips the dump and opens the share sheet. The
frame dump hotkey does the same when its message is clicked, so the game
isn't interrupted. Both go through OnFrameDumpCreated(), which moves the
share to the main thread, since the recorder calls back on the emulation
thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:14:46 +01:00
Henrik RydgårdandClaude Opus 5.5 f2c049d178 Translate the savedata export strings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:14:46 +01:00
Henrik RydgårdandClaude Opus 5.5 ba94ea9da8 Savedata manager: Export saves as zip files
- Fixes #23031

The savedata popup gets an Export button, which zips the save folder (or
a savestate and its screenshot) and hands it to ShareFilesAsZip: the
share sheet on Android and iOS, a Save As dialog on Windows. The zips
use the layouts the zip import already recognizes, so they import back.

Opened for a game, the manager outlines that game's saves and states,
scrolls to them, and offers to export all of the game's saves in one
zip. The game screen gets a button that opens it that way.

Installed data is never exported. It looks like a save (same PARAM.SFO
fields), but games write the installed files with sceIo rather than the
savedata utility, so they're missing from SAVEDATA_FILE_LIST.
IsInstalledGameData() counts a folder as installed data when the files
outside that list add up to 10 MB or more. The game screen's split
between save and install sizes now uses it too, instead of treating any
folder over 10 MB as installed data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:14:46 +01:00
Henrik Rydgård 362be19c51 Merge pull request #23037 from hrydgard/vulkan-pipeline-wait-lock
Vulkan: Don't hold a pipeline's lock while waiting for it to compile
2026-11-11 10:44:19 +01:00
Henrik RydgårdandClaude Opus 5.5 70b0bfec34 Vulkan: Don't hold a pipeline's lock while waiting for it to compile
When the render thread reaches a pipeline that isn't compiled yet, it waits
for it, and it did so holding the pipeline's mutex. EndCurRenderStep takes
that mutex for every pipeline a render step used, so the emulation thread
stopped in the middle of a draw until the compile was done: 302 ms in one
frame of a Tekken 6 fight on a Galaxy S8, for a pipeline that waited 251 ms
for its shaders and then compiled in 68 ms.

The render thread now only holds it to find (or make) the Promise, and waits
without it. DestroyVariants, which can drop Promises meanwhile, deletes them
through the deletion queue like the VkPipelines, so they outlive the wait.
The emulation thread now runs on until the frame queue is full.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 08:49:16 +01:00
Henrik Rydgård 6dfe2b1d89 Merge pull request #23036 from hrydgard/module-load-speed
Speed up module loading and unloading
2026-11-11 08:47:46 +01:00
Henrik RydgårdandClaude Opus 5.5 420db34118 MIPSAnalyst: Hash and name only the functions of the module just loaded
Each module load hashed every function found so far, rebuilt a hash-to-
function multimap of all of them to apply the hash map, reinstalled every
replacement and reloaded the builtin hash table. Unloading rebuilt the
multimap too.

Now FinalizeScan() takes the scanned range and does that work only for the
functions there (and any never hashed), looking each up in the hash map
directly, and the builtin table is loaded once. With knownfuncs.ini enabled
it still names functions everywhere, since the file can add names for any.

On a Galaxy S8, Tekken 6's sceUtilityLoadModule went from 67 ms to 13-23 ms,
its LoadModuleDone events from 48-57 ms to 12-18 ms, and unloading modules
from 13-22 ms to under 10.

Regression risk: code that changed in memory outside a module load is no
longer rehashed as a side effect of the next load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 23:17:01 +01:00
Henrik RydgårdandClaude Opus 5.5 7bc99a54ea SymbolMap: Update the active symbols per module on load and unload
Loading or unloading a module flagged the active symbol copies for a full
rebuild, a std::map insert per symbol of every module ever loaded, which the
next lookup paid. MIPSAnalyst's ApplyHashMap looks up labels right after a
load, so that was half the time of loading a firmware module.

Now just that module's symbols are added to or removed from the active
copies, without renumbering every function (the index only alternates
disassembly colors, and SortSymbols() still renumbers). On a Galaxy S8,
Tekken 6's sceUtilityLoadModule went from 126-139 ms to 67 ms, and its
LoadModuleDone events from 68-94 ms to 48-57 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 23:17:00 +01:00
Henrik Rydgård 5108c39cec Merge pull request #23035 from hrydgard/jit-compile-timing
IR JIT: Compile ~38% faster, and measure compile stalls
2026-11-10 21:51:57 +01:00
Henrik RydgårdandClaude Opus 5.5 b912c9e992 IR: Make IRWriter move-only
Nothing copies one: IRFrontend's copy constructor (for the debugger) leaves
its writers out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:50:12 +01:00
Henrik RydgårdandClaude Opus 5.5 16bb808a56 IR JIT: Remove what's left of function preloading
FindPreloadBlock (and HashMatches, which only it used) had no callers, and a
few comments still mentioned preloading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:49:35 +01:00
Henrik RydgårdandClaude Opus 5.5 f3eb70aac4 IR interpreter: Keep blocks' profile stats when the block table grows
JitBlock's move constructor didn't copy profileStats_, so every time the
vector of blocks reallocated, all execution counts and times went back to
zero, and --jit-profile under the IR interpreter only counted what ran since.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:49:09 +01:00
Henrik RydgårdandClaude Opus 5.5 81e1593eb4 Don't save the CPU core picked with --cpu
Like the other command-line overrides (--graphics etc). Running once with
--cpu=ir otherwise left the IR interpreter as the saved setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:32:52 +01:00
Henrik RydgårdandClaude Opus 5.5 d0e12caabb IR: Cheaper bookkeeping in PurgeTemps
Exits and barriers read every reg, and filled all 256 lastReadFrom entries
twice each, for GPRs and FPRs. That's now one index. The FPR lists are only
built for instructions with an FPR operand, and the working list holds IRInsts
rather than IRInstMetas (a quarter of the size). The output is the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:32:17 +01:00
Henrik RydgårdandClaude Opus 5.5 dcc51d8b18 IR: Keep IRWriter's instructions in a FastVec, and inline Write
The compiler didn't inline std::vector<IRInst>::push_back into IRWriter::Write,
which the frontend and every pass call for each instruction. FastVec's append
is a compare and a store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:26:45 +01:00
Henrik RydgårdandClaude Opus 5.5 235c5ba87b IR JIT: Reorder RegStatusNative to drop its padding
8 bytes instead of 12. Start() copies them for each block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:19:51 +01:00
Henrik Rydgård 55d87956fc Merge pull request #23032 from hrydgard/ghidra-client-uncap
Increase response size limit for ImStructViewer to 256Mb
2026-11-10 20:13:40 +01:00
Henrik RydgårdandClaude Opus 5.5 13e275ac24 IR JIT: Reuse scratch buffers instead of allocating for every block
PurgeTemps' and OptimizeLoadsAfterStores' working lists, IRApplyPasses'
buffers, the frontend's output writer, IRJit::Compile's instruction list and
the backends' per-instruction address list all keep their capacity between
blocks. The pass ones are static, since compiling only happens on the
emulation thread.

--jit-bench on a Galaxy S8: 21.4-22.4 -> 20.1-20.6 us per block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:11:03 +01:00
Henrik RydgårdandClaude Opus 5.5 1116a2a971 IR JIT: Bit masks for known constants, smaller reg status, skip Vec4 pass
- IRImmRegCache keeps which regs are known and written as bit masks rather
  than bool arrays, so they're quick to clear for each block, and FlushAll
  walks the set bits instead of memchr'ing.
- RegStatusMIPS is packed to 12 bytes from 24. Start() copies all 256 of them
  for each block.
- ReduceVec4Flush returns early for blocks without a Vec4 or matrix operand,
  which is most of them, rather than clearing its three 256-entry arrays.

With the inlining: --jit-bench on a Galaxy S8, 23.5-24.3 -> 21.4-22.4 us per
block. The IR is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:59:44 +01:00
Henrik RydgårdandClaude Opus 5.5 8b05f62e90 IR JIT: Inline the small helpers the passes and backends call per instruction
GetIRMeta(IROp), the IRAnalysis read/write queries (IRReadsFromGPR/FPR,
IRDestGPR, IRDestFPRs, ...), JitOptions::Disabled and the ARM64 emitter's code
pointer getters move to the headers. The passes call the IRAnalysis ones for
each instruction and each register they track.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:59:44 +01:00
Nemoumbra 69bc6ef8d8 Increase response size limit for ImStructViewer to 256Mb 2026-11-10 21:51:45 +03:00
Henrik RydgårdandClaude Opus 5.5 dd3496d63d IR JIT: Find native blocks by start with a DenseHashMap, inline reg checks
FindBlockByStart runs for every exit and link. IsValidGPR/IsValidFPR are asked
all the time by the register cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:45:35 +01:00
Henrik RydgårdandClaude Opus 5.5 7079ea8d18 IR: Keep PurgeTemps and OptimizeLoadsAfterStores linear
PurgeTemps went through every check since the first live one for each
instruction, though most end soon after they start. Ended ones are now
dropped. OptimizeLoadsAfterStores skips looking at what an instruction writes
when it knows of no loads or stores. The output is the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:42:58 +01:00
Henrik RydgårdandClaude Opus 5.5 c046b83ce9 Android: Link ppsspp_jni with -Bsymbolic-functions
Calls between functions in the library went through PLT stubs, since it's
built with default visibility. The stubs were about 5% of JIT compile time on
a Galaxy S8 (the IR passes call many small helpers in other files), and a few
percent of the emulator thread in general. --jit-bench: 27 -> 25 us per block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:40:09 +01:00
Henrik RydgårdandClaude Opus 5.5 c3d4ad3485 ARM64 IR JIT: Fix storing regs in pairs at exits
Since decccf199a, FlushAll's needsFlush() only accepted regs in memory, so no
pair was ever stored with STP, and the loop over all 256 regs found nothing.
It now takes regs that are out of memory (an IMM always counts as dirty),
respects the gprs/fprs arguments, only looks where STP can reach (regs 1-63),
and stores adjacent IMMs in the upper GPR range (like HI and LO) together.
A REG_AS_PTR reg is also unadjusted by its native reg, not the IR reg.

The x86 IR JIT has the same inverted check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:38:24 +01:00
Henrik RydgårdandClaude Opus 5.5 5c2521729d IR JIT: Compile faster
Measured with --jit-bench on a Galaxy S8, recompiling the 11243 blocks of a
Tekken 6 state: 363 ms -> 300-310 ms, with the ARM64 pairing fix.

- FlushAll, which runs at every exit, no longer looks at all 256 regs. FPRs are
  found from the native regs, and the rest are skipped quickly when in memory.
- IRApplyPasses alternates between two buffers that keep their capacity,
  instead of allocating a new one for each pass.
- The ARM64 backend flushes the icache once per block, for the block's code and
  every exit linked to and from it, with one set of barriers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:38:19 +01:00
Henrik RydgårdandClaude Opus 5.5 f4b35e08f7 IR JIT: Add --jit-bench, and time compiles with the CPU counter
--jit-bench=N recompiles every block in the cache five times after N flips
(at the next safe point), and logs each run and the best, split by phase. With
a state loaded, it gives the same set of blocks every time, so changes to the
compiler can be compared run to run. Game frames vary too much for that.

The compile timing reads cntvct_el0 on ARM64 Linux/Android rather than calling
clock_gettime(), through time_now_fast_ticks(). The counter's rate is measured
against time_now_raw(), since a Galaxy S8 reports 0 in cntfrq_el0 (it runs at
26 MHz there).

The IR pass list in DoJit is a fixed array instead of a vector.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:38:19 +01:00
Henrik RydgårdandClaude Opus 5.5 6860e78bf7 IR JIT: Time compilation, and log frames that spend long compiling
Totals per phase (frontend, passes, backend, finalize/linking, invalidation and
full clears) go in the JIT stats overlay, and the frame graph draws each frame's
compile time in orange at the bottom of its bar. A frame that spends 2 ms or
more compiling logs a "JIT stall" line with the breakdown.

Each phase boundary reads the clock once, since that costs around 0.3 us on a
Galaxy S8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:14:08 +01:00
Henrik Rydgård efdf633d8a Merge pull request #23030 from hrydgard/empty-frame-handling
sceVideocodec: Fail decoding an empty access unit, as the PSP does
2026-11-10 18:49:53 +01:00
Henrik RydgårdandClaude Opus 5.5 6e70f2b193 sceVideocodec: Fail decoding an empty access unit, as the PSP does
sceMpegAvcDecode called again on an access unit it has already decoded passes
the ME an empty one, and on a PSP that fails with 0x80628002
(pspautotests video/mpeg/decoderepeat). We returned success with no picture.

A movie player that retries the same access unit until a picture comes back
then never got out of its loop, and as the decode took no time, it starved the
lower-priority thread that feeds the ringbuffer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 18:23:55 +01:00
Henrik Rydgård 0155d9be34 Merge pull request #23029 from hrydgard/misc-fixes
Font style refactor, custom Vulkan driver fallback, firmware .nomedia
2026-11-10 16:43:52 +01:00
Henrik RydgårdandClaude Opus 5.5 f53b614be4 AGENTS.md: Don't include VulkanLoader.h from UI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 16:15:36 +01:00
Henrik RydgårdandClaude Opus 5.5 879933abce Fix the Linux build: keep the Vulkan headers away from the UI ones
X11, which the Vulkan headers bring in, defines Screen. NativeApp gets the
custom driver functions from VulkanDriverVars.h instead of VulkanLoader.h,
and the GPU debug overlays take their font from the caller instead of
including View.h for the theme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 16:13:47 +01:00
Henrik RydgårdandClaude Opus 5.5 e361ba7ae1 Translate the custom Vulkan driver failure notice
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 15:09:16 +01:00
Henrik RydgårdandClaude Opus 5.5 b44e93b3c6 Android: Check for a crashing custom driver on Android 11+ too
There the backend is never given up on, but a custom driver that crashed
Vulkan last time is cleared, and Vulkan is tried again with the system's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 15:09:16 +01:00
Henrik RydgårdandClaude Opus 5.5 3c491b7423 Vulkan: Retry with the system driver when a custom driver fails
Before falling back to OpenGL. Covers the availability check, a library
that won't load, a failed InitAPI and a crash on the last run. The setting
is cleared and a notice names the driver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 15:00:39 +01:00
Henrik RydgårdandClaude Opus 5.5 59c9084fed Firmware: Create the NAND's .nomedia on every install
Installing from an updater file left it out, so Android's media scanner
indexed the firmware. Also only creates it when missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:33:12 +01:00
Henrik RydgårdandClaude Opus 5.5 822bae58e6 UI: Pass the font style to each text draw, drop UIContext's current style
What a draw used depended on whatever was set last, often by another view,
so several draws only got the right font because something before them
happened to reset it. Those now pass the theme's font, which is what they
got in practice.

DrawTextRectSqueeze also measured with the theme's font whatever style it
drew with, so small text could be squeezed more than it needed to be.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:31:41 +01:00
Henrik Rydgård e7660457ca Fix font bug in InfoItem 2026-11-10 14:23:50 +01:00
Henrik Rydgård 14eebd9b09 Add Gitaroo Man Lives! to compat.ini for CenterLines, fixing title screen bug 2026-11-10 14:18:48 +01:00
Henrik Rydgård 8b993e27fb Merge pull request #23028 from hrydgard/misc-fixes
Misc UI reordering and behavior fixes
2026-11-10 13:51:07 +01:00
Henrik RydgårdandClaude Opus 5.5 daeb18ecc8 Settings: Move PSP firmware to the System tab, as "PSP firmware (XMB)"
Gives it an XMB icon, and points the PS1 firmware errors at its new place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 13:29:38 +01:00
Henrik Rydgård 767532b2c7 Reorder settings some more. System tab is now second after General. 2026-11-10 13:07:02 +01:00
Henrik RydgårdandClaude Opus 5.5 31c0a927c4 Settings: Switching tabs opens the tab on its root page
Not when going to a search result. Reselecting the current tab while
searching does nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 13:06:29 +01:00
Henrik RydgårdandClaude Opus 5.5 e57c8ba756 Settings: Clicking the current tab goes back up to its root page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 13:06:28 +01:00
Henrik Rydgård 7cb6276a00 Log fix, add some more sprite sampling compat entries 2026-11-10 13:06:28 +01:00
Henrik Rydgård f8632abf70 Merge pull request #23026 from hrydgard/lag-sync-adaptive
Rework "Force real clock sync": Pace without spinning, shrink the audio buffer, call it Low-latency CPU timing
2026-11-10 13:01:30 +01:00
Henrik Rydgård 7ee4ca4227 Merge pull request #23025 from hrydgard/branch-in-delay-slot-test
CPU: Branches and jumps in delay slots as on hardware
2026-11-10 12:45:24 +01:00