mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
367b0b7e14f5a0539ed9f9e041bce79fb7ebdb4f
11936
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
367b0b7e14 |
GS: report replacement files shadowed by an earlier file
The palette fallback moved Persona 3 FES from 189 hits / 323 misses to 441 / 71, with ZERO of the remaining misses matching a name under another palette -- so the palette hash is settled, and what is left is textures the map does not hold under that name at all. The base HD pack now applies; the layered mods still do not. emplace does not overwrite, and this scan is RECURSIVE. Two files in different subdirectories that decode to the same texture name collide, and whichever the directory walk reaches first wins -- silently, with nothing to say which of them ended up on screen. That is exactly how a mod layered over a base pack loses: not on intent, on traversal order. Count the collisions, name the first six with both paths, and report files scanned alongside textures indexed. "My mod is not applying" and "my mod is being shadowed by another pack" are indistinguishable to the person reporting it, and until now they were indistinguishable from the log too. Nothing about which file wins changes here. Measure first: if the count is zero, the layered mods are absent rather than shadowed, and that is a different fix. |
||
|
|
d9252de06e |
GS: palette-relaxed replacement fallback (TEST BUILD, not for release)
A paletted texture is looked up by TEX0 hash AND palette hash, so a pack only applies while the game asks for a palette its packer happened to dump. The Persona 3 FES logs land exactly there: the pack carries several palette variants per glyph (ea34cb7b, df582335, b6eebe9e, 99b15967 ...) and the game asks for cf4df1c018862d85, which is none of them. Every glyph misses while the unpaletted art around it replaces fine -- 189 hits alongside 323 misses -- which is why the scene looks right and the font does not. Worth recording what this ISN'T, since each cost a round to eliminate: the cache budget (already present in the working version), the pre-ELF hack strip (transient, ApplyCoreSettings restores it), paltex (it relaxes the hash cache, never the replacement lookup -- CreateTextureName always uses the real palette hash for paletted formats), and name parsing, the hash function and the paltex defaults, all byte-identical to 2.6.6. So when the exact palette is missing but the pack holds this TEX0 under others, take one -- lowest palette hash, so the choice is reproducible rather than dependent on map iteration order. NOT correct as shipped, and deliberately labelled so: the replacement image has the packer's palette baked in, so substituted art can carry the wrong tint. It exists to answer one question. If the mods appear, the palette hash was the only obstacle and the real fix is about WHICH palette to prefer, not about loading at all. If they still do not appear, the pack was never the whole story. |
||
|
|
ddcfd39dab |
GS: print the pack's own filenames, instead of asking the player for them
The previous diagnostic could say "the pack holds this TEX0 under a different
palette" but not which, and the difference is the entire diagnosis:
<tex0>-<clut>-<w>x<h>-<bits>.ext a real, different palette hash -- the palette
contents differ at run time from dump time
<tex0>-<w>x<h>-<bits>.ext no palette field, so it indexed with
CLUTHash=0 and can only match under paltex
Answering that meant asking someone to list a directory on their own device. The
people who hit this are players; that is not a reasonable thing to ask, and it is
information the emulator already has in memory.
So print it: up to two matching entries per miss, with their CLUT hash and
filename, plus three sample names at map load so the pack's naming style is on
the record even when nothing misses.
Bounded the same way as the rest of this logging -- at most eight misses reported,
at most two entries each -- because it walks the filename map, and this game's
pack has 8339 entries in it.
|
||
|
|
0fe5715336 |
GS: make the replacement diagnostics able to answer the question
The first version could not. LookupReplacementTexture runs once per NEWLY HASHED
texture, not per draw, so a whole session can be a few hundred calls -- and a
report threshold of 20k therefore printed once, at the very first lookup, and
never again. Two tester logs came back reading "0 hits, 1 misses" after 139 and
195 seconds of play, which cannot distinguish one lookup from twenty thousand.
That ambiguity was the whole answer they were supposed to give.
Three changes:
- Report on a geometric schedule (1, 2, 4, 8 ... then every 4096) rather than a
fixed threshold. Bounded whatever the rate, and dense at the start, which is
where the answer usually is.
- Print the first eight misses in full: the TEX0 hash, CLUT hash, dimensions
and PSM actually asked for, plus whether the pack holds that same TEX0 under
a DIFFERENT CLUT hash. That is the difference between "the pack does not have
this texture" and "it has it, under another palette hash" -- and the latter is
the usual answer for paletted UI art, which is what a Persona 3 FES font and
menu panel are.
- Log preloading, paltex, async and upscale once at map load. Those decide
whether a lookup is ever ATTEMPTED rather than whether it matches; a pack that
indexes thousands of files and is then never consulted looks identical from
outside to one that misses every time.
|
||
|
|
b5b2ed24e1 |
GS: say whether replacement lookups are hitting, not just how many indexed
"My texture pack does nothing" has two failure stages and the log only covered the first. The indexed count proves the FILES were found and their names parsed; it says nothing about whether any draw ever asks for one. A pack that indexes thousands of textures and misses every lookup is a hash problem -- wrong dump settings, paltex vs CLUT, wrong upscale -- and from outside it looks exactly like a pack that never loaded at all. Both read as "the mods are not applying". Count hits and misses in LookupReplacementTexture and summarise one line per 20k lookups. Summarised rather than logged per lookup because this runs per draw, and log volume on its own is enough to stall the emulator. Also counts how many misses WOULD have matched with the CLUT hash zeroed. That separates "the pack does not contain this texture" from "it does, but the palette hash differs", which is the usual answer for paletted UI art -- fonts and menu panels, the exact things a Persona 3 FES mod replaces. Counters reset in ReloadReplacementMap: carried over from a previous game, a stale hit count reads as a healthy pack. Also -Wno-missing-braces on the savers target. Welsh's sources initialise nested aggregates without inner braces throughout, which -Wall diagnoses once per site per TU: ~5 million lines and an 842 MB build log, which is how it was noticed. Upstream code we do not restyle, so the diagnostic has nothing to tell us. Purely a diagnostic flag; no codegen change. |
||
|
|
c3f859270f |
Credit the PR contributors in the code, not only the release notes
Merging preserves authorship in git history, but nobody reads git log to find out who wrote a file. The tree already had the convention -- "Feature contributed by misantronic (PR #391)" on exportRecentGamesPublic -- it just was not applied to the three PRs merged here. MemoryCardBackup.kt bmdhacks (PR #608) PerGameOverrides.{h,cpp} bmdhacks (PR #593) RecentGamesContentProvider.kt misantronic (PR #566) The provider's line also records that the opt-in gate was added on merge, so the gate is not mistaken for part of the contribution. |
||
|
|
ff2b0c2155 | Merge branch 'pr-593' into jit-android-catchup-gv7 | ||
|
|
2c36725e11 | Merge branch 'pr-608' into jit-android-catchup-gv7 | ||
|
|
9262ea0480 | Merge remote-tracking branch 'origin/master' into jit-android-catchup-gv7 | ||
|
|
d9b961eb36 |
Community batch: seven feature requests
Second screen (BrainBeat, NiceRon): - The panel picked its display by "not DEFAULT_DISPLAY", so launching ARMSX2 on the second panel put the panel on top of the running game. Anchor on the display the activity is actually on, and re-pick it on every resume rather than only on a foreground change. - Restyle the panel: it inherits the system dialog theme, not the app's, which is why it looked like a stock Android dialog. Dark ground, rounded tiles, one accent, painted in code since a Presentation is outside the Compose tree. - Customisable grid: SecondScreenTiles declares the tiles (stats, actions, macros, achievements, hide-panel), SecondScreenLayout stores which and in what order, and App settings edits both plus the column count. Stored by tile id, not ordinal. Achievement tile shows collection progress plus whatever unlocked this session -- the snapshot carries no timestamp, so "recent" is the locked-to-unlocked edge on the panel's own tick. Library (Isshin, GBSUPREMO): - Cover region per game, overriding the library-wide choice; "Library" is the absence of a pin, not a fifth region. Both cover components now subscribe to the region state -- game.coverUrl resolves it inside a plain getter, which Compose could not see, so cards kept their old art. - Memory cards reachable from the long-press menu. The card picker already did per-game assignment whenever handed a game; only the in-game menu ever handed it one. In-game (Sizor, Grayy): - Quick menu docks left or right. Alignment, slide direction, rounded corners and inset all move together. - Stats position (four corners), driving PCSX2's own OsdPerformancePos. Stored as the core's enum ordinal so there is no translation table to keep in sync. - Cycle-display-refresh hotkey, using preferredDisplayModeId -- the frame-rate vote in EmulationSurface is a hint the compositor may ignore, which is right for latency and useless as a user-facing toggle. - Analog Sticks section in the quick menu, extracted from PadTab the same way Gyro and Macros already were. Also drop the -fexceptions/no-PCH carve-out on GSLsfg.cpp: it existed because lsfg-vk-android reported failure by throwing, and the Eden port has no throw sites. |
||
|
|
8c0ae12398 |
Patch: stop Hardcore blocking presentation patches
Reported by EddyOP (60 FPS and Widescreen disabled under RetroAchievements
Hardcore) and diagnosed by Jetup, who found that moving the same lines under a
widescreen heading re-enabled them — ARMSX2 issue #541.
The Hardcore gate from
|
||
|
|
879d07209c |
GS: size the texture cache as RAM minus a reserve, not a fraction of it
Third attempt at this budget, so the reasoning is written down properly.
Uncapped OOM-killed Android on a 5 GB uncompressed Persona 3 FES pack. Capping
at RAM/4, then RAM/2, stopped that and broke the same pack on 8 GB devices where
it had been working — 5 GB against a 4 GB budget evicts continuously, each load
dropping the previous one. That is the Persona 3 FES report: corruption first
(a failed upload injected with undefined contents, before
|
||
|
|
7bbe5b2fc1 |
GS: remove the texture-replacement cache cap
Reverses the 2026-07-20 policy. Reported by JustVibin247 for Persona 3 FES: mods worked on 2.6.6 and stopped on 2.6.6.1, first showing as corruption and later as simply not applying. The cap was added to stop a 5 GB uncompressed Persona 3 FES pack OOM-killing Android mid-load, and it did stop that. It also broke every setup where an oversized pack had been working. Budget was RAM/2, so on an 8 GB device that same 5 GB pack sat permanently about 1 GB over and evicted continuously — each load immediately dropping the previous one. That produced both reported symptoms in the order they were reported. The churn means constant re-upload; before |
||
|
|
f6ddff79bc |
LSFG: expose adaptive frame pacing, and remove the diagnostic tracing
The pacer shipped in the previous commit but was inert: GSConfig.LsfgTargetRate defaulted to 0, which means "hold the multiplier fixed", and nothing in the Android settings could change it. So the port ran, but the specific problem it was brought over to solve — games that oscillate between 60 and 30fps on a 60Hz panel, where a fixed multiplier presents 120 then 60 and judders at every transition — was still there. Working but inert is the failure mode worth naming: nothing errors, the feature simply does not do the thing it was for. Plumbed through the usual twelve places (the field, INI read and write, the differs chain, toJson/fromJson, both per-game override paths, the reset list, the search index, the strings, and the two call sites), plus the two C++ ones in Pcsx2Config. Presented as a switch rather than a number. The pacer needs a concrete Hz, but picking one by hand is not a decision anyone can make usefully and the only sensible answer is the panel's own refresh rate — so the UI writes that when the toggle goes on, and 0 when it goes off. Off remains the default, so behaviour is unchanged until it is asked for. Also removes the step tracing added while chasing the Turnip crash. It did its job: five rounds of reading the code produced three wrong theories, and the trace produced the answer in two. The reasoning it uncovered is in the comments, which is where it belongs — the instrumentation is not. The new strings live in the github-only table, so the Play split still holds: playDebug has zero class files containing 'Lossless' or 'perf.lsfg', githubDebug has 2 and 4. |
||
|
|
2080bd1c44 |
LSFG: make frame generation actually run on device
Verified working on an Adreno 740, on BOTH the stock Qualcomm driver and Turnip:
an interpolated frame presented for every rendered frame, no crash.
Six real defects between 'compiles' and 'runs', all mine. Recording them because
every one of them compiled cleanly and several looked like somebody else's bug.
★ THE LAST ONE, and the least guessable. Waiting on a semaphore signalled by a
SECOND vkAcquireNextImageKHR within one frame segfaults inside Turnip at
vkQueueSubmit. Stock Qualcomm accepts it; Turnip does not. The extra acquire now
signals a FENCE which we block on before recording, so the submit waits only on
the caller's render-finished semaphore — exactly what a non-generating frame
does, and that shape always worked. It costs a short CPU stall per generated
frame, still far cheaper than the two full device idles per frame the old
implementation paid.
The tell was in the trace, not in the code: working frames submitted
waits=1 signals=1, the frame that died submitted waits=2 signals=2. Everything
else about that frame — the dispatch, the copy, the fences, the presents — was
identical.
The other five:
· __fi on a free function in a header. PCSX2's __forceinline is
__attribute__((always_inline, unused)) with NO inline keyword, so every
including TU emitted its own copy: duplicate symbol at link, from a header
that compiles perfectly alone. Pcsx2Defs.h provides __forceinline_odr for
exactly this and the rest of the renderer only uses __fi inside class
bodies, where members are implicitly inline.
· Acquire budget. Vulkan allows imageCount - minImageCount + 1 images held at
once and the presented frame already holds one, so with min=3 and 3 images
the budget was ZERO. Acquiring anyway is undefined behaviour, not a failed
call. GetImageCount() - 1 was simply the wrong bound.
· Swap chain image count. Asking for base + 1 does nothing: on FIFO the base
is 2, so it clamps straight back up to minImageCount. The request has to be
anchored to minImageCount, or the budget stays zero and frame generation
silently never runs with nothing reporting an error anywhere.
· One command buffer, one semaphore set, no fence. Resetting a buffer that is
still executing and resubmitting one that is still pending are both
undefined. The OLD implementation had the same single-slot arrangement and
got away with it because it called vkQueueWaitIdle twice a frame — removing
those idles is the entire point of this port, and it removed the accidental
serialisation that made reuse legal. Now one slot per swap chain image, each
with its own fence.
· Initialisation order. Moving image allocation into CreateResources without
moving the allocator ahead of it dereferenced an empty std::optional and
killed the GS thread during BIOS boot — before frame generation would ever
have produced a frame, so it presented as an entirely unrelated crash.
Generated frames also now go into images WE own and are copied into the acquired
swap chain image, rather than being dispatched straight into it through a
storage view. That theory did NOT fix the crash — but it is what both Eden and
the old implementation do, it asks nothing unusual of the WSI, and it let the
swap chain drop VK_IMAGE_USAGE_STORAGE_BIT entirely, which removed the
'enable it, then restart the renderer' wart along with it.
Diagnosis was step-tracing the present path, not reading it: five rounds of
reading produced three wrong theories, and the trace produced the answer in two.
The instrumentation is removed; the reasoning is in the comments.
|
||
|
|
0bfefd4b69 |
LSFG: run frame generation on our own device, and delete the old path
Completes the switch to the Eden port. GSLsfg keeps its entire public surface — availability, status text, display FPS, the settings and OSD plumbing all untouched — and only its internals change, so nothing above the renderer had to move. What actually changed on screen: the old implementation ran the interpolator on a SECOND VkDevice and shared images as AHardwareBuffers, and because Android offers no cross-device semaphore (Turnip rejects OPAQUE_FD export on AHB memory) the only barrier available was a full device idle — twice per frame, every frame. That is gone. Generation is now ordinary compute recorded into a command buffer on the device we already have, and interpolated frames are written STRAIGHT into an acquired swap chain image through a storage view, so the intermediate copy is gone too. The pacer comes with it, which is the fix for games that oscillate between 60 and 30fps on a 60Hz panel: the generation count now varies to hold the presented rate near a target instead of blindly multiplying whatever the game produced. ★ ONE submit, N+1 semaphores. All the generation work goes into a single command buffer, submitted once, waiting on the caller's render-finished semaphore plus every acquire, and signalling one semaphore per present that follows. The obvious alternative — a submit per generated frame — walks straight back into the binary-semaphore bug this file was bitten by before, where the real present and the first generated present both want to wait on the semaphore that says the source has been read. A binary semaphore may be waited exactly once. ★ The hook fires AFTER vkQueueSubmit, so FrameGen had to take its command buffer as a parameter. It was written against GSDeviceVK::GetCurrentCommandBuffer(), which at that point is in flight or already belongs to the next frame; recording into it is undefined and the symptom would have been interpolation running a frame late rather than anything resembling an error. Layout bracketing is ours: the ported passes speak Eden's convention where a presentable image lives in GENERAL, and PCSX2 hands them over in PRESENT_SRC_KHR and needs them back in it. The swap chain now requests VK_IMAGE_USAGE_STORAGE_BIT — but only when frame generation is on AND both the surface and the chosen format allow it. Asking unconditionally fails swap chain creation outright on drivers that do not, which would take the whole renderer down for a feature that is switched off. The format half is the easy one to miss: a surface can report STORAGE support while the sRGB format picked for it has no STORAGE_IMAGE feature bit, and that only shows up later as a validation error at image-view creation. Because usage is fixed at creation, switching the feature on mid-session needs a renderer restart; Initialize says so rather than failing silently. DELETED: platforms/android/app/src/main/cpp/3rdparty/lsfg in full — the lsfg-vk-android framegen library, the DXVK dxbc compiler, pe-parse, volk and its 759-symbol collision with VKLoader, the C ABI shim, the version script, the separate .so and the dlopen that found it, and the -fexceptions carve-out they needed. GSLsfg.cpp went from 1259 lines to 654. The ~130 MB configure-time fetch goes with it. build-play-aab.sh's guard was rewritten rather than dropped: it checked for a file that can no longer exist either way, so it would have passed forever without proving anything. It now looks inside the core for a symbol only the ported implementation defines. Verified: all 18 affected translation units compile without errors, with ARMSX2_HAS_LSFG on AND off (the play flavour still compiles the feature out entirely). Not yet run on hardware. |
||
|
|
5e1d979b4e |
LSFG: port Eden's frame generation (passes, pacer, DLL reader)
Ports the frame-generation implementation from Eden (eden-emu PR #4263), which is a substantially better design than the lsfg-vk-android one we currently ship. Why it is better, concretely. Ours runs framegen on its OWN VkDevice, shares images through AHardwareBuffer, and — because Android gives no cross-device semaphore, Turnip rejecting OPAQUE_FD on AHB memory — uses full device idles as its only barrier. Eden's runs as ordinary compute on the device we already have. It also needs none of what ours drags in: no DXVK dxbc compiler (its shader translate is a SPIR-V validate plus a descriptor-binding renumber, because current Lossless.dll ships SPIR-V in its RCDATA resources), no pe-parse, no volk and its 759-symbol collision with VKLoader, no separate .so, no C ABI, no dlopen, and no -fexceptions carve-out. It also brings a real frame PACER, which is the answer to games that oscillate between 60 and 30fps on a 60Hz panel. A fixed multiplier presents 120 then 60 there and judders at every transition; the pacer varies the generation count to hold the OUTPUT near a target instead. New GSConfig.LsfgTargetRate drives it, defaulting to 0 = the existing fixed-multiplier behaviour, so this is opt-in. Nothing is wired up yet — GSLsfg still drives the old path. This commit is the ported library only. ★ The load-bearing decision is LsfgVkCompat. The pass code is written against yuzu's RAII wrapper and its Device/MemoryAllocator, which PCSX2 has no analogue for. Rather than rewrite ~2000 lines of call sites, the slice of that API the code actually uses is reimplemented over PCSX2's raw handles and VMA — it came to five command-buffer methods, three Device queries, two allocator entry points and eight handle types. The result is that every pass body is BYTE-IDENTICAL to Eden's, so upstream fixes stay a readable diff instead of a merge puzzle. Deliberate departures, each commented at the site: · paths are std::string, not std::filesystem — the GS backend uses neither · CityHash -> GSXXH3_64bits, already used elsewhere in GS · the shader cache gained mtime + a flags field so a hit costs a stat() rather than a full read, hash and PE walk of the DLL on every launch; Eden keys on a content hash and so must read the whole file before it may look at the cache. GSLsfg.cpp already validates on size+mtime, so this matches the tree. · Eden's RemoveInstalledLosslessDll() is NOT ported. It deletes the DLL, which is safe there because Eden owns that file; here the path is whatever GSConfig.LsfgDllPath says and nothing checks it points inside our storage. Only the cache half is kept, as ClearShaderCache(). · vk::Buffer gained Flush(). The port initially dropped Eden's flush because the shim had nothing to flush through. That write is the shader's entire uniform block, and the failure mode is not a crash — it is interpolation reading stale constants, which reads as a motion artefact, not a bug. Verified: all 14 translation units compile clean against the real PCSX2 headers under -Wall -Wextra. The reconstructed util.cpp helpers were diffed against the genuine Eden source fetched from the merge commit — the extracted diff hunks in the working copy are PARTIAL, added lines only, so they were not safe to trust. |
||
|
|
cd20f6454d |
Android: flush memory card writes when the app is backgrounded
A memory card write does not necessarily reach the file system when it happens,
and on Android that is data loss rather than a detail.
A FOLDER card holds writes in an in-memory page cache and flushes two frames
after the last one, counted down by the per-frame tick that runs off vsync. So
pausing does not delay that flush, it stops it ever being reached -- the counter
does not advance at all while the VM is paused.
A FILE card writes through stdio with no flush anywhere in the path. Seeking on
an update stream pushes the previous write out, so a run of writes mostly
self-corrects, but the last write of a save sequence sits in the buffer until the
next card access or fclose.
Either way the pending write is lost if Android reclaims the process while it is
backgrounded, which it may do with no further callback. Save in-game, switch
apps, get reclaimed, and the save was never on disk.
The pause path already handles exactly this shape for the BIOS NVRAM
(cdvdSaveNVRAM, added because "the process is frequently killed while paused"),
so the card flush goes next to it, in both the Running and already-Paused
branches. It runs on the CPU thread, queued after SetPaused, so the console is
stopped and nothing can be written behind it, and it is fire-and-forget -- onPause
is on a deadline and blocking it risks an ANR.
- FileMcd_Flush() / FileMemoryCard::Flush() / FolderMemoryCardAggregator::Flush()
write out what is buffered without closing anything, so the console keeps
playing afterwards.
- FileMemoryCard::Flush deliberately does NOT stamp the running checksum the way
Close() does. That value is a change-detector a savestate load compares to
decide whether the card moved under the console, not an integrity check, and
m_chkaddr is card data rather than a header field we own. A stale value costs
one auto-eject on the next savestate load, which is the safe direction, so
writing to the card on a path upstream never writes on buys nothing.
- FolderMemoryCard::FlushNow clears the frame countdown so a resumed VM does not
repeat the work. Flush() is already a no-op when nothing is cached, so calling
this on a quiet card costs nothing.
- Save() flushes each sector as it is written.
Also bounds the emulation-thread join in onDestroy. NativeApp.shutdown() already
gives up waiting after 5 s and returns anyway, so an unbounded join inherited a
wedged CPU thread and hung the destroy path until Android force-closed us.
|
||
|
|
df17121cc1 |
SIO/Memcard: refuse a card write whose read-back failed
FileMemoryCard::Save is a read-modify-write: it reads the sector being written into a scratch buffer, ANDs the new data into it (memory card bits only ever go 1->0 without an erase), and writes the result back. When the read failed it reported the error and then carried straight on into the merge. m_currentdata is only ever grown, never cleared, so the merge ran against whatever an earlier -- and possibly completely unrelated -- write had left in the buffer, and that was what got written to the card. A transient read failure therefore did not lose a write, it corrupted a sector the console never asked to change. Refuse the write instead. The sector keeps its previous contents, which the console can retry. The return value is discarded by the only caller (Sio.h), so this reads as "don't write" rather than as an error report -- the same shape as the two Seek failures already in this function. Desktop hosts rarely see a read fail on a card file. Android does: the storage memory cards live on is FUSE-emulated, where this tree already documents libc calls being denied outright. |
||
|
|
dfef534426 |
UI: the Android internal-resolution set in the Qt and Big Picture pickers
Both desktop resolution pickers ran Native, 2x, 3x ... in whole steps. That is the wrong shape for a handheld at both ends: below native is unreachable even though rendering there is a large win and the GS accepts it -- only the top end is clamped, in GSClampUpscaleMultiplier -- and above native the jump from 1x to 2x is four times the pixels with nothing in between. Both pickers now carry the set the Android UI has offered since issue #207: 0.25x 0.5x 0.75x Native 1.25x 1.5x 1.75x 2x 2.25x 2.5x 2.75x 3x 3.5x 4x 5x 6x 7x 8x Everything above 8x moves behind Extended Upscaling Multipliers, which used to unlock 13x and up. That checkbox was dead on ordinary hardware: the GPU cap is max texture size / 1280, so a 16K-texture part reports exactly 12x and the "supports extended" test wanted more than 12x. It now unlocks anything past 8x, which is the first time it does something on a normal GPU, and 9x-25x still appear only as far as the GPU can actually go. Big Picture indexed two parallel arrays as "slot i means multiplier i+1", which a non-uniform list breaks. Replaced with one table carrying the label, the INI string and the multiplier, filtered against the cap. The quarter steps are all exact in binary, so matching a saved multiplier by equality is safe. Two things fixed in passing, both of which the new list would otherwise have broken. The Qt global tab keyed "nothing found" off index 0 being Native, and index 0 is now 0.25x. And Big Picture passed "1.000000" as its default while the INI is written by StringUtil::ToChars, which produces "1" -- so with no key set the picker read back a value matching no entry and displayed Unknown. |
||
|
|
05d94ed82d |
iOS: download the RetroArch shader collection from inside the app
867 presets over 27 categories, one manifest and one zip each, and no third request anywhere. A tester asked for what Manic EMU has: a button that fetches the collection instead of making people find a zip and side-load it. The closure resolution and the licence sign-off landed first; this is the phone half. Order is the whole safety argument, because a remote manifest is attacker- controlled if the host is. The stated size is refused before the transfer rather than after -- the manifest carries it, so the refusal costs nothing. The received byte count and the SHA-256 are both compared before the importer is called, and the hash is streamed rather than read whole. The relative path is validated before it becomes a URL, because .. and / both survive percent-encoding. Then the fenced extractor does the writing, unchanged, so there is no second containment guard to get wrong. The manifest is 8 MB raw and 312 KB gzipped, and 96% of those bytes are the per-file array. The entry type does not declare that key, so it is skipped: the zip carries its own hash and that covers every file inside it. What lands in the cache is this build's own projection rather than the served bytes, which is also what makes browsing work with no network -- a failed refresh ages the list instead of emptying it. Two things the import path never had. Staging files are swept at launch, because defer does not run when iOS kills a backgrounded app mid-download, which is the ordinary outcome and not an edge case. And cancelling removes the pack if the extract already began, which is not the same as stopping it; the comment says so rather than implying otherwise. The importer returns the name it installed instead of only publishing it. One property on a shared object is fine for one caller and wrong for a screen with 867 rows and no reason to install them one at a time: two installs overwrite each other's answer, which would write one entry's marker into the other's folder and make cancelling one delete the other. Three fixes in the code around it, from the same review. A loaded chain owned a render target and a pipeline per pass and nothing freed any of it when the player turned shaders off, because DestroyShaderChain had exactly two callers, a preset change and device teardown. The Metal frame path flushed on success and returned on failure, though a chain that failed partway has already encoded passes into the same command buffer and needs the submit for the same reason the success path does. And the pack extractor held every file's bytes resident to the 32 MB cap, because the autoreleased data was never drained inside the loop. The catalogue is not published yet. The base URL is one constant, and an INI key no UI writes can repoint it, accepting only https and file -- which is how a simulator reads a local emit, since ATS refuses plain HTTP and there is no reason to weaken it for a test. Eleven checks in the new fence, six mutations run against the real source and all six caught, each restored byte for byte. Still open and written down rather than left to be rediscovered: the extractor's per-entry decompressed cap is applied after the entry is fully inflated, so a crafted zip can spend up to that cap before the refusal. Bounding it earlier needs a streaming inflate. The extractor's own fence gains an ordering claim. It asserted that a canonical resolve appears somewhere in the method, which passes for a resolve whose answer is discarded; deleting the entire containment refusal left it green. It anchors on resolvedParent now, because the body carries several refusal sites naming the same constants and anything looser is satisfied by a neighbouring refusal that has nothing to do with containment -- which is the same trap the first attempt at this fix fell into. |
||
|
|
f23ddabf1e |
iOS: run RetroArch shader chains on the Metal renderer
librashader built for arm64 and pinned, wired into GSDeviceMTL, and a preset library behind it that can name a file the same way twice across a reinstall. The chain runs from DoApplyShaderChain, after ShadeBoost and before present, on the same ping-pong the FXAA path uses. Two things about it are load- bearing rather than incidental. EndRenderPass comes first, because the chain opens its own passes and Metal aborts if ours is still encoding. And FlushEncoders comes last, because librashader recycles its per-frame objects over a ring shallower than our deferred-submit window, so a chain frame has to end the batch. A failure latches on the preset that caused it, or a preset that will not compile recompiles every frame forever. Static archive rather than dylib, decided by building both against a working tracer and measuring, and the loser was deleted rather than left as an option. The library underneath is where the reinstall problem lives. Both preset roots sit under a container UUID that changes on every sideload, so a selection stored as an absolute path is stale within days. A preset is stored as a marker plus a root-relative path -- bundle: or data: -- and re- rooted at launch. The separator is a colon because Files refuses one in a name and it is not a path separator, so the relative half never needs escaping. Packs come in as a picked zip or folder through an extractor that keeps the directory tree, because a .slangp names its stages by relative path and the tree is part of the pack rather than an arrangement of it. That is the opposite of the skin extractor's flattening policy, so a test fences the two apart. Sixteen presets ship in the app, each cleared against its own header rather than a blanket grant. librashader's own cache goes to Library/Caches through XDG_CACHE_HOME, set before anything loads it. Latent today because the Metal runtime never reaches that cache, but a pin bump that adds caching would otherwise put a disposable file somewhere iOS can neither purge nor keep out of a backup. |
||
|
|
5b616729f1 |
EE rec: stop raising TLB misses on unknown MMIO too
The _ext_mem* fallbacks raise a TLB exception when a registered region gets an access its device has no case for. Under a recompiler that is the defect just removed from vtlb_Miss by another route: nothing diverts the block, so the raise only latches Status.EXL. Raise on the interpreter alone. Recompilers report instead, which is new - MEM_LOG is devbuild-only, so the raise was all a release build left. |
||
|
|
19cb54586f |
microVU: read the I immediate at run time under IbitHack
x86's doIbit picks between folding the immediate into the block and loading it from micro memory; arm64 only ever folded. Scarface (SLUS-21111), the game the gamefix is listed for, writes a per-object transform into VU1 micro memory as I immediates. Under the recompiler its trees and much of the geometry around them took whichever object's transform compiled first. The interpreter re-reads the word on every dispatch and was unaffected. |
||
|
|
c5ad9ddd4d |
EE rec: stop raising TLB misses, matching x86
vtlb_Miss raised the exception under the arm64 recompiler and returned, on the assumption that the rec would pick cpuRegs.pc up at the next dispatch. Nothing picks it up: the block runs on and its tail writes its own branch target over the vector PC, while cpuException has already latched Status.EXL. cpuException leaves EPC alone whenever EXL is already set, so from the first swallowed miss onward every exception keeps its predecessor's EPC, and the guest kernel's syscall epilogue erets to an address belonging to the fault. Report the miss and continue instead, as the x86 rec already does. The guest's handler still does not run, so a title that needs demand paging still cannot work under the rec; a stray miss now stays local to the instruction. ee_rec_tlb_divert_tests.cpp describes the divert and stays disabled. With nothing left to set s_recTlbMissOccurred, the poll after every interpreter call goes too. It could not have covered the raise anyway: only the interpreter-call sites had it, so a flag set at an inline access sat there until an unrelated later recCall consumed it and diverted on a pc belonging to neither. This gives up the one case the rec did handle, a miss in a branch delay slot reaching the vector through the cpuRegs.branch bracket epilogue. Its recompiler half moves to the disabled file; the interpreter half stays live, beside a new test pinning the rec's behaviour. The bracket itself stays: the tlb_fallback_* handlers raise from a delay slot on their own. |