11947 Commits
Author SHA1 Message Date
Warley Oliveira 3099e7f3ff Fix LSFG DLL import verification to handle empty streams and improve file validation 2026-08-24 17:10:16 -04:00
jpolo1224 d8350359bb SGSR: credit CamilleLaVey in the feature's C++ entry point
The shader carried the attribution; the C++ side someone actually lands on
when reading the feature did not.
2026-08-24 16:30:11 -04:00
jpolo1224 001ca40803 GS: add SGSR upscaling (Qualcomm Snapdragon Game Super Resolution 1)
A third display upscaler beside FSR1, and the first one written for the
hardware this app actually runs on: FSR1's two compute passes are the
expensive outlier on mobile, while SGSR is a single pass Qualcomm designed
for Adreno.

The filter is Qualcomm's, BSD-3-Clause, unchanged in substance. What differs
is the shape around it -- theirs is a fragment shader over a fullscreen
triangle, this is a compute pass, because that is what GSDevice already knows
how to schedule. So the interpolated texcoord becomes a UV computed from the
invocation id and the fragment output becomes an imageStore. The crop
handling and the widened 0..2 sharpness range come from the Eden Emulator
Project's port (GPL-3.0-or-later, compatible with PCSX2's GPL-3.0+); PCSX2
hands the pass a display rectangle inside a larger target, which is the same
problem FsrEasuConOffset solves for FSR1.

Deliberately a strict subset of what FSR1 already requires -- same descriptor
types, same rgba8 storage image, textureGather with a constant component and
no offset, which is core Vulkan 1.0 and needs no optional feature. So any
driver already running FSR1 can run this, Turnip included, and there is no
vendor gate on either. A driver that cannot compile the pipeline clears
Features().sgsr and the renderer falls back to plain bilinear with an OSD
notice, rather than failing.

The Android upscaler control becomes a picker rather than an on/off toggle:
three mutually exclusive upscalers expressed as two toggles that silently
switch each other off is a worse way to say it than one list. FSR1 and SGSR
share the existing sharpness slider -- the number means different things to
each, but it is the same intent, and a second slider would only invite the
two to disagree.

★ The Settings.kt clamp on the persisted enum was still bounded at
UPSCALER_FSR1, and would have silently rewritten any SGSR selection back to
Off. That clamp's own comment warns about exactly this failure; it still had
to be updated by hand. Worth remembering the next time the enum grows.

Suggested by CamilleLaVey, who authored the upstream changes (eden-emu #4293).
2026-08-24 15:44:20 -04:00
jpolo1224 d2a7613fb3 Second screen and OSD: the rest of the batch
Custom panel background. Theme / library / black covered three of the four
asks; "an own background" needed a picker. Takes the persistable read grant
like the library's own picker -- without it the URI works until the process
restarts and then resolves to nothing, which reads as the background
disappearing on its own. Darkened by the same scrim as the library backdrop,
because an arbitrary photo has no obligation to be dark and tile text still
has to be readable.

Clock and battery move into a status bar across the top instead of being two
grid cells. Same information, but it stops the clock competing for space with
the things you actually press, and the grid gets two cells back.

The rest of the in-game OSD's figures reach the panel: VPS, EE / GS / GPU
load and frame time. These were not missing by choice -- getFPS() was the
only figure with a way across the JNI boundary, so the panel could show
frames and a percentage of nominal and nothing else. PerformanceMetrics
already computed all of it for the overlay. Each getter returns 0 with no VM
rather than the last value, so an idle panel reads as idle instead of frozen
on whatever the last game was doing.

Tile height is now settable. Columns already decided width -- tiles split the
row equally, so choosing columns IS choosing width, and a second width
control would only be a way to disagree with it. Height had no control at
all, which is why a panel could only ever be as tall as its text.

A display can be told to stay out of it. "The second screen also still
appears on the external monitor when connected via usbc" is not a bug by the
display-picking rule -- a USB-C monitor is a perfectly good second display --
so this records a preference instead of guessing: the panel's own Not-this-
screen tile drops the display it is on, and settings can re-enable them.
Keyed by display NAME, since ids are reassigned across replugs. Guessing from
internal-vs-external would have been wrong anyway; Android has no stable
public display type before API 34.

Device temperatures on the performance overlay, which is where they were
asked for. The core cannot read a temperature -- there is no portable API,
and on Android the only route is a vendor-specific sysfs the app layer
already discovers for the panel -- so the app pushes the values in and the
overlay draws what it was given. Atomics because the writer is a UI-thread
poll and the reader is the GS thread. A sensor that could not be read is
omitted rather than drawn as a zero.
2026-08-24 10:53:45 -04:00
jpolo1224 9111cd617d GS: remove the texture-replacement diagnostics
All of it was instrumentation for the Persona 3 FES investigation, and that turned
out not to be a texture problem at all -- three named patch groups were failing to
enable, and the texture pack stopped matching because the mod they gate patches the
game's font data.

Removed: the TCPROBE cross-version probe in HashCacheKey::Create, the hit/miss
counters and their geometric reporter, the per-miss detail listing what the pack
holds for a TEX0, the pack filename samples at map load, the duplicate/shadowed
accounting, and the mtime sidecar the palette fallback needed.

Kept the pre-existing "N indexed for '<serial>' (scanned <dir>)" line, which
predates this and is genuinely useful: a zero there with a path that does not match
the user's pack folder is still the fastest read on a pack that does nothing.

The cache budget work is untouched.
2026-08-21 23:26:12 -04:00
jpolo1224 2bbe909210 Revert the palette fallback and the purge notice
Both existed to work around the wrong diagnosis. The Persona 3 FES mods were not
failing in the texture cache at all -- a PNACH mod was switched off by the
enable-list purge, and because PNACH mods patch game DATA the texture pack keyed
to the patched font stopped matching as a side effect.

The palette fallback substituted a different colour variant of a glyph when the
exact palette was missing. It raised the hit count but could not be right: the
palette IS the colour, so substituted art carries the wrong shade, and it never
addressed why the hashes moved. Gone; the replacement path behaves exactly as it
always did.

The one-time notice goes too, per jpolo1224: the purge no longer takes deliberate
lists, so nothing needs announcing going forward.

Kept: the diagnostics that actually found this -- files scanned versus indexed
versus shadowed, hit and miss counts, and the per-miss detail naming what the pack
holds for that texture. Those are what turned "mods do not apply" into a
measurement, and they cost nothing when nothing is wrong.
2026-08-21 21:24:35 -04:00
jpolo1224 4d97b6cdf5 GS: fall back to another palette variant when the exact one is absent
A paletted replacement is keyed on TEX0 hash AND palette hash, so a pack only
applies while the game asks for a palette its packer happened to dump. Persona 3
FES lands exactly there: the pack carries several palette variants of each glyph
and the game asks for one that is not among them, so every glyph misses while the
unpaletted art around it replaces fine -- 189 hits against 323 misses, which is
why the scene looked right and the text did not. With this, 441 against 71, and
none of the remainder are palette mismatches.

Lowest palette hash, deliberately. It is arbitrary but STABLE: the same glyph
resolves to the same file on every draw and every run, so text renders in one
consistent colour. Choosing by file mtime was tried and was worse in a way worth
recording -- different glyphs won different variants and the text came out
multicoloured.

The colour can still be wrong, since the replacement image has the packer's
palette baked in and there is no recolouring it. That is the trade, and it is the
right way round: a mod that applies in the wrong shade beats one that does not
apply at all.

This does NOT explain why the pack matched on 2.6.6. That is a separate finding
and still open: the same glyph at the same address with the same palette hashes
differently now (2.6.6 asks 85076d2a533c0128, current asks f0576dc2f0bb17d5),
with TBP0, TBW, PSM, TW/TH, region and lod all identical between them. The bytes
in GS local memory differ, which is upstream of texture replacement entirely.
2026-08-21 21:06:50 -04:00
jpolo1224 a4f6fb0bd9 GS/TC: split the base-level hash from the mip chain in the probe
Both builds report identical TBP0, TBW, PSM, TW/TH, region and lod for the same
font glyph, and identical leading bytes -- yet different TEX0 hashes. Since
HashTextureLevel reads straight out of GS local memory, the bytes at that address
differ; the shipped hash folds the base level and every mip into one value, so it
cannot say which of them moved.

Recompute the base level alone for the probed textures and log it beside the
combined hash, plus the actual mip range rather than the bool the first probe
recorded. Base matching with the combined differing means the mip chain moved;
base differing means the glyph data itself did. Those are different bugs.
2026-08-21 21:01:28 -04:00
jpolo1224 6375594405 GS/TC: cross-version probe for the texture-hash divergence (diagnostic)
The two builds ask for disjoint TEX0 hashes for the same on-screen font -- nine
values on 2.6.6, eight now, no overlap, same palette hash, same PSMT4. Every
function feeding the hash is byte-identical between them, so what differs is the
DATA, and HashCacheKey records only the region's width and height: never where it
starts, never which address it came from, never whether mips were folded in.

Print exactly what the key discards -- TBP0, TBW, TW/TH, the full region rect
including its origin, whether lod was present, and the first bytes actually
hashed -- so two logs can be diffed instead of theorised about.
2026-08-21 20:40:17 -04:00
jpolo1224 7573a3e41f GS: drop the palette substitution, keep what it taught us
A paletted glyph's palette IS its colour, so when the game asks for a palette the
pack does not carry there is no "close enough" file to stand in for it.
Substituting produced multicoloured text: the pack holds eight colour variants of
each glyph, all from one extraction seconds apart, and different glyphs won
different variants. Both selection rules were wrong for the same reason -- lowest
hash preferred the base pack, newest was noise, and neither can be right when the
thing being chosen IS the colour.

Keep the diagnostic half, which is what actually advanced this: on a paletted
miss, list every colour of that TEX0 the pack does hold. That distinguishes "the
pack lacks this texture" from "the pack lacks this COLOUR of it", and the second
is the real finding here -- the modder dumped eight colours and the game is asking
for a ninth.

Leaves the replacement path behaving exactly as it did before any of this, with
better logging. The open question is unchanged and now isolated to one value: why
the game asks for a palette that is not among the dumped eight.
2026-08-21 19:50:44 -04:00
jpolo1224 461bcdc52f GS: palette fallback picks the NEWEST variant, and names every candidate
Shadowing was not it: 8386 files, 8339 indexed, only 4 collisions -- and the
remaining misses are 640x384 PSMCT32 with no palette, i.e. FMV frames, which hash
uniquely per frame and can never match. So the layered mods index fine and the
game does ask for them.

What actually loses is the choice BETWEEN palette variants. A mod over a base
pack supplies the same glyph under a different palette, so the two never collide
during indexing -- and the first cut of this fallback took the lowest palette
hash, which is arbitrary, and silently preferred the base pack. The player ends
up seeing the pack they installed FIRST, which is the opposite of what layering a
mod means, and matches the report exactly: base HD text applied, slim font did
not.

Prefer the newest file instead. Modification time is the one signal that
separates "the pack added last" from "the pack it was layered over", and the
directory walk already reports it, so this costs no extra I/O. Ties break on the
lower hash purely to stay reproducible.

Also log every candidate -- palette hash, mtime, full path -- for the first few
fallbacks, so the choice is auditable rather than asserted. If the newest file is
still the wrong one, that log says so immediately instead of costing another
round.

Still not shippable: substituted art carries the packer's palette, so tints can
be wrong, and mtime is a heuristic rather than an expression of intent. A real
fix wants explicit pack precedence.
2026-08-21 19:11:18 -04:00
jpolo1224 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.
2026-08-21 18:53:47 -04:00
jpolo1224 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.
2026-08-21 18:37:38 -04:00
jpolo1224 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.
2026-08-21 18:25:28 -04:00
jpolo1224 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.
2026-08-21 17:58:03 -04:00
jpolo1224 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.
2026-08-21 17:41:13 -04:00
jpolo1224 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.
2026-08-21 15:14:13 -04:00
jpolo1224 ff2b0c2155 Merge branch 'pr-593' into jit-android-catchup-gv7 2026-08-21 15:08:40 -04:00
jpolo1224 2c36725e11 Merge branch 'pr-608' into jit-android-catchup-gv7 2026-08-21 15:03:02 -04:00
jpolo1224 9262ea0480 Merge remote-tracking branch 'origin/master' into jit-android-catchup-gv7 2026-08-21 14:57:26 -04:00
jpolo1224 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.
2026-08-21 13:43:40 -04:00
jpolo1224 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 2f3f28faae allows a group through only if it DECLARES
gsaspectratio or gsinterlacemode. That rule was picked to separate a Skip
Cutscenes cheat from a Widescreen patch living in the same pnach, and it does
separate those two. What it does not survive is the rest of the database.

Measured across the shipped 4437 files, the groups it blocked were overwhelmingly
not cheats:

    305  50 FPS
    177  60 FPS
    168  50/60 FPS
    102  Remove Blackbars      <- a widescreen patch
    100  NTSC Mode
     31  480p Mode

Almost every real presentation patch changes the picture by writing EE memory and
declares nothing, so the declaration test caught the tail and missed the bulk.
The original commit sampled Car select and Auto-activate analogs and concluded
the blocked set read like cheats; across the whole database it does not.

So the name is now consulted too, matched case-insensitively against the labels
the database actually uses. Matched on the EFFECT rather than the verb — Remove
Blur, Disable Blur and No Blur are the same patch, and listing verbs caught one
spelling while blocking the others.

After: 4673 groups stay by declaration, 1206 more stay by name, 495 remain
blocked. What still blocks reads the way it should — Adjusted triggers
sensitivity, Trigger control mappings, GT3 Chase Camera, and Skip Cutscenes
itself, which is the case the gate was written for.

An unrecognised name still fails CLOSED, which is the safe direction: a new cheat
is blocked by default and only an understood presentation class is let through.

★ Frame-rate patches are the judgement call here. They are in the allowlist
because they are what was reported broken and desktop PCSX2 permits them, but
they are the entry RetroAchievements is most likely to object to. If RA rules
them out, deleting three strings from ALLOWED is the whole change.
2026-08-21 12:16:27 -04:00
jpolo1224 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 9a6db38395), then
'mods do not apply at all' once failed uploads correctly fell back. Removing the
cap entirely just traded the crash back.

A FRACTION of RAM is the wrong shape, and that is the actual mistake. It scales
the RESERVE with total memory, when what has to be reserved is roughly constant:
the EE/GS allocations, the JIT, Android itself. On 8 GB, RAM/2 holds back 4 GB
to protect something needing about 1.5 GB.

A fixed 5 GB is wrong too, and specifically so: the pack measures 5.0 GB, so a
5 GB cap sits exactly on the boundary and evicts anyway.

So reserve a constant 2.5 GB and give the rest to textures:

   6 GB ->  3.5 GB   (was 3.0)   GoW1 HD 2.97 GB fits
   8 GB ->  5.5 GB   (was 4.0)   P3 FES HD 5.0 GB fits
  12 GB ->  9.5 GB   (was 6.0)

The 8 GB row is the reported case, and it now holds the pack whole with headroom
rather than missing by a gigabyte.

The budget is logged at startup with the physical RAM it came from, and the
over-budget warning now says the pack does not fit rather than only quoting the
budget — the actionable question is which is bigger, and the old wording did not
let anyone answer it.
2026-08-21 11:36:43 -04:00
jpolo1224 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 9a6db38395 a failed upload was injected anyway
with UNDEFINED contents, which is the green blocky corruption in the original
screenshots. After 9a6db38395 a failed upload correctly drops the texture, so
the same cause changed appearance to 'the mods do not apply at all'.

No budget satisfies both cases: 5 GB does not fit inside any fraction of 8 GB
that also leaves the emulator room. So this is a choice about which failure the
user gets, not a bug with a correct answer. An oversized pack now behaves as it
did before — it loads, and the device is allowed to run out of memory if it
genuinely cannot hold it. Half a pack applied with nothing on screen explaining
why is the worse outcome; it reads as broken rather than as too big.

The byte accounting is kept and now warns ONCE past physical RAM/2, naming the
footprint. It is a breadcrumb, not a limit: nothing is refused and nothing is
evicted, but if the process is killed the log says what was resident. The LRU
ordering is kept dormant for the same reason — if OOM reports return,
reinstating eviction is one line in GetReplacementCacheBudget.

The genuine fix is on the pack side: block-compressed (BC7/BC3) it would be
several times smaller and fit comfortably.
2026-08-21 11:26:36 -04:00
jpolo1224 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.
2026-08-21 10:30:15 -04:00