A zipped PS3 save folder has a PARAM.SFO and ICON0.PNG, the same layout
as a PSP save, so it was offered for install as one. PS3 saves have
CATEGORY SD in the SFO (PSP saves have MS) and a PARAM.PFD; either now
makes the zip unrecognized.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Android, Save picks a place with the system's save picker, through
a new BROWSE_FOR_FILE_SAVE implementation (ACTION_CREATE_DOCUMENT).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exporting a single save stays in the savedata manager's popup. Exporting
a whole game's saves is rarer, so it moves to the game's menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
--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>
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>
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>
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>
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>
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>
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>