Commit Graph
100 Commits
Author SHA1 Message Date
jpolo1224 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.
2026-08-21 00:53:19 -04:00
jpolo1224 517fa69c4c OSD: stop any settings change from wiping the active OSD mode
Changing any setting at all — brightness, a speedhack, a controller binding —
made the on-screen display disappear.

The OSD has two independent controls that both write the same native flags. The
per-stat selection in settings, and the MODE picked from the in-game menu or the
hotkey (Full / Minimal / Custom / Off). Settings.applyTo() pushes the per-stat
osdShow* flags unconditionally, and applyTo runs on EVERY settings change, so it
was overwriting whatever mode was active with the Custom flag set. On most
setups the Custom set is mostly off, which is why the symptom reads as the OSD
vanishing rather than as it changing.

The mode STATE was never lost — InGameOverlay.osdMode still said Full, and the
in-game menu still showed Full. Only the native flags had been replaced, so the
UI and the screen disagreed and nothing looked wrong from the app's side.

Fixed at the applyTo choke point rather than at its five call sites: a re-assert
that reapplies the mode when it is anything other than Custom. Custom is left
alone deliberately — applyTo has just written exactly what Custom means, and
re-applying would be a redundant round trip through the CPU thread.

This is the same shape as the boot-time applyStoredOsdMode() and the
second-display reapplyOsdMode(), which already restore the mode after something
else has pushed flags underneath it. applyTo was the third place that needed it
and the only one that had no such guard.
2026-08-21 00:25:08 -04:00
jpolo1224 54d2850295 LSFG: keep it out of the Play build entirely, not just switched off
Play builds cannot carry LSFG at all, and they did. The gating was a
BuildConfig.LSFG check inside shared files, which is a weaker claim than it
reads as: the rows were never drawn, and all 22 frame-generation strings still
shipped in the Play dex in plain text — including "Lossless Scaling",
"Lossless.dll" and the requirements dialog naming the product, which is exactly
what a text search over the artifact finds. The native half was already
genuinely compiled out (-DARMSX2_ENABLE_LSFG=OFF); only the Kotlin half looked
like it was.

Moved to source sets, which is the arrangement that actually excludes:

  LsfgSection.kt      main -> github, with a no-op stub in play
  the 22 EN strings   -> I18nLsfg.kt, real in github and an EMPTY MAP in play
  the 5 search rows   -> SettingsSearchLsfg.kt, likewise
  LsfgEmulationCard   new, so the shared pause-menu file no longer even names
                      the section's string key (SectionCard became internal)

EN is now BASE_EN + LSFG_EN and the search index BASE + LSFG, so whichever
flavour is in scope supplies its half and no caller knows which build it is in.
Splitting the search rows is a behaviour fix as well: in the play build they were
indexed while the section they pointed at was compiled out, so searching would
offer a result that rendered its own key as its title and led nowhere.

The settings FIELDS stay shared on purpose — identifiers rather than product
names, and an identical config schema across flavours is what lets a config move
between builds without losing data.

Verified on compiled output rather than source: playDebug has zero class files
containing 'Lossless' and zero containing 'perf.lsfg'; githubDebug has 2 and 4.
I18nLsfgKt.class is 3633 bytes in github and 833 in play. build-play-aab.sh now
greps the AAB's dex for both strings and fails the build if either appears, so a
later edit to a shared file cannot quietly undo this.

★ That verification first came back clean for BOTH flavours, which was a false
negative: Xcode's strings(1) parses a .class as a Mach-O fat binary, errors, and
prints nothing — indistinguishable from a pass. LC_ALL=C grep -a is what the
check uses, and what the comment in the script warns about.
2026-08-21 00:22:31 -04:00
jpolo1224 2a98726692 Merge remote-tracking branch 'origin/master' into jit-android-catchup-gv7 2026-08-16 14:22:55 -04:00
jpolo1224 5b790427dd LSFG/FSR: remove the debug instrumentation
The per-second LSFG branch counters and the FSR gate line were added to find two
specific bugs and both did their job — the counters proved generated frames were
reaching the screen uncounted (VK_SUBOPTIMAL_KHR treated as failure), and the
gate proved all three FSR conditions passed while a misplaced log made the pass
look dead. Neither belongs in a release: one printed every second, the other on
every state change.

What stays is event-driven and diagnostic in the ordinary sense: LSFG's
initialise line, shader-cache hits and misses, load and ABI failures, and one
FSR line per output-size change.
2026-08-16 14:22:55 -04:00
jpolo1224 c246b9a03c LSFG: let the generated frame wait for a display slot; unhide FSR on renderer=auto
Two bugs of mine, both found on-device with everything else working.

★ The zero-timeout acquire disabled frame generation entirely.

Forcing FIFO fixed the MAILBOX discard, and the display rate still equalled the
real rate. Under FIFO the presentation engine returns an image at a vblank, so
at steady state nothing is ever free INSTANTLY — vkAcquireNextImageKHR with a
zero timeout returns VK_NOT_READY every frame, the loop breaks, and every
interpolated frame is dropped. Silently, because a dropped generated frame is a
legitimate outcome and nothing logs it.

The reasoning behind the zero was that an interpolated frame is a bonus not
worth stalling for. That is backwards: presenting two frames per rendered frame
MEANS waiting for the second display slot. Waiting is the mechanism, not the
cost. Now a 50ms bound — six vblanks at 120Hz, so it expires only when something
is genuinely wrong, while still keeping a lost surface from wedging the GS
thread the way an unbounded wait would.

★ The in-game FSR row was gated on renderer == "vulkan", and the default is
"auto".

"auto" resolves to Vulkan on Android, so the row was hidden from anyone who had
not explicitly pinned the renderer — which is nearly everyone, and was the
reporter. Gated on the two backends that genuinely cannot run it instead.
2026-08-16 13:08:05 -04:00
jpolo1224 2f1a74c88a LSFG: force FIFO presentation, and surface FSR in the in-game menu
★ Frame generation produced nothing on a MAILBOX swapchain, silently.

Reported on an Adreno 740: LSFG logged 'active: 1920x1080 x2 frames, 3.1p',
cached its 52 shaders, never logged a single failure — and both the FPS and the
LSFG display counters read 59. The interpolator was working perfectly and its
output was being thrown away.

MAILBOX keeps only the most recent image queued for a given refresh. Presenting
an interpolated frame and then the real frame immediately after replaces the
interpolated one, so it is generated, costs its full GPU time, and is never
displayed. IMMEDIATE discards the same way. Nothing errors anywhere along that
path, which is why the only symptom is a display rate identical to the real one.

The device landed on MAILBOX because vsync was off — 'Immediate not supported
for vsync-disabled, using mailbox'. SelectPresentMode now forces FIFO while
frame generation is enabled. Eden reached the same conclusion; their setting
text reads 'Forces FIFO presentation while enabled'.

Gated on the setting rather than GSLsfg::IsAvailable(), which cannot answer at
swapchain-creation time: the DLL path only reaches GSLsfg from EndPresent.

Also adds the FSR rows to the in-game GraphicsPane. In full settings FSR sits
under Display Effects beside CAS, which is the right shelf for a post-effect and
the wrong one for finding it — it is an upscaler, so in the quick menu it goes
with the internal-resolution controls, which is where you reach while watching
the framerate. Vulkan-only, so it is never a dead toggle on OpenGL.
2026-08-16 12:49:03 -04:00
jpolo1224 eeb3affb13 GS: FidelityFX Super Resolution 1 as an output-scaling mode
Adds FSR1 (EASU upscale + RCAS sharpen, two compute passes) to the Vulkan
backend, so a game rendered below display size can be upscaled properly instead
of bilinear-stretched at present. Slots in beside the existing MetalFX branch in
GSRenderer rather than introducing a parallel abstraction: GSUpscaler and the
non-pure DoXxx virtuals already occupy that design space, and OpenGL and Metal
inherit a false return and need no change.

FSR1 is MIT (AMD, 2021) and the tree already ships ffx_a.h and ffx_cas.h under
the identical grant, so the headers are vendored verbatim with their licence
blocks intact.

★ ffx_a.h is NOT replaced. FSR1 wants the 2021 header, ours is 2019, and the
2019 one has been locally patched for Metal Shading Language (A16, A_MSL,
A_MAYBE_UNUSED) with ffx_cas.h depending on those. Swapping it would break the
Metal backend. The 2021 copy ships alongside as ffx_a_fsr1.h, used only for
GPU-side string substitution. The CPU-side FsrEasuConOffset/FsrRcasCon compile
against the existing 2019 header — verified by compiling a probe, not by
grepping, because AU1_AF1 and AU1_AH2_AF2 are functions and a grep for a #define
reports a false negative.

Two shader modules, not two specializations. FSR_EASU_F and FSR_RCAS_F are
preprocessor gates deciding which function bodies ffx_fsr1.h emits at all, and
specialization constants resolve after preprocessing, so CAS's constant_id trick
would produce a shader calling undefined functions. Confirmed distinct:
disassembly shows EASU with three OpImageGather and RCAS with none.

Both passes push the full 80-byte constant block. With all five uvec4 declared
so one layout serves both, Sample decorates to byte offset 64 — pushing the 32
bytes RCAS nominally needs would leave it undefined, and Sample gates a
gamma-squaring branch, so garbage there squares the image.

Binding 0 is a combined image sampler, unlike CAS's plain sampled image, because
EASU uses textureGather.

The EASU intermediate stays in GENERAL with explicit compute-to-compute
barriers. Layout::ShaderReadOnly targets the FRAGMENT stage and
TransitionToLayout early-outs when the layout already matches, so neither of the
usual tools makes a compute write visible to a compute read. The barrier also
covers frame N+1's EASU write against frame N's RCAS read, since the image is
parked across frames.

FSR and CAS are alternatives, not a chain: RCAS is itself a sharpener. Selecting
FSR hides the CAS rows. Pipeline compilation failure is non-fatal and leaves
Features().fsr1 false, matching the CAS path that exists because of an Adreno
650 crash.

GSUpscaler::FSR1 is appended, not inserted, since the enum is persisted as an
integer. Android clamps to the enum's own maximum rather than the count of
options its picker shows — clamping to the picker would have rewritten FSR1 back
to Off on every save, because MetalFX occupies value 1 and is never displayed.

Verified: build clean, no C++ or Kotlin errors; all three resource files
packaged into the APK; FSR code present in the core. NOT verified: anything on a
GPU. No visual check, no perf numbers, and in particular no confirmation that
textureGather in a compute shader works on the Adreno drivers this targets.
2026-08-16 12:11:33 -04:00
jpolo1224 dfd92a4f31 LSFG: persist settings and shaders, report status, add flow scale and 3.1p
Five changes, one of which is a plain bug in what shipped.

★ LSFG settings were never persisted. lsfgEnabled, lsfgMultiplier and
lsfgDllPath were absent from toJson/fromJson, and that pair IS the persistence
format — ConfigStore stores toJson().toString(). So every choice, including the
Lossless.dll the user went and found, was discarded on restart. Added to the
round-trip, the per-game override diff/merge, and gsDiffersFrom.

Translated SPIR-V is now cached to disk. Extraction used to keep raw DXBC and
translate inside the shader callback, so all 26 translations re-ran on the GS
thread inside EndPresent after every enable, resize or multiplier change. Now
ExtractShaders translates eagerly, drops the DXBC, and writes
<cache>/lsfg_shaders.bin. The DLL's size and mtime go in the header and a
mismatch re-extracts — ARMSX3's equivalent has no invalidation at all.

Frame generation can no longer fail invisibly. GetStatusText() feeds one line
to the performance overlay, empty ONLY when the user has not enabled it:
unavailable / failed / no shaders / starting / a display rate. That rate counts
frames actually PRESENTED, real plus generated, because the acquire loop can
break early and assuming the multiplier would overstate it. FPS alone cannot
show this — frame generation deliberately does not change the emulator's frame
rate, so without a separate line 'working', 'broken' and 'unsupported' are all
the same absent line.

Flow scale and the 3.1p pipeline are exposed. flowScale is a DIVISOR — framegen
computes flowExtent = inputExtent / flowScale — so the UI percentage is passed
as clamp(100/percent, 1, 4). ARMSX3 passes percent/100, where only the default
is right because 1.0 is its own reciprocal and every lower position makes it
slower; that inversion is not copied here. 3.1p is a separate shader family with
separate device state, so the shim fixes the choice at initialise and dispatches
every entry point on it, and the name table gains the p_* resource IDs.

Frames the game did not draw are no longer interpolated. PresentWithGeneration
captured unconditionally, so pause menus and boot screens got interpolated at
full GPU cost. It now takes frame_has_new_content, sourced from the condition
GSRenderer already computes (current && !blank_frame) rather than a new
heuristic, and consumed with std::exchange because RenderBlankFrame presents
without going through BeginPresent. A false also resets the frame history, so
the pair either side of a gap is never stitched into one bogus in-between frame.

Verified in the built APK: four status states and lsfg_shaders.bin in the core,
26 p_* names in its table, 3.1p linked into the shim. Build clean, no C++ or
Kotlin errors. NOT verified: any of it at runtime — no Adreno 7xx here, so the
flow-scale direction, the cache round-trip and the capture gate are reasoned,
not observed.
2026-08-16 11:13:54 -04:00
jpolo1224 7fde8ce980 Merge remote-tracking branch 'origin/master' into jit-android-catchup-gv7 2026-08-16 10:52:15 -04:00
jpolo1224 c66f28a395 LSFG: fix binary semaphore misuse and the unbounded acquire
Three defects in the frame-generation present path, found comparing it against
ARMSX3's.

1. Binary semaphores signalled without being waited, and waited twice.

   The loop reassigned the real present's wait to the last post-copy semaphore.
   That left s_pre_copy_sem signalled and never waited, so the next frame
   signalled an already-signalled binary semaphore; and it made the last
   post-copy semaphore the target of two waits, its own present and the real
   one, when a binary semaphore's signal can be consumed exactly once. Both are
   spec violations, and the kind that work until a driver decides otherwise.

   The fix is to delete the reassignment, not to add semaphores. The real
   present must wait on s_pre_copy_sem specifically, because the pre-copy reads
   the real image as TRANSFER_SRC and returns it to PRESENT_SRC — presenting
   before that lands would present an image still being read. The generated
   presents are independent: different swapchain images, each gated by its own
   post-copy. Presents issued on one queue are processed in call order, which is
   what keeps them on screen ahead of the real frame. Every semaphore is now
   signalled once and waited once.

2. vkAcquireNextImageKHR with UINT64_MAX, on the present path.

   Two problems at once. It bypassed the bounded ACQUIRE_TIMEOUT_NS that
   VKSwapChain::AcquireNextImage deliberately adopted so a surface destroyed
   under the GS thread — background, rotate, fold — does not leave every thread
   asleep at 0% CPU with nothing in any log. And the swapchain is 2 or 3 images
   while this loop holds the real one and asks for multiplier-1 more, so at
   x3/x4 it serialised on a vblank per generated frame, spending exactly the
   time the feature exists to save.

   Now a zero timeout: an interpolated frame is a bonus, so if nothing is free
   the right move is to drop it and get the real frame out. VK_NOT_READY leaves
   the semaphore unsignalled, so the slot stays clean for the next frame.

3. The fetch was not actually pinned.

   LSFG_PIN was "release" — a branch — under a comment explaining that an
   unpinned fetch would let a remote push change what our core links. Now the
   commit we have actually built and verified against, with GIT_SHALLOW off
   because a shallow clone carries only branch tips and cannot resolve a SHA.

Also caches the structural PE check. GetUnavailableReason() runs once per frame
from EndPresent while the feature is on, and it was doing a full
fopen/fread/fseek/fread/fclose on the GS thread every frame; the verdict can
only change when the path does, which is where it is now invalidated.
2026-08-16 10:40:50 -04:00
jpolo1224 cafb5e758b Android: make the legacy tier actually run on ARMv8.0 cores
The legacy APK claimed Android 8 (minSdk 26) while being compiled
-march=armv8.1-a, which lets clang emit LSE atomics inline. Android 8 means
Cortex-A53/A72/A73 — ARMv8.0, no LSE — so the one tier whose entire purpose is
reach did not reach them.

This is not a theoretical concern. BuildParameters.cmake:145 already records it:
'proven by a casal SIGILL on a real A53 device'. The guard written in response
only applies the safe default when nobody passes an -march, and this script
always passes one, so the tier defeated the protection added for it.

Legacy now builds -march=armv8-a -moutline-atomics, which is exactly what that
comment prescribes. Outline atomics keep LSE on cores that have it via a
runtime HWCAP dispatch, so a modern phone loses nothing.

Verified on the built core rather than assumed. The flags reach 3990 and 2036
compile lines respectively, and disassembling an LSE site shows the dispatch:

    bti  c
    adrp x16, ...                 ; __aarch64_have_lse_atomics
    ldrb w16, [x16, #0xc10]
    cbz  w16, 0x10b7128           ; no LSE -> fall through to LL/SC
    cas  w0, w1, [x2]
  0x10b7128:
    ldxr w0, [x2] / cmp / stxr / cbnz

An A53 takes the branch and never reaches the cas. APK minSdk confirmed 26.

Also moves a11/a13/a15 onto ARMSX3's SDK/NDK pairs and pins all four to NDK 29.
The NDK is not a device-compatibility knob — API level and -march gate devices,
and nothing on the device can tell which toolchain built the binary — so one
toolchain across the matrix is what makes a cross-tier comparison mean anything,
and there is no reason to withhold the measured gain from the weakest tier.

Needs a new armsx2.marchExtra gradle property: -moutline-atomics has to be its
own token, and BuildParameters.cmake's escape hatch keys on CMAKE_CXX_FLAGS
matching '-march='.

Artifact renamed to ARMSX2-<VN>-legacy-armv8.0-sdk26.apk. The updater keys on
the -sdkNN suffix, which is unchanged, so no updater change is needed. The Play
AAB is untouched: build.gradle.kts defaults still say minSdk 26 / NDK 28, and
only the APK script ever passes the tier properties.
2026-08-16 10:38:25 -04:00
jpolo1224 318588f7a6 Android: LSFG frame generation (github flavour only)
Drives Lossless Scaling's interpolation from our own Vulkan present path.
Upstream's consumer app captures the screen with MediaProjection and
composites over the target process, because Android 12+ forbids injecting
code into a non-debuggable app. That constraint is not ours: ARMSX2 owns its
swapchain, so it hands the library its own images through the AHardwareBuffer
entry points. No screen capture, no overlay, no accessibility service.

NOTHING PROPRIETARY SHIPS. The interpolation shaders are read at runtime out
of the user's own Lossless.dll, supplied through SAF exactly as a PS2 BIOS is.
The requirements dialog says so before the toggle commits, not after it
silently fails. Only the MIT-licensed lsfg-vk-android framegen library is
fetched; its sibling app carries a no-commercial-use licence and is not.

framegen is ISOLATED IN ITS OWN .so BEHIND A C ABI. It links volk, which
defines 759 globals named vkCreateImage, vkQueueSubmit and so on -- precisely
the names VKLoader.cpp defines. In one library that is a duplicate-symbol
error at best; at worst the linker merges them and framegen's volkLoadDevice()
call, made against its OWN VkDevice, silently repoints every entry point the
GS renderer uses, which would present as a driver crash with nothing pointing
back at frame generation. libarmsx2_lsfg.so gives volk its own copies, and
nm confirms only the eight armsx2_lsfg_* entry points are exported. The
interface is C because the CMake project builds ANDROID_STL=c++_static, so an
std::vector crossing that boundary would be two unrelated types sharing a
name; errors come back as codes, never exceptions.

The shader chain (pe-parse over the PE resources, then upstream's DXBC to
SPIR-V translator) stays in the core -- neither half touches Vulkan symbols.
GSLsfg.cpp is the one PCSX2 translation unit built with exceptions, because
that translator throws and the alternative is std::terminate on exactly the
paths a wrong DLL takes.

Present path mirrors upstream's Android sequence: copy the rendered frame
into shared storage, idle, interpolate, idle, present each generated frame,
then the real one. The idles are not laziness -- Turnip rejects OPAQUE_FD on
AHB-imported memory, so there is no cross-device semaphore and a device idle
is the only barrier that exists. Every failure degrades to an ordinary
present rather than taking the GS thread down.

Gated on Vulkan + Adreno 7xx and newer, asked of the resolved driver profile
rather than a GL_RENDERER substring. The UI reports WHY it is unavailable,
since 'needs an Adreno 7xx' and 'you have not picked a DLL yet' are the same
greyed row otherwise and only one is actionable.

Rows live in All Settings > Performance and the in-game performance tab, from
one shared section, wired to each host's own settings tier the same way
ShaderChainSection is. Play builds compile the whole thing out -- gradle sets
ARMSX2_ENABLE_LSFG=OFF, BuildConfig.LSFG is false, and build-play-aab.sh now
fails closed if libarmsx2_lsfg.so ever appears in a bundle.

Verified: github APK carries libarmsx2_lsfg.so and 13 live @@ANDROID_LSFG@@
strings in the core; the play variant configures with zero references to
either. NOT verified: the present path itself, which needs an Adreno 7xx
device, a real Lossless.dll and a running game.
2026-08-14 12:52:39 -04:00
jpolo1224 146bce27de Android: four release targets, keyed by minSdk suffix
Splits the ARMv8.2 build into three platform tiers instead of two, so the
Android 11 floor gets FP16 + DotProd as well:

  legacy  minSdk 26  NDK 28  armv8.1-a
  a11     minSdk 30  NDK 28  armv8.2-a+fp16+dotprod
  a13     minSdk 33  NDK 28  armv8.2-a+fp16+dotprod
  a15     minSdk 35  NDK 29  armv8.2-a+fp16+dotprod

Artifacts are now ARMSX2-<VN>-{legacy-armv8.1-sdk26,a11-armv8.2-sdk30,
a13-armv8.2-sdk33,a15-armv8.2-sdk35}.apk, and the updater classifies on the
-sdkNN suffix alone. The old markers were -v82 and -v82-sdk35, where one was a
substring of the other and only a carefully ordered when-branch kept Android 15
devices off the standard build; that hazard grows with every tier. The four sdk
suffixes cannot overlap.

An asset with no recognised marker still counts as legacy, so releases published
before tiering keep resolving.

The release-shape check now requires all four and verifies each name carries
exactly one, distinct sdk marker, and prints the upload-order warning: every
updater up to 2.6.6.6 takes the first .apk asset in a release regardless of
name, so the legacy build has to go up first or those installs are handed an
APK that SIGILLs on its first hot path.
2026-08-14 12:21:01 -04:00
jpolo1224 cdb7310a74 Merge branch 'pr539' into jit-android-catchup-gv7 2026-08-04 21:35:10 -04:00
jpolo1224 7eb414260a Android: community batch — stick sprint button, overlays, second screen, save-state delete
Pressure modifier now applies to buttons that are ALREADY held: the range was only
read when a press was emitted, so the gesture these games actually use — hold the
button, then ease off — did nothing (MGS2 cancels a shot on a half-pressed Square).

Macro turbo holds each state for at least 24ms. The pad is sampled on the VM's own
schedule, so the fastest frequencies were emitting presses that fell between two
samples and never registered, which read as the turbo being dead.

Extra button on the on-screen left stick, for sprint/jump. The stick locks the
gesture onto the pointer that started on it, so a separate widget could never be
reached by a finger gliding up off the stick; the stick hit-tests the zone itself
and keeps emitting deflection, making run-and-sprint one thumb motion.

Landscape render position (Center/Top), for foldables and clamshell controllers
whose screens open downward. Reuses the vertical-align switch that was gated to
portrait.

Custom internal resolution as a percentage of native, for steps the presets miss.
A value matching no preset also stops displaying as "0.25x" while the GS runs
something else.

RetroArch overlay artwork: import a pack and draw it between the game frame and
the touch controls, so it layers with a shader preset and never covers a button.

Second-display panel (Ayn Thor, Retroid dual screen): FPS, battery, clock and
buttons for save/load state, fast-forward, pause and screenshot.

Battery low and temperature warnings, off the sticky battery broadcast.

Delete a save state from the in-game picker by long-pressing its slot.

Point the PCSX2 CheatDB source at its current home; the old address is gone.
2026-08-04 21:34:01 -04:00
jpolo1224 156a778cf9 Android: import external save-state files into a slot 2026-08-03 09:45:34 -04:00
jpolo1224 12a7e37682 Android: 2D fallback background, portrait status layout, Clear Shader Cache placement, memcard delete wording 2026-07-30 18:20:12 -04:00
jpolo1224 1a45319a6a Android: fix Auto Progressive Scan hold, and correct Samsung QHD touch offset 2026-07-30 18:20:12 -04:00
jpolo1224 9d735f9ccb GameDB: no readbacks for Need for Speed Underground 1 & 2 2026-07-30 18:20:12 -04:00
jpolo1224 036eeea43e Patch: apply cheats filed under the generic all-CRC name 2026-07-30 18:20:12 -04:00
jpolo1224 e0f39849ce GS: keep Adreno framebuffer-fetch on by default, gate Snapdragon 8 Elite off 2026-07-30 18:20:12 -04:00
jpolo1224 9762b69bc7 Pause music: don't play behind a backgrounded app
Swiping out with the pause menu up left the track playing on the OS home
screen. Two paths caused it: backgrounding a running game calls
InGameOverlay.open() from onPause, which sets overlayVisible = true and
re-fires the pause-music LaunchedEffect — that effect then ran start()
after onPause had returned, beginning playback while backgrounded — and
more generally nothing stopped a late start() from a background thread (a
MediaPlayer with USAGE_MEDIA is not auto-paused by the system).

Add a foreground guard: onResume sets it true, onPause sets it false
(early, before open() flips the overlay state) and pauses any current
playback. start() no-ops whenever it is false, so no effect, resume, or
toggle path can begin playback behind a backgrounded app. Coming back,
onResume clears the guard and restarts the track if a menu is still up.
2026-07-30 02:43:59 -04:00
jpolo1224 110308e92a Pause music: slower ambient track, and fade it in
Swap the bundled pause-menu track for the slower, more ambient edit
(res/raw/pause_music.ogg -> .mp3; R.raw.pause_music is unchanged), and
ease it in instead of starting at full volume — the abrupt onset was the
complaint.

start() now begins the player silent and fadeIn() ramps to the set volume
over ~1.6s, reading the volume each step so a live slider change tracks.
stop() mirrors it with a ~0.5s fade-out then release, so resuming the game
isn't a hard cut; ownership of the player is handed to the fade coroutine
(player = null) up front and released in a finally, so a reopen mid-fade
builds a fresh player, the two cross-fade, and nothing leaks. A manual
volume change cancels an in-progress fade so the slider never fights it.
2026-07-30 02:31:55 -04:00
jpolo1224 caa1a808ab Pause music: play over the silent game stream, default on
The pause-menu track never played. It deferred to active audio the way
LibraryMusic does (to stay out of Spotify's way), but that check is wrong
here: on an overlay pause the game keeps its audio DEVICE open and just
underruns to silence — pauseForOverlay calls setOutputPauseSuppressed(true)
so Android does not reclaim the idle stream and stall the resume (#333).
So AudioManager reports the game's own stream as active the whole time the
menu is up, even though nothing is audible, and start() deferred forever.

Drop the isMusicActive() guard: play a second stream over the silent game
one. No audio focus is requested either, so the game's stream and its
resume are left untouched. Also default the toggle on — the menu was
silent and this fills it; the switch turns it off. The effect's retry loop
shrinks accordingly (start now plays immediately; a couple of light
retries only cover a transient MediaPlayer prepare hiccup).
2026-07-30 02:19:56 -04:00
jpolo1224 79b17a655d Present startup blank frames that carry an OSD message or toast
RetroAchievements toasts (and other OSD) were invisible on first boot
with Skip BIOS on, until you opened the pause menu — at which point the
toast appeared, and vanished again when you backed out.

Cause is the Android startup blank-frame suppression (GSPresentationPolicy).
With Skip BIOS on there is no boot animation, so the game shows a black
screen with no GS output for a while, and RA posts its "achievements
loaded" summary toast into exactly that window. ShouldSkipAndroidBlankFrame
returns true for every one of those frames (Vulkan + blank + no current
output), so the present is skipped — BeginPresentFrame(true) reports
FrameSkipped and EndPresentFrame() never runs, which means neither
RenderOSD() nor FullscreenUI::Render() (where notifications are drawn)
executes. The toast just sits queued in s_notifications. Opening the pause
menu forces real presents so it finally draws; closing it resumes the
skip. With BIOS on, the boot animation produces GS output before RA
posts, so has_current_output is already true and the toast presents
normally — which is why the bug only showed with Skip BIOS.

Gate the skip on new ImGuiManager::HasPresentableOverlayContent(): an OSD
message (pending or active) or an open FullscreenUI window or a queued
toast now forces the blank frame down the normal present path, where
EndPresentFrame() draws the overlay over black and presents it — exactly
what the pause menu already does during boot. Once the game produces its
first frame the suppression is moot anyway (has_current_output true), so
this only ever presents the few black boot frames that actually have
something to show, and is a no-op when there is nothing queued.
2026-07-30 02:04:58 -04:00
jpolo1224 d2608346c9 Add in-game pause menu music
The pause menu was silent, and people sit in it for minutes browsing
settings, achievements or memory cards mid-game.

New PauseMusic object, deliberately separate from LibraryMusic rather
than a second gate on it: LibraryMusic refuses to play unless
eState == STOPPED, which is the exact opposite condition, and the two
have opposite lifetimes. One player serving both would spend its life
fighting the other's start conditions.

Driven off WindowImpl.overlayVisible || inGameScreen != null rather than
from InGameOverlay.open()/close(), for the same reason as the effects
around it: many paths reach each state (back, menu button, hotkey,
dismissInGameScreen, a boot that force-closes the overlay) and hooking
them one by one always misses one. Including inGameScreen matters
because openInGameScreen() closes the menu as it opens Settings, and
sitting in Settings is the long silence this exists to fill.

Start retries for ~3s like the library track: pausing suspends SPU2 but
Oboe takes a moment to actually go idle, and start() politely defers
while AudioManager still reports audio active, so a single attempt would
lose that race every time. Audio focus is deliberately NOT requested —
the game's own audio is already suspended, so there is nothing to duck,
and grabbing focus for a menu track would stop whatever the player has
going in another app.

Toggleable and volume-adjustable in App settings like the library music,
off by default (audio starting when you open a menu is startling if you
didn't ask for it), and a custom track can be imported the same way.
onPause/onResume mirror the library track's handling so it never plays
behind a backgrounded app; onResume restarts explicitly because the
overlay states don't change while backgrounded, so the effect won't
re-fire on its own.
2026-07-30 01:45:56 -04:00
jpolo1224 3c6bb8b26f Android: expose every emulated USB device, driven by the existing pad
GunCon 2 alone was half the answer. The core registers eighteen USB devices — Buzz
buzzers, a Rock Band drum kit, Keyboardmania, BeatMania, a DJ turntable, the Printer,
EyeToy, Gametrak, RealPlay, Train controller, mic, headset, HID keyboard/mouse — and
none of them were reachable on Android.

The list is enumerated FROM RegisterDevice rather than hardcoded, so it cannot drift
from what a given build actually supports, and subtypes (different wheels, different
turntables) come along with it.

Making them USABLE is the real work, and it did not need a second binding editor.
Every InputBindingInfo already declares a generic_mapping (Cross, DPadUp, L1, ...), so
on attach native builds GenericInputBinding -> bind_index for the device and
applyPadButton forwards each press to the matching bind. The player's existing controls
— physical pad, on-screen buttons, macros, anything that funnels through that one
chokepoint — drive the device with nothing extra to configure. Bindings with no generic
equivalent (Gametrak's axes, the printer) get nothing, which is correct: there is no
sensible pad button for them.

Aiming stays special-cased, because a pointer is not a button: Lightgun owns it, and
UsbDevices.setType now tells it when a port changes so the aim layer cannot stay live
over a port that has become a drum kit — otherwise every touch would be swallowed as a
shot at a device that is no longer attached.
2026-07-30 01:31:39 -04:00
jpolo1224 c7ef124237 Android: GunCon 2 lightgun, and gesture controls in the in-game menu
Lightgun: the core already emulates the device (usb_lightgun::GunCon2Device,
DEVTYPE_GUNCON2). What Android lacked were the three things that feed it, because our
input path is bespoke rather than InputManager/SDL:

  * device selection -> USB{n}/Type via USB::SetConfigDevice
  * aiming           -> InputManager::UpdatePointerAbsolutePosition, which is what
                        GunCon2State reads through GetPointerAbsolutePosition(0) when
                        it has no relative binds. Window pixels, and our SurfaceView
                        spans the window, so touch coordinates pass straight through.
  * buttons          -> USB::SetDeviceBindValue(port, BID_*, 0/1)

Touch aims continuously rather than only on tap, so you can lead a target before
firing, and the aim is pushed BEFORE the trigger because the core samples the pointer
when the trigger goes down. A touch within 6% of a screen edge fires OFF-SCREEN
instead: that is how these games reload, and without it they are unplayable past the
first magazine. A/B/C, Start, Select and Cal (recalibrate) sit down the right edge,
composed above the aim layer so pressing one is a button press and not a shot;
recalibrate matters because several of these games open with a calibration step.

The aim layer consumes its pointers, unlike the gesture layer — with a gun attached a
touch on empty screen IS the shot, so there is nothing else it could belong to. It
still ignores a DOWN a widget already claimed, so the gun buttons and pause still work.

Device type is restart-required and says so: swapping a USB device on a live VM is the
emulated equivalent of yanking the plug from a port the game has already probed.

Gestures also reachable in-game: swipe distance and the Tap/Hold mode are values you
only find by playing, and walking out to the settings tree to nudge them loses the
moment. The six button assignments stay in All Settings — they are set once, and six
pickers would swamp that pane.
2026-07-30 01:18:56 -04:00
jpolo1224 3cb1e88029 Merge remote-tracking branch 'origin/master' into jit-android-catchup-gv7
# Conflicts:
#	tests/ctest/core/gs/CMakeLists.txt
2026-07-30 01:10:19 -04:00
jpolo1224 3768d442c7 Android: gestures, status cluster, texture-pack fixes, working Controls reset
Gesture control (PPSSPP-style): swipes and a double-tap on empty screen area fire a
PS2 button. The double-tap takes a Tap/Hold mode — Tap pulses (NFS nitro), Hold
latches until you double-tap again (ARPG camera lock). The layer composes below every
widget, rejects a DOWN a control already consumed, and never consumes anything itself,
so it cannot swallow a press. Pulses hold 40ms because the emulated pad drops an
instant down+up.

Clock + battery readout in the library toolbar and the in-game menu header. The glyph
drains with charge (green/amber/red) and shows a bolt while charging. Time-remaining
appears only while charging: Android has computeChargeTimeRemaining() but no public
discharge-time API, and inventing an estimate would be worse than omitting it.

Texture packs:
- write() did delete()+renameTo(); if the rename failed the whole install record was
  gone, and runCatching swallowed it while still bumping revision. Now an atomic
  Files.move with a .bak fallback. This is "packs forget being installed after ~57
  downloads", and very likely also "delete does nothing" / "still says Installed".
- reconcile() refuses to wipe records when a scan reports zero packs but records
  exist — that is a failed listFiles(), not sixty simultaneous deletions.
- refresh() walked every file of every pack on every open (~250k stats at 60 packs).
  Sizes now come from an mtime-keyed cache.
- The catalogue paged 20 rows at a time with a "Show N more" that names the
  remainder; it cannot be a LazyColumn inside the existing verticalScroll.
- Installed packs show game names, and the catalogue sorts by game or serial.

Controls reset now exists: resetTunables() hand-listed keys, drifted behind every
setting added after it, and had ZERO call sites. Replaced with resetAllControls(),
which sweeps by key prefix so it cannot rot, scoped global or per-game.

Confirmations are inline overlays claiming an exclusive nav layer, not Compose
dialogs — a dialog is its own window and swallows controller keys, so those prompts
were touch-only. Adds Clear All for Recently Played and a full app reset; the reset
also purges the in-folder settings mirror and gamesettings INIs, without which the
next launch would silently restore everything it just wiped.

Motion control falls back to the accelerometer where there is no gyroscope, with the
tilt limitation stated in the UI (gravity cannot observe yaw). Adds a Motion Recenter
hotkey — recenter() previously had no call site at all.
2026-07-30 01:09:39 -04:00
jpolo1224 de7ec8509c GS: 20:9/19.5:9/custom aspect, interlace+presentation policies, VK feedback flags
Aspect ratios: added 20:9, 19.5:9 and a user-entered Custom ratio
(GSOptions::CustomAspectRatio, clamped 0.5..5.0). All APPENDED, never inserted —
these values are persisted as raw ints in the ini and in the Android prefs, so
slotting one in mid-enum would silently repoint every saved config at a different
ratio. Also filled in the two ultrawide cases RequestDisplaySize was missing.

Interlace/presentation: ported sashkinbro's EmuCoreX 30799e4. SelectGSInterlaceMode
centralises the mode choice and keeps shader_mode -1 for automatic full-frame output
(a deinterlace pass must not run over progressive output during a video-mode
transition); our formula already agreed, so this is centralisation plus
static_asserts rather than a behaviour change. ShouldSkipAndroidBlankFrame is new
behaviour: Vulkan now suppresses only the startup blank, so a mid-game fade reaches
the normal present path and its recorded command buffer is submitted.

Vulkan: declare the attachment feedback loops on the PIPELINE, not just on the image
layout and render pass. We put attachments into FEEDBACK_LOOP_OPTIMAL without ever
setting VK_PIPELINE_CREATE_{COLOR,DEPTH_STENCIL}_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT,
which the spec requires — undefined behaviour rather than a missed optimisation, and
strict mobile drivers are where undefined shows up as stale attachment reads.
2026-07-30 01:09:12 -04:00
jpolo1224 10f4f73fe8 Patch: stop patches arming themselves, and make disabling one stick
Reported as "patches apply with every patch setting off, and won't turn off" —
SOTC/KH2/GOW2. One chain of defects, verified on device:

- PatchManagerViewModel.refresh() called syncAllEnableLists() unconditionally, so
  merely OPENING the Patch Manager persisted every uncommented group of every
  on-disk .pnach as enabled. Community pnach files ship uncommented, and patches
  are matched by NAME, so a name like "60 FPS" then armed the same-named group in
  any of the ~4000 bundled files, for games never opened. Removed; import still
  registers its own file, which was the only legitimate use.
- EnumeratePnachFiles fell back to the bundled zip even when disk files existed,
  contradicting its own "prefer files on disk" comment. Deleting a pnach silently
  promoted the identically-named bundled group in its place.
- delete() removed the file but never dropped its names from the enable list, so
  they stayed armed forever.
- ReloadPatchAffectingOptions never reset CurrentCustomAspectRatio, which only
  ever gets set, so 16:9 survived disabling widescreen.
- LocalCheatRow and OnlineEntryRow armed the row under the cursor on D-pad Right,
  so scrolling a cheat list enabled everything you passed. Confirm only now.

Patches cannot be un-applied without a reboot: PatchCommand has no original-value
field and UnloadPatches never touches guest RAM, so disabling one mid-session only
stops it being re-written.
2026-07-30 01:08:54 -04:00
jpolo1224 15538d066d GameDB: no readbacks for Guitar Hero II and III
The note-highway render target is never sampled back, so the GPU->CPU download is
pure cost on a tiler. Covers GH2 (SLES-54442, SLUS-21447 — the latter is also the
serial GH2 Deluxe ships under) and GH3 (SLES-54962, SLES-54974, SLKA-25363,
SLKA-25414, SLUS-21672).

gsHWFixes is a clear-then-replace map in this overlay, so each GH3 entry restates
every upstream key. Dropping one would have silently undone the crowd-texture,
bloom and post-processing fixes those entries already carry.
2026-07-30 01:08:39 -04:00
jpolo1224 d4caacf512 Android: Exit back in the library menu, Skins next to the control tabs
Exit returns to the library's overflow menu. It had moved to the navigation
drawer, which put it below every other destination -- so quitting, one of the
most frequent things anyone does from that screen, meant opening the drawer and
scrolling to the bottom every time. Reported as issue #460 by shinobumaehara,
whose point is simply that frequency should decide placement. It stays in the
drawer as well; this is the short path, not a replacement.

Smaller than it looked: onExitApp was still a parameter and its confirmation
dialog was still wired up. Only the row that reached them had been deleted.

LibraryOverflowItem gained optional iconRes/iconTint for it, because the power
symbol (U+23FB) is not in the bundled font and rendered as a tofu box -- it now
uses the same ic_power drawable and red as the drawer's row, so the two entries
match. Every other row keeps the text-glyph path untouched.

Skins moves to sit after Shortcuts and before Network. It is controller artwork,
so people look for it beside Controls and Shortcuts rather than past On-Screen.
Suggested by Isshin.
2026-07-28 12:50:06 -04:00
jpolo1224 363009987a ImGui: queue notifications instead of stacking them
Finishing a game submits every leaderboard in the same frame. AddNotification
gave each one start_time = current_time, so they all began at once -- Final
Fantasy XII posted six, which covered the screen and pushed the mastery unlock
out of sight before it could be read. Reported with a screenshot showing exactly
that.

Three on screen at most now, at least 0.4s apart, and anything past the limit
waits its turn. A QUEUED notification does not age while it waits: its duration
starts when it actually appears, so nothing expires unseen in the backlog.

Also guards the same-key replacement path. It recomputes start_time from elapsed
time, which is NEGATIVE for a notification that has not appeared yet -- and
Timer::Value is unsigned, so subtracting it would have wrapped and flung the
notification years into the future. It now keeps the scheduled start instead.

Ordering is still insertion order, so an unlock posted after a batch of
leaderboards still comes last. Visible rather than buried, but not prioritised.
2026-07-28 12:50:06 -04:00
jpolo1224 0ddf000e3f Android: screenshot button, macro skins, and make grid snap survive Save
Three touch-overlay changes that share the same files.

SCREENSHOT is now an on-screen button next to SAVE and LOAD, off by default.
It started as a pause-menu entry, which was the wrong place: the core writes the
PNG and confirms on the OSD, and the OSD is hidden while the menu is up -- so you
tapped it, saw nothing, and only learned it had worked after backing out.

Macro buttons can take skin artwork. A macro can already fire any pad input,
including L-Stick Left/Right, so a racing layout of steer/steer/accelerate/brake
was buildable -- but the four buttons were stuck with the generic M1-M4 labels
because there was no skin slot for them. ic_controller_macro1_button.png through
macro4, and m1-m4 too, since that is the name David reached for first.

That also exposed a silent truncation: the skin import cap was 24 while the key
list is now 28, so a complete pack would have had images dropped on import with
no error at all -- they simply would not appear. Raised to 40, with a comment
tying it to the key count so the next slot added does not repeat it.

Grid snap now commits on Save. The editor draws widgets snapped while leaving the
underlying fraction raw (snapping live fights the transform gesture's delta
accumulation and makes dragging feel stuck), and the commit was supposed to
happen on finger-up. But that lived in detectTapGestures' tryAwaitRelease, and an
actual DRAG is consumed by the neighbouring detectTransformGestures, which
cancels the tap detector and makes tryAwaitRelease return false. So it only ever
committed if you tapped without dragging: the layout looked aligned the whole
time you were editing and reverted the moment you saved.

Reported by David (SSR) and by a user who caught the grid reverting.
2026-07-28 12:49:48 -04:00
jpolo1224 c553aa8acb GS: add a 21:9 aspect ratio
Requested by David (SSR), who noted no PS2 emulator offers one: without it the
only way to use an ultrawide patch was Stretch, which distorts. Useful on folds,
tablets, DeX and anything driving a 21:9 panel.

Added to the generic aspect AND the FMV override, since a game that wants
ultrawide gameplay usually wants it during cutscenes too. Adding it to the FMV
enum also shifted MaxCount, which the name array is sized from -- that array had
to grow with it or the last entry would have been a hole.

The Kotlin side needed SIX edits for one new enum value, and getting five of them
right still left the feature completely dead:

  RendererTab       options list + clamp
  RendererTab       FMV options list + clamp
  EmulationMenu     setAspectRatio clamp
  EmulationMenuScreen  the pause menu's own options list
  Settings          NativeApp.setAspectRatio(coerceIn(0, 4))   <-- the killer
  Settings          INI name<->index, both directions

That fifth one clamped on the way to the core, so the picker highlighted 21:9
while the emulator was told 10:7 -- UI correct, nothing happens, no error. The
sixth meant the choice would not have survived a reload even once it applied.

The NATIVE clamp needed no change at all, because it derives its bound from
AspectRatioType::MaxCount instead of hard-coding it. That is the pattern the
Kotlin side should follow; four literal 4s in four files is why this was a
six-site change instead of a one-site one.
2026-07-28 12:49:30 -04:00
jpolo1224 1eafb9971d GS/VK: check device features exist before asking for them
An advertised extension does not guarantee its feature bit, and requesting a
feature the driver does not have fails vkCreateDevice outright with
VK_ERROR_FEATURE_NOT_PRESENT. So a driver that offers, say, VK_EXT_line_
rasterization while reporting bresenhamLines as false did not cost us one
optional nicety -- it took Vulkan down completely, and the renderer refused to
start with "Failed to create render device".

Reported on a PowerVR BXM-8-256, which is neither Adreno nor Mali and so had
never been through this path. Deterministic: three identical failures in a row,
and the only way out was switching to OpenGL.

The file already knew about this trap. A comment above the depth-ROAA probe spells
it out exactly -- but the lesson had been applied to that one sub-feature and
nowhere else, so the other six were still requested blind. ProcessDeviceExtensions
does perform the same reconcile, and would have caught it, except that it runs
AFTER vkCreateDevice and can therefore only ever describe the failure.

Probe all six up front in one vkGetPhysicalDeviceFeatures2 call -- provoking
vertex, line rasterization, ROAA colour and depth, feedback-loop layout,
swapchain maintenance1, fragment shader interlock -- and drop whatever is not
really there. Dropping is logged WITH THE FEATURE NAME, because "Vulkan works but
one thing is off" is a completely different bug report from "Vulkan does not
start", and the next person needs to know which feature the driver misrepresented
rather than guessing from a bare error code.

Confirmed fixed on the reporting device.
2026-07-28 11:29:59 -04:00
jpolo1224 f98371ddcd Android: license the Discord helper MIT, not GPL
The process split was only half done. discord_bridge.cpp still carried PCSX2's
"GPL-3.0+ / PCSX2 Dev Team" header -- wrong on both counts, since it is neither
PCSX2's code nor compatible with what it links -- and the four helper Kotlin/Java
files carried no header at all, so they inherited the repository's GPL by
default. A GPL file that links Discord's proprietary SDK is exactly the defect
the separate process exists to prevent; the boundary has to hold in the licence
headers as well as in the linker.

Everything on the helper side of that boundary is now MIT with accurate
copyright: the bridge, the native wrapper, the service, the auth activity and
the shared IPC definitions. MIT rather than Apache-2.0 because it has to work in
both directions -- the emulator side (DiscordPresence and the Friends UI, which
stay GPL) consumes the shared IPC definitions, and that only holds if this side
is permissive.

Each header says why, so it does not get "fixed" back to the PCSX2 boilerplate
by someone tidying up.

No functional change: comments only, both sides still compile.
2026-07-28 01:52:41 -04:00
jpolo1224 b762cc01de Android: make the bundled patches visible, and add All on / All off
Two reports from Rei Ayanami, one of which turned out to be a real gap rather
than a misunderstanding.

A game could report "3 game patches are active" with an empty patch list and
nothing anywhere to turn them off. Those patches come from the ~2 MB patches.zip
we ship, and the manager only ever listed files in the patches folder, so
everything inside the zip applied invisibly. Worse, the core auto-applies any
group with no [Name] ("we auto enable anything that's not labelled" --
Patch.cpp), and a group with no name has nothing for a toggle to hang off.

There is now a card showing exactly what the bundled zip contributes to this
game, with unlabelled groups marked as always-on, and a button to copy the file
into the patches folder. That is not convenience: the core prefers a pnach on
disk over the zip and explicitly disables the bundled copy when it finds an
unlabelled patch on disk, so extracting both makes the cheats individually
switchable AND takes the invisible copy out of play. It is the only way to turn
an unlabelled bundled patch off at all. The card is hidden when the game already
has a pnach on disk, because then the zip contributes nothing and showing it
would be a lie.

All on / All off sits above the cheat list, not below: a community pnach can run
to a hundred entries, and a control you have to scroll past all of them to reach
does not solve the problem it exists for. It rewrites the file once rather than
once per cheat -- a hundred read-modify-writes is a hundred chances to
half-apply -- and each button disables itself when there is nothing left to do.
2026-07-28 01:38:35 -04:00
jpolo1224andSplaser bf7300e654 Android: show the game CRC next to its serial
A PNACH is named <SERIAL>_<CRC>.pnach, so the two values needed to name one
should not live on separate screens. The CRC now appears in the pause-menu
header, in the game details you get by long-pressing in the library, and as a
row that stays put in the Info tab instead of appearing mid-identification and
shoving everything below it down.

Based on Splaser's PR #459, with three changes:

- The long-press sheet showed "CRC ..." forever for an image that could not be
  identified, because produceState starts at null and RESOLVES to null; those
  two states are indistinguishable. Now tri-state: "..." while identifying, an
  em dash when identified as unknown.
- The VM-then-identify fallback was copy-pasted into a second place. One
  DiscIdentity.resolve now, keeping the serial-match guard -- without it a
  different running game lends its CRC to whatever you long-pressed.
- The pause header cannot trust the live VM CRC alone. On ISO boots the core
  hands ELFLoadingOnCPUThread an empty path, UpdateELFInfo takes its failure
  branch, and s_current_crc stays 0 -- the emulog shows the loader computing the
  real CRC and the VM then reporting 00000000. It falls back to identifying the
  image, same as the other two screens.

Co-authored-by: Splaser <splaser@users.noreply.github.com>
2026-07-28 01:38:35 -04:00
jpolo1224 cc94bc57c0 Android: credit the right people in What's New
Contributors were derived from commit authorship between tags, which is not who
a release credits. Work ported in from other projects lands as commits authored
by whoever did the porting, so the people the notes thank never appeared, while
anyone who happened to commit inside that tag range did -- credited in a release
that says nothing about them.

Read the @mentions in the release notes instead. That is the credit somebody
deliberately wrote. Avatars come from github.com/<login>.png, a plain redirect,
so this costs no API calls at all: no rate limit, nothing to cache, and it works
offline from cached notes. The tag-to-tag comparison it replaces cost one
request per release against an unauthenticated budget of sixty an hour.

Each card also shows the release author's avatar, which was already in the
releases payload and free.
2026-07-28 01:38:34 -04:00
jpolo1224 bc951c84dd Android: run the Discord SDK out of process, and finish the friends UI
ARMSX2 is GPL-3.0+ and the Discord Social SDK is proprietary. Linking them into
one binary would make the emulator a combined work with a library whose
corresponding source cannot be supplied, so the SDK now runs as a separate
program in its own process and the two talk over a deliberately dumb message
interface: a title, a serial, an image URL, a list of names. No emulator type
crosses that line.

libemucore has no DT_NEEDED on the SDK and never loads it; the bridge builds
into its own libarmsx2_discord.so, loaded only in :discord. With the feature
switched off that process does not exist and the library is never mapped at all.
Verify with: llvm-readelf -d libemucore_4k.so | grep -i discord  (must be empty)

The SDK itself is no longer in this repository. Discord's terms permit shipping
it inside a working application but not republishing the raw SDK, so it is an
optional private build input via DISCORD_SDK_DIR. Unset -- every public clone --
the feature compiles out and the UI reports itself unavailable. Deliberately not
a product flavour: one release variant, and the only difference is whether that
directory was present at build time.

Also here, from testing the feature into shape:

- Presence uses the game's own cover art, and our logo (an Art Asset key, not a
  repo URL, so it tracks the current mark) when idle.
- Friends show their avatar AND the cover of what they are playing, never one
  instead of the other -- swapping the face out for box art loses the identity
  exactly when the row gets interesting.
- A count badge on the Friends entry point, in the drawer and in the in-game
  header, because the point of it is to be seen by someone not looking at the
  friends list.
- Friends moved off the in-game tab rail into a header button with its own
  panel. It was last on a rail that scrolls, so reaching it meant knowing it was
  there. The panel is composed, not a Dialog: a Dialog takes its own focused
  window and swallows gamepad keys before our input plumbing sees them.
- "<friend> is now online" is one Compose banner at the Activity root, replacing
  a split between an emulator OSD message in game and nothing at all in the
  library. The OSD is text-only and could never show an avatar.
- Reconnect after a drop, with backoff. The SDK does not retry and its
  connection does not survive being backgrounded, so a drop used to be terminal
  until the app was restarted.
2026-07-28 01:38:34 -04:00
jpolo1224 069f8a44f3 Android 2.6.5.1: Local Link LAN play, async GS readback, pause/rotation/settings fixes
Crash and correctness
- Fix a crash when backgrounding the app mid-game: onPause flushed the Vulkan
  pipeline cache from the UI thread while the GS thread was creating pipelines
  into the same VkPipelineCache. Vulkan requires that handle to be externally
  synchronised, so this was a driver-level data race and crashed on Adreno and
  Xclipse alike. The flush now runs on the GS thread, posted via the CPU thread
  so it does not race the EE-owned MTGS ring.
- Fix an unbounded out-of-bounds vertex read in the GSRendererHW sprite-merge
  paving path: the inner loop advanced i instead of j, so j stayed loop-invariant
  and the scan walked past m_vertex->tail.
- Fix per-game settings being silently ignored: gamesettings/<serial>_<CRC>.ini
  loads into a higher-priority layer than anything the app writes, and saves made
  from the library never regenerated it, so any key already in that file
  overrode the user permanently. Only the category-Reset path rewrote it, which
  is why Reset appeared to be the only thing that worked.
- Fix screen rotation: the BIOS followed the launcher rotation instead of the
  renderer's (it has no GameInfo, and the tier was keyed on that), and the
  launcher stayed locked in a game's orientation after exit because the cleanup
  lived only inside stop()'s vmRunLoopActive-guarded branch, which loses a race
  against the VM thread's own finally. Rotation tier is now an explicit flag and
  the cleanup runs on every terminal path.
- Discard the Vulkan pipeline blob whenever the SPIR-V cache is discarded. It was
  validated only against the device header (vendor/device/pipelineCacheUUID),
  which is identical across an app update, so a SHADER_CACHE_VERSION bump kept
  every pipeline built from the old shaders and nothing pruned it.
- Make eeRecExitRequested atomic: it was a plain bool written from the JNI thread
  and read on the CPU thread.
- OpenGL: restore GL_PACK_ALIGNMENT after readback, add the missing memory
  barrier after the CAS dispatch, and initialise GLState::depth_mask to GL's
  actual default.
- DEV9: log the GetNetAdapter default: bail and the InitNet skip. Both returned
  silently, so a settings mistake surfaced as missing hardware three layers away.

Local Link (new)
- New DEV9 backend bridging emulated PS2 Ethernet between devices over
  authenticated local UDP, so games with a built-in LAN / System Link mode can
  play together. Ported from EmuCoreX (sashkinbro) with the wire format
  unchanged, so peers remain compatible across both forks.
- Network mode picker (Online / Host / Join), host address readout, auto-derived
  peer ids, generated room codes, hostname support alongside numeric IPv4, and a
  link to the supported-games list. Fully controller-navigable.

Performance
- Asynchronous hardware download mode (experimental, opt-in): non-blocking
  GPU->CPU readback so the EE thread no longer waits on the GS thread. Ported
  from EmuCoreX. Appending Asynchronous to GSHardwareDownloadMode makes the enum
  non-ordered, so the relational comparisons on it are replaced with
  IsHardwareDownloadReadbackEnabled / IsHardwareDownloadEEThreadRead.
- Affinity Control Mode (experimental, opt-in): EE/VU/GS priority orders plus a
  Performance Cores mode. Android otherwise leaves these threads unpinned.
- Raise the texture-replacement cache ceiling from 6 to 16 GB; RAM/2 remains the
  real limiter, so this only binds at 12 GB RAM and up.
- Low Latency frame pacing is no longer the default, with a one-time migration
  for installs that took the earlier flip.

Features
- Auto renderer resolves to Vulkan HW on Adreno.
- Auto Progressive Scan (per-game): holds Triangle+Cross through boot.
- OLED black as a modifier over any accent colour, including Custom and RGB.
- Optional system keyboard instead of the built-in on-screen one.

Game compatibility
- Everybody's Golf 4 / Hot Shots Golf Fore! hwDownloadMode across all regions
  (PR #421, XDarkFallenX).
- Delta Force: Black Hawk Down (PR #401, XDarkFallenX).
- Reduced input latency and input handling improvements (PR #403, Splaser).

RetroAchievements
- Inject the client version from a build-time secret kept out of public source,
  with a stock-PCSX2 fallback for secret-less builds, so third parties cannot
  copy the client identity. Covers the iOS token too.
2026-07-25 00:48:56 -04:00
jpolo1224 8e23439b26 Android 2.6.5: Adreno Vulkan default, low-latency default, UI sounds, RA client hardening, GameDB
Rendering
- Auto renderer now resolves to Vulkan HW on Adreno (OpenGL elsewhere).
- Mobile hardware ROV (Phase 0): tile-native depth feedback behind the ROV toggle.

Performance & input
- Low Latency frame pacing is the default on capable devices, with a one-time
  migration for existing installs; low-end devices keep the queued pacing.
- Reduce Android input latency and improve input handling (PR #403, Splaser).
- Experimental CPU clock hint (ADPF) toggle in Performance settings (default off).

Audio & UI
- Pop-up open/close sound cues (info, hardcore confirm, patches & cheats).
- Alternating controller navigation / slider tick sounds.

RetroAchievements
- Inject the RA client version from a build-time secret kept out of public source,
  with a stock-PCSX2 fallback (no hardcore) for secret-less builds. Applies to the
  iOS client token too. Prevents third parties from copying our User-Agent.

Game compatibility
- Delta Force: Black Hawk Down (SLUS-21124 / SLES-53299) GameDB fixes
  (PR #401, XDarkFallenX).
2026-07-24 02:40:17 -04:00
jpolo1224 d856d404e8 Android (github flavor): opt-in auto-check for updates on launch
Adds a "Check on launch" toggle to the App-tab updater panel, default OFF. When
enabled, a silent GitHub check runs once at boot (AutoUpdateGate, mounted at the app
root in setContent, gated on IN_APP_UPDATER) and pops the update prompt only if a
newer release exists -- no "up to date" popup on every launch, and nightly builds are
skipped via checkForUpdate's versionCode guard. Reuses the manual button's exact
check/download/install path.

Github flavor only, like the rest of the updater: AutoUpdateGate is real in src/github
and a no-op stub in src/play, so the boot-check + network code never enters the Play
AAB (build-play-aab.sh still fails closed on REQUEST_INSTALL_PACKAGES).

Requested by takanome9104.
2026-07-23 03:00:02 -04:00
jpolo1224 8f60f14e43 Android (github flavor): in-app GitHub-release updater
Adds a "Check for updates" panel to the top of the App settings tab, github
sideload flavor only. It queries the GitHub releases/latest API, semver-compares
the latest stable tag against the installed build, and offers to download the APK
and hand it to the system installer (progress bar + FileProvider). Nightly builds
(versionCode = Unix seconds, so > 1e6) are always ahead of any stable release, so
they short-circuit to "on the nightly channel" and are never prompted to a stable.

Kept entirely out of the Play build, the same way all-files access is:
- IN_APP_UPDATER BuildConfig flag (true github / false play) gates the App-tab hook.
- The real updater + REQUEST_INSTALL_PACKAGES + the FileProvider live in src/github;
  src/play ships a no-op UpdaterEntry stub so shared code still compiles for play.
- build-play-aab.sh now FAILS CLOSED if REQUEST_INSTALL_PACKAGES appears in the AAB
  (a self-updating app is a hard Play-policy violation).

Verified: the github APK ships the permission + FileProvider + updater code; the play
AAB has neither the permission, the FileProvider, nor the network/install code.
2026-07-23 02:04:46 -04:00
jpolo1224 9ee2b71d7c Android build: adapt the GameDB-from-bin generation to AGP 9.2.1
The generateSharedResources wiring failed the Android build on our pinned
AGP 9.2.1 / Gradle 9.4.1:

- assets.srcDir was passed a Provider (generateSharedResources.map { it... }),
  which AGP 9.2.1 rejects at configuration time ("cannot add Provider instances
  to the Android SourceSet API") -- so the whole Android build was red. Use a
  concrete build-dir path and wire the task dependency explicitly instead.
- the dx11 exclude ("dx11/**") is relative to bin/resources and never matched
  shaders/dx11/, so 8 Windows-only DX11 shaders leaked into the APK. Fixed to
  "**/dx11/**".
- declared generateSharedResources as a dependency of the asset-merge AND lint
  model/analyze tasks (they consume the generated assets dir), so Gradle 9's
  strict implicit-dependency validation passes.

Verified: dual-core release APK builds clean (vc1301); the generated tree carries
bin GameIndex.yaml + the overlay, Dirge hwDownloadMode:2 intact, dx11 trimmed.
2026-07-23 01:44:15 -04:00
jpolo1224 1fde0e4467 Android: achievement filter + RA notification/indicator settings, touch-editor grid snap + movable panel, Back-opens-menu
- RetroAchievements: an All/Unlocked/Locked filter over the achievement list,
  plus DuckStation-style notification settings -- notification and leaderboard
  duration sliders and on-screen position pickers (a 3x3 grid) for both the
  toast notifications and the challenge/progress indicators. Backed by a new
  setAchievementsOptionInt JNI bridge over the existing [Achievements] config.
- Touch layout editor: snap-to-grid (widgets snap by their centre so they line
  up cleanly), and a movable + resizable settings panel -- drag the grip to
  move it, -/+ to resize, double-tap to reset -- with per-orientation placement
  so it stops covering the buttons being edited.
- The Android system Back button / gesture opens the in-game menu (#384),
  toggle in Settings > Hotkeys (default on). Only the system back is routed;
  controller Circle stays the PS2 button.

Requested in #384 by resurrectdev1 and Leunamme30.
2026-07-23 00:15:02 -04:00
jpolo1224 6ce3d9ad7d GS: work around Mali r44p1 device loss on the feedback-loop blend path
The Mali-G615 r44p1 blob loses the rendering context under the in-tile
feedback-loop blend path used for accurate blending: VK_ERROR_DEVICE_LOST on
Vulkan (attachment-feedback-loop) and an equivalent context loss on OpenGL ES
(ARM framebuffer-fetch), crashing effectively every game at any resolution.
It is specific to this driver build -- other Mali blobs, including other
Mali-G615 units on different drivers, run the fast path fine, and NetherSX2's
older renderer is unaffected.

Gate only r44p1 off that path, narrowing by driver version rather than
re-blocking the vendor -- exactly what the extension-select comment above the
Vulkan site anticipated. Vulkan falls back to the per-primitive barrier path
(verified on hardware: no measurable slowdown, God of War holds 60fps);
GLES has no texture-barrier extension so it falls to the framebuffer-copy
path (stable, marginally slower) rather than crashing.

Reported and confirmed on-device by Aryan3472.
2026-07-22 21:50:37 -04:00
jpolo1224 0a017612af Android: library opacity, remove-from-recents, recents export, patches notice
- Library opacity slider (Settings -> App) fades the game rows and cards so the
  wallpaper shows through the list.
- Long-press -> Remove from Recently Played drops a single game from the shelf;
  the game menu now scrolls so no action is clipped in landscape.
- The recently-played list is mirrored to recent_games.json in the data root for
  companion apps, reworked from misantronic's PR #391 to run off the UI thread
  and to update on removal too.
- A notice on the Patches & cheats screen warns that outdated cheats/patches are
  the most common cause of false bug reports.
2026-07-22 15:39:39 -04:00
jpolo1224 6a1e12a3c3 GameDB: apply per-game hardware download mode; default Dirge to no-readbacks
hwDownloadMode was parsed as an invalid GS HW fix and dropped, silently
ignoring the entries that used it. Make it a real fix that sets HWDownloadMode,
applied as a default only so a player's own Hardware Download Mode still wins.
Dirge of Cerberus (all regions) now ships with no-readbacks, holding full speed
on GPU-bound devices.
2026-07-22 15:39:39 -04:00
jpolo1224 2bca7f91b9 Android: Nintendo Joy-Con d-pad now binds and works in-game
Android ships no key layout for the Joy-Con (vendor 0x057E), so every button
arrives as KEYCODE_UNKNOWN and the d-pad could never be mapped or dispatched.
Synthesise a stable, distinct keycode from each button's scanCode at the entry
of dispatchKeyEvent and re-dispatch once, so bind-capture, menu nav and the
in-game lookup all see the same real, matchable key. Gated to 0x057E so no
other controller is affected; scanCode added to the @@JOYCON@@ diagnostic.
2026-07-22 15:39:39 -04:00
jpolo1224 c0351e3105 Android: fast-forward speed slider, and fix the game staying paused after the in-game menu
- Fast-Forward Speed slider in the pause menu (On-Screen tab, under Frame Limit):
  2x-10x, or Unlimited (the default, unchanged behaviour). Below the top it runs
  Turbo at the chosen multiplier via a new setTurboScalar JNI; at the top it uses
  the uncapped path. Every fast-forward entry point (toggle, hold, hotkey,
  settings re-apply) now routes through one ffLimiterMode() helper.
- Fixed the game sometimes staying paused after backing out of the in-game menu.
  Reverts the brief menu-pause audio-keepalive added earlier — its purpose (the
  fast-forward-from-menu hitch) turned out to be a GLES shader-compile stall, not
  audio — restoring the previous pause/resume behaviour. closeAndResume also now
  resumes on RUNNING as well as PAUSED, covering the open-then-close race where the
  asynchronous pause hasn't flipped the state yet.
2026-07-22 00:22:11 -04:00
jpolo1224 d56bff1010 Android: RetroAchievements profile picture and both score totals, plus in-game vibration
- The RetroAchievements menus now show the user's profile picture (RA UserPic) and
  both point totals — hardcore and softcore — on the full RA screen and in the
  in-game pause panel. Hardcore reads "HC" in red, softcore "SC" in blue. The
  avatar URL is emitted in the achievements JSON: from the live client while a game
  is loaded, or rebuilt from the saved account username so the library RA menu can
  show it with no game running.
- The Vibration Strength slider (the global 0-200% haptic multiplier over both
  controller rumble and on-screen touch feedback) is now reachable in-game from the
  pause menu's Controls pane, not only from All Settings.
2026-07-21 21:31:27 -04:00
jpolo1224 90daa091db Android: audio backend options, setting descriptions, RA/haptics polish, and ported GS fixes
Audio
- Optional OpenSL ES output backend for devices where the default AAudio path
  crackles, glitches or won't initialise (Settings -> Audio), plus a lightweight
  SPU2 mode that skips the reverb pipeline to free CPU on low-end devices.
- Keep the audio device alive across the in-game menu pause so Android no longer
  reclaims the idle stream and drops sound after the menu sits open (#333).

Settings
- Restored the per-setting descriptions under every GameDB Fix and Advanced
  Speedhack toggle (lost in the settings redesign).
- Per-game Reset now clears the native per-game INI, so it truly reverts to the
  global values instead of the game keeping stale overrides.
- On-screen display now defaults off; Custom stats appear on boot without a
  reset (#385).

Controls / RetroAchievements
- Vibration Strength slider scaling all rumble and touch haptics 0-200%.
- Achievement Sound Volume slider; points now show in the menu before a game
  loads; unlock sounds play with Do Not Disturb enabled.

Misc
- Drop the compiled GS shader/pipeline cache automatically on app update to
  avoid post-update graphical corruption.
- Animated XMB library-background fallback for GPUs without float-texture
  filtering.

GS correctness (ported from sashkinbro/EmuCoreX)
- Reset per-game hardware-hack HLE state on game change (Burnout bloom,
  IRem/GT channel-shuffle) so it no longer leaks across in-app game switches.
- Fix a non-strict-weak-ordering comparator in SortMultiStretchRects.
- Free the leaked m_expand_vao on the OpenGL device teardown path.
2026-07-21 20:42:35 -04:00
jpolo1224 1637c1e76a Android: OSD cycle, library music controls, display zoom, skin polish
- OSD hotkey now cycles Full / Minimal / Custom / Off instead of a plain
  on/off, mirrored by a selector in the in-game On-Screen menu; the mode
  persists and every mode drives the GPU-stats line so "Off" is truly off (the
  VSI/PSI leak). (Cotcho)
- Library music: a volume slider (default 15%), a user-chosen custom track with
  reset-to-default, and device-volume support. (KamFretoZ)
- Display Zoom: one AetherSX2-style slider that trims every edge by the same
  fraction to zoom in without distortion, in place of juggling the four manual
  crops. (#383)
- Drop the iOS-layout note from the skin downloader.
2026-07-20 23:25:27 -04:00
jpolo1224 30355e4b62 Android: honour PGO_MODE in the Play AAB build script
build-play-aab.sh hardcoded pgo=optimize, so a caller asking for a profile-free
build silently got one built against the profile anyway. Take PGO_MODE like the
sibling build-release-apk.sh does.
2026-07-20 23:25:26 -04:00
jpolo1224 3666dcea8c Android: animated PS3-XMB library background
Ports linkev's PlayStation-3-XMB (the same wave iOS renders in Metal): a flat
grid displaced in a GLES 3.0 vertex shader by a base spline curve plus flow /
tension / FFD terms, shaded with a fresnel-edged translucent white. Runs on a
TextureView so it composites under the Compose library, capped at ~30 fps to
stay fan-friendly, with the bundled still as a fallback if GL init fails. It is
only the default: a user-picked image (still / GIF / WebP) still overrides it
and clearing that returns to the wave. The readability scrim is dropped to a
whisper over the wave so its blue reads vivid.
2026-07-20 23:25:26 -04:00
jpolo1224 a72ad0eb52 GS: raise the texture-replacement cache budget to RAM/2, 6 GB ceiling
RAM/4 gave a 7 GB phone only 1.75 GB, so the 2.97 GB God of War 1 pack evicted
mid-preload ("cache budget reached") and thrashed, felt as a sustained FPS drop
(#376). RAM/2 gives 3.5 GB and holds that pack whole; the 6 GB ceiling lets a
big tablet keep the 5 GB Persona 3 FES pack resident. Still bounded so low-RAM
devices stay clear of the OOM killer. 6 GB is safe here: Android is arm64-only,
so size_t is 64-bit and cannot wrap.
2026-07-20 23:24:59 -04:00
jpolo1224 40f0cc9910 GS/Vulkan: drop the swap chain when a lost-surface recreate fails
On VK_ERROR_SURFACE_LOST_KHR the recreate can hit NATIVE_WINDOW_IN_USE on the
stale Android window; the old code kept the half-dead swap chain and retried
the same inline recreate every frame, so a game relaunch stayed black forever
on stock Qualcomm Adreno (#380 / #374 — Turnip tolerates it and recovers).
Fully drop the swap chain on that failure so the next onNativeSurfaceChanged
rebuilds from a fresh surface instead of hammering the in-use one.
2026-07-20 23:24:59 -04:00
jpolo1224 4d67c416ad DEV9: keep Android DNS empty so fast-forward networking works
Android's Auto DNS returned an empty list before 2.6.4; 2.6.4 started falling
back to public resolvers (1.1.1.1 / 8.8.8.8), which the console reaches over a
real UDP round-trip through the sockets forward path. Fast-forward outruns that
round-trip's wall-clock timing and DNS fails (#379). Restore the pre-2.6.4
behaviour by leaving Android out of the public-resolver fallback; iOS is
unchanged, and the separate GetAdapterAuto gateway-probe skip stays.
2026-07-20 23:24:59 -04:00
jpolo1224 e2aa896bd4 Android: ambient music on the game library screen
Plays a looping ambient track on the library, like a console dashboard, and
stops the moment a game boots. Toggle in App settings, on by default.

Library-only is deliberate. SPU2 output goes through Oboe and Android has
been seen reclaiming that stream when the VM pauses, so a second long-lived
stream over gameplay would land on top of an existing problem. Gating on the
VM state also matches what the hardware dashboards do.

Defers to whatever is already playing rather than starting a second stream
over a podcast, and handles audio focus: permanent loss releases the player,
transient loss pauses and resumes.

Starting is retried on a timer because the boot splash video carries its own
audio track and MainActivity is launched from its completion callback, so the
first attempt races the splash stream tearing down. A single attempt lost
that race and never retried, leaving music working only after a game had been
launched and exited.

The player is built by hand instead of MediaPlayer.create, which prepares
internally and would leave the media audio attributes ignored.

Track: Calm Ambient 1 (Synthwave 4k) by The Cynic Project, released CC0.
Attribution is not required by that licence but the author asks for it, so it
is credited in the About screen.
2026-07-20 18:22:04 -04:00
jpolo1224 ff537acc80 Android: browse and download controller skins in-app
Adds SkinRepo, following the same shape as the driver and patch browsers:
read an index from the skins repository, download the archive, and hand it to
the existing skin installer so extraction and validation stay in one place.

Skins are listed with a preview image, name and size. The manifest is
preferred and the git tree is a fallback, so the browser still works if the
index is missing. Archives under 1 KB are skipped — the repository briefly
carried two-byte placeholders that downloaded fine and installed nothing.

Every path segment is percent-encoded because almost every skin filename
contains spaces. Downloaded archives import through a new entry point that
takes an explicit name: the picker path resolves names through a document
URI, which yields nothing for a downloaded file, so every download would
otherwise have installed as "skin", "skin_1", "skin_2".

The list notes that these packs carry iOS-only layouts, so a downloaded skin
changes how the buttons look and not where they sit.
2026-07-20 18:21:51 -04:00
jpolo1224 5c2e93904f Android: add an on-screen keyboard hotkey for the emulated USB keyboard
Android soft keyboards commit text through an InputConnection rather than
sending KeyEvents, so the on-screen keyboard did nothing for games that read a
USB keyboard while a physical or Bluetooth one worked. Host an invisible text
editor view, convert the committed text back into key events with
KeyCharacterMap (which also emits the shift presses that make capitals and
symbols come out right), and feed the existing usbKeyboardKey path.

Bound to a hotkey rather than a setting so chat can be opened without pausing
the game. The close path lives in onKeyPreIme because soft keyboards claim
gamepad buttons for their own navigation and would otherwise swallow the
hotkey, leaving no way to dismiss the keyboard. TOGGLE_KEYBOARD is appended
last in SysHotkey because bindings resolve by ordinal.

Key states are paced on a worker thread: usbKeyboardKey only sets a bind value
that the VM samples on its own schedule, so a press released immediately can
fall between two samples and never register.
2026-07-20 16:55:16 -04:00
jpolo1224 5843b5da11 Android: replace the compatibility row with play time in the game info tab
Compatibility only has a value where the GameDB carries one, so the row read
"—" for most of the library. Play time is populated for anything actually
played, and the per-serial totals were already being recorded.
2026-07-20 16:55:16 -04:00
jpolo1224 f964d89c84 Android: show play time and last played in the game info tab
The per-serial tracking never stopped - PlayTime.startSession and
endSession still bracket the running VM, and PlayTime's own comment says
it is shown in the info tab. Only the two rows that displayed it were
lost in the interface rebuild, so existing users already have totals
recorded and will see them as soon as they open the tab.
2026-07-20 15:17:32 -04:00
jpolo1224 613f384fc0 Android: add Material You, an RGB cycle and a custom accent theme
Material You uses the wallpaper-derived dynamic palette. It needs Android
12 while minSdk is 26, so the option is hidden below that rather than
falling back silently - picking a theme and getting a different one reads
as a bug. It also follows the system light/dark setting, so it joins
System in the two places that decide system-bar contrast; left in the
dark-theme branch it would have put dark status-bar icons on a light
palette.

RGB cycles the hue continuously the way peripheral lighting does, accent
and surfaces together. The hue is quantised before the scheme is rebuilt:
a ColorScheme change re-runs MaterialTheme and recomposes the whole tree,
so animating it per frame would repaint every screen at display rate for
a decorative effect.

Custom derives a scheme from a colour picked with RGB sliders. The hue is
kept exactly while saturation and brightness are clamped into a legible
band - a raw accent lets you choose near-black or a muddy brown and get
unreadable chips, which comes back as a bug report rather than as a bad
choice. Surfaces take the same hue, as the fixed palettes do.
2026-07-20 15:17:32 -04:00
jpolo1224 611066a47c Android: add a Cyan UI theme
Bluer and brighter than Teal, which leans green - the two sit next to
each other in the picker and need to be tellable apart at a glance.
2026-07-20 14:55:39 -04:00
jpolo1224 47b917b251 Android: patch manager, memory card and library fixes
Patch manager: the install action rendered below the full cheat list, so
with fifty-odd cheats it sat off the bottom of the screen and a ticked
patch was never installed - the tick is only a selection. It now sits
above the lists. Installed files are listed for the running game rather
than every game at once, failing open when there is no serial to scope
by, since hiding a file the user just installed is worse than listing a
few extra. An install with no known CRC now reports that instead of
writing a filename the core will never load.

Memory cards: with a game in context the slot buttons write a per-game
card and can be cleared back to the global one. Previously both slots
were always global and a separate button covered slot 1 only, so "this
card in slot 2 for this game" could not be expressed at all. Folder
cards are created through the Java file API, and are shown as folders
rather than as zero-byte files.

Also: achievements in progress sort above the rest, and the library
keyboard gains a shift key.
2026-07-20 14:06:43 -04:00
jpolo1224 b9ab52241c Android: controller input fixes and shoulder-button tab navigation
The right stick's directions could not be bound on a Joy-Con while R3
bound normally, because R3 is a keycode and the directions are axes: a
Joy-Con reports its right stick on AXIS_RX/RY and every right-stick path
read AXIS_Z/RZ, so the axes were simply invisible. Resolve the pair per
device (cached - getDevice is a binder call and motion events are far
too frequent to query per event) and use it for bind capture, dispatch,
stick hotkeys and both D-pad folds. Pads that report Z/RZ are untouched.

Adds a per-stick response curve, a left-stick-as-D-pad mode, and routes
both halves of a Joy-Con pair to one port so a game sees one controller.

L1/R1 now flick between settings tabs. It is handled in the key
dispatcher rather than in Compose because shoulder buttons never reach a
Composable, and the hook is registered only while the settings screen is
showing, so in-game shoulder presses still reach the pad.

Launching a .cue from a frontend resolves the sheet's first track and
boots that instead; the core has no cue parser and .cue is not in its
disc whitelist, so the file could never boot directly.
2026-07-20 14:06:43 -04:00
jpolo1224 6d527bc89d Android: per-tab settings reset, UI colour themes, and renderer options
Reset restored the entire scope - Settings() globally, or the whole
per-game override blob - so pressing it on the Renderer page also wiped
Audio, Network, Performance and Fixes. It now resets only the tab being
shown, the confirm dialog names that tab, and it is hidden on tabs that
own no settings at all. Per-game scope prunes just that tab's override
keys instead of deleting every override the game had.

Themes: "Dark" was always the blue-tinted dark theme, which only became
confusing once other hues existed, so it is renamed to Blue and joined
by Purple, Pink, Red, Orange, Green and Teal. Each is built from the
night scheme the way Black and OLED already were, so surfaces carry the
accent's tint rather than leaving blue chrome under a different accent.
The stored preference is the enum name, so an existing "Dark" simply
falls through to the Blue default - same colours, nothing to migrate.
The picker is now a wrapping chip group driven off the enum; a segmented
row is fixed-width and would squeeze eleven options into slivers.

Also: a GS multi-threading toggle, a portrait top/centre option, an
honest 59.94 Hz NTSC framerate stop (an integer slider could neither
display nor re-select the true default once dragged), and round action
buttons centre their glyph rather than its padded layout box.
2026-07-20 14:06:43 -04:00
jpolo1224 558ad4699b Android: commit the bundled ANGLE libraries
libEGL_angle.so and libGLESv2_angle.so were never tracked, so a fresh
clone built without them. applyAngleEnv silently unsets its environment
variables when the libraries are absent and the OpenGL renderer falls
back to the system GLES driver with no error, which meant the ANGLE
option quietly did nothing from the first build made in a clean checkout
onward - on precisely the devices whose native GLES stack it exists to
work around.

These are prebuilt vendor binaries, not build output, so the usual rule
of keeping jniLibs out of the tree does not apply to them.
2026-07-20 14:05:51 -04:00
jpolo1224 07d03a5eac Android: route native file creation through Java, and scope patch state per game
Two fixes that both live in the JNI layer.

Folder memory cards on a user-chosen data folder crashed on the first
new save. FUSE-backed shared storage denies libc file CREATION even
though mkdir is already routed through Java, so SaveYAMLToFile opened a
not-yet-existing _pcsx2_index with an unchecked OpenCFile and then
dereferenced null. Existing saves reuse that file, which is exactly why
only new saves crashed. Null-check the write, and add a
CreateFileViaJava fallback in OpenCFile so a denied create is retried
through the Java file API - the same libc/Java asymmetry that
CreateDirectoryPath already relies on.

Patch and cheat enable-state was written to the base settings layer,
keyed only by patch name, so enabling e.g. "Widescreen 16:9" for one
game switched on the identically named patch in every other game.
LayeredSettingsInterface returns the first non-empty layer with the game
layer ahead of the base one, so upstream keys this per serial and CRC;
do the same, and strip the migrated names from the base list so an empty
per-game list cannot fall back through to it.

The per-game INI exporter also rebuilt the file from scratch, dropping
every key it does not own - the patch lists above, and per-game
MemoryCards and Gamefixes overrides. Load the existing file and clear
only the sections the exporter actually writes.
2026-07-20 14:05:51 -04:00
jpolo1224 9232a10663 Achievements: expose whether an achievement is an active challenge
So the frontend can sort challenges in progress to the top of the list
instead of leaving them buried among the locked ones.
2026-07-20 14:05:32 -04:00
jpolo1224 c581ca0926 GS: add a portrait top-align option for the display rect
In portrait the image was always centred vertically, leaving the game
floating in the middle of a tall screen. Allow pinning it to the top,
which is what a phone held upright generally wants.
2026-07-20 14:05:32 -04:00
jpolo1224 3291334652 GS: bound the texture replacement cache and pace its uploads
The replacement cache had no size limit and no eviction. It was only
ever cleared wholesale on shutdown or game change, so every replacement
a game touched stayed resident until the process was killed: a 5 GB
uncompressed DDS pack OOM-killed the emulator mid-load, and turning
precache off only changed how quickly memory filled.

Track bytes and evict least-recently-used entries past a budget derived
from physical memory - a quarter of RAM, capped at 3 GB so a pack that
would otherwise fit is not evicted needlessly. A texture larger than the
whole budget is uploaded once without being cached, rather than evicting
everything to make room for something that cannot help.

Also pace the uploads. ProcessAsyncLoadedTextures uploaded every pending
replacement in a single VSync while holding the cache lock. Entering a
new area streams a batch in at once and an uncompressed 2048x2048
replacement is 16 MB, so ten arriving together meant roughly 160 MB of
GPU upload inside one frame. Spread them across frames instead; the cost
is a frame or two of pop-in rather than a dropped frame.
2026-07-20 14:05:32 -04:00
jpolo1224 e43707d64c DEV9: use the Android sockets path for adapter selection and DNS
The mobile fallbacks were gated on TARGET_OS_IPHONE only. On Android
GetAdapterAuto still required a host default gateway, which the app
sandbox cannot read out of /proc/net/route, so auto-selection failed and
InitNet force-disabled Ethernet for the session - reported as
"connection device not found" with the interface plainly visible in the
picker. GetDNS likewise returned an empty list because there is no
/etc/resolv.conf.

Extend both gates to __ANDROID__: select on a usable IPv4 interface and
fall back to public resolvers. iOS and desktop behaviour is unchanged.
2026-07-20 14:05:32 -04:00
jpolo1224 e8234e0ddf Android: expose GS Multi-threading (GV7 back-thread) in settings + in-game menu
Off/On toggle (On = Pipelined, enum 3) for GSBackThreadMode, default Off:
- Renderer settings tab, under the graphics-API/driver picker
- In-game quick menu (Graphics), grouped with renderer + Apply & Restart
  (MenuSwitchRow gains an optional inline description)
- Settings model with per-game override, i18n strings, search index entry
- native-lib snapshots the field across a live settings apply so the
  restart-required option can't trigger a mid-game device recreate
2026-07-19 21:04:34 -04:00
jpolo1224 22f900e6cb Achievements: emit Encore / Spectator / Unofficial-test mode state
The Android achievements UI already reads encoreMode, spectatorMode and
unofficialTestMode out of the options JSON and offers toggles for them, but the
native emitter never wrote those fields, so all three always read false
regardless of the underlying setting. The rc_client wiring behind them already
exists; this only exposes their state.

Applied as a hunk rather than a file copy so the iOS UserStats/GameStats/
AchievementList stubs in this file are preserved.

(cherry picked from commit a0bdcbe917)
2026-07-19 21:04:34 -04:00
jpolo1224 adeab9c430 Android UI: pause redesign, custom game names, in-game keyboard, GameDB
Pause button: one clean top-right glyph, single tap opens the menu, and the
on/off toggle is replaced by a tap-to-reveal option that cannot lock the user
out. OSD defaults to 65%, migrating anyone still on the old 100 default, and the
mislabelled On-Screen slider is renamed with the two UI-size sliders moved up
beside it. Widescreen patches relabelled to state that they auto-apply.

Custom game names, editable per title from the Info tab.

The in-game keyboard is hosted once at the top level. It was mounted inside the
in-game screen, but Settings is a separate nav destination that unmounts it, so
the keyboard only appeared after backing out of per-game settings.

Removes the duplicate All Settings shortcut from the in-game menu rail (the
Options tab already has a labelled one) and centres the remaining tabs, which
were left top-aligned with dead space beneath them.

GameDB: Everybody's Golf 4, Magna Carta, Rumble Racing and others from the merged
android-v24 batch, plus four repairs to it - a misspelled gsHWFixes key and a
value missing its '#' comment marker, each of which silently dropped that game's
entire fixes block. Ratchet: Deadlocked / Gladiator get FullVU0SyncHack across
all six SKUs, which is what clears the in-game lockup.

(cherry picked from commit 42baebb860)
2026-07-19 21:04:34 -04:00
jpolo1224 41e91adf0b Android: hardcore patches, overscan crop, OSD colour, fast-forward, frontend launch
Patches under RetroAchievements hardcore: the gate dropped every on-disk pnach,
which is asymmetric with the fallback below it - the bundled patches.zip stays
enabled in hardcore, so a widescreen or bug-fix patch worked from the archive and
silently did nothing from disk, killing everything the in-app Patch Manager
writes. Gate cheats only; they remain blocked at enumeration and in
ReloadEnabledLists, and the two feed separate stores.

Overscan crop (issue #293): the core has always honoured GSConfig.Crop but
Android never exposed it. Four sliders in native PS2 pixels, so a value means the
same thing at any upscale multiplier.

Custom OSD colour: new GSOptions::OsdColor, with the ImGui overlay drawing from
it instead of a hardcoded white.

Fast-forward now uses Unlimited rather than Turbo. Turbo caps at
EmulationSpeed.TurboScalar (2.0x) and produced no visible speed-up on these
devices while "frame limit off" - the same Unlimited mode - demonstrably did.
Settings.applyTo() also forced the limiter back to 0/3 on every apply, cancelling
an active fast-forward while the UI still reported it on; it now preserves the
latch like the in-game overlay path already did.

Per-game settings when launching from a frontend: an external launch passed a
null GameInfo, so settingsKey was null and launchGame resolved global settings -
per-game settings, per-game memory cards and per-game orientation all ignored,
while the same title from our own library applied them. Build a GameInfo for the
incoming URI, probing the serial off the image the way the library scan does.

(cherry picked from commit 7c16cbfa13)
2026-07-19 21:04:34 -04:00
jpolo1224 20d5daf95d Android GS: CPU-decode BC textures, and make pack state visible
Mobile GPUs frequently expose no block-compression support at all (Vulkan
textureCompressionBC false on Adreno 650 / Snapdragon 865, and on Mesa Turnip
for any Adreno; OpenGL gates S3TC and BPTC on separate extensions). The DDS
loader rejected those files outright, so an entire pack silently did nothing.

Worse for packs that also ship game-side data: the P3P Slim Font mod pairs new
FONT0.FNT glyph metrics with 1467 BC7 replacement glyphs, so with the textures
dropped the game indexes new narrow metrics into the old wide atlas and renders
letters sliced in half. Decode BC1/2/3/BC7 on the CPU when the GPU cannot sample
them; the decoders were already built (common/TextureDecompress.cpp, previously
used only for alpha min/max).

Every failure path out of ReloadReplacementMap was silent and they all look
identical from outside - feature off, wrong serial, empty folder, unparseable
names - so pack problems were unanswerable without an instrumented build. Log
the indexed count and the exact directory scanned ("indexed", not "loaded": the
number only proves filename discovery and parsing).

The Texture Manager now shows the serial the CORE will scan, read live from
VMManager::GetDiscSerial(), and warns when no installed pack matches it. Booting
a raw .ELF takes the elf-override branch where the serial becomes the ELF's
filename, so packs installed under the disc serial were never found. Import now
uses that runtime serial too. Texture Packs is reachable from the in-game menu
with a restart button, since the replacement map is only built at boot.

(cherry picked from commit 566ef031d8)
2026-07-19 21:04:34 -04:00
jpolo1224 30b778e9ec Android storage: fix folder memory cards on a custom data folder
libc mkdir() is denied on the FUSE-backed emulated storage Android hands out for
a user-chosen data folder, while java.io.File.mkdirs() on the same path succeeds.
FileSystem::CreateDirectoryPath went straight to mkdir() and returned failure, so
every folder-memory-card save-data creation failed: "Format failed", and a crash
on first save in Soul Calibur 2 / Ratchet & Clank / GT4. Reproduced only with a
custom data folder, never with internal app storage.

A Java bridge for exactly this existed (NativeApp.createDirectoryPath plus the
FileSystem::CreateDirectoryViaJava JNI) but nothing called it after the monorepo
migration - the linker was dropping it as dead code. Wire it in as a fallback on
EPERM/EACCES, in both the flat and per-segment recursive paths.

Also adds folder-card import, which had no working route at all: a folder card is
a directory plus a _pcsx2_superblock marker, but the picker was OpenDocument()
(files only), so people zipped them and the importer appended ".ps2" to the
archive and copied it verbatim - producing a card the core read as unformatted.
Directories can now be imported directly, zips are unpacked, and both validate
the superblock instead of silently producing a broken card.

(cherry picked from commit 265ddb7657)
2026-07-19 21:04:34 -04:00
jpolo1224 3080080226 GS/Vulkan: restore the plain null-texture usage
The ROV-conditional null_usage rode along on a file copy from the development
tree; it is unrelated to this batch and was not meant to land.
2026-07-18 08:37:41 -04:00
jpolo1224 2948db9edf Android: ship the achievement sounds
The unlock / message / leaderboard-submit sounds were caught by the blanket
*.wav ignore, so the assets folder shipped empty from a clean clone even though
local builds had the files on disk. Add them and un-ignore that folder so future
sounds aren't dropped the same way.
2026-07-18 08:34:16 -04:00
jpolo1224 7422eb350e Android: pause button redesign, OSD defaults, patch manager fixes, display-resolution shaders
- Replace the in-game settings cog with a single top-right pause button (#357).
  Single tap opens the menu; it renders outside the auto-hide/"Never" gate so
  hiding the on-screen pad can no longer strand you without a way in.
- "Tap to reveal pause" replaces the old show/hide toggle, which could lock the
  menu away entirely. Migrates old layouts, including per-game and per-orientation.
- Run the RetroArch shader chain at the frame's on-screen size instead of the
  internal one, so CRT scanlines land at display pixel density. Sized to the
  aspect-corrected draw rect, since librashader maps input to the whole viewport.
- Patch manager: stop one game's patches showing under another, de-duplicate
  repeated cheats, split patches/cheats into collapsible sections, and drop the
  lag on large lists. Rename the widescreen toggle to say it auto-applies.
- On-Screen settings: the top slider drove OSD scale while labelled "UI Size" and
  shared a label key with the real UI slider. Renamed to "OSD Size" and grouped
  the three size controls together. OSD now defaults to 65%.
- Per-game graphics API, rotation and GPU driver.
- Gate the Adreno push-descriptor disable on driverID so 8 Elite keeps them.
- Load/save state slots no longer squash on the Load screen.
- Sync GameDB and fix two entries that parsed as no-ops: Genji's vu0ClampMode
  casing and DOA2's mis-indented minimumBlendingLevel.
2026-07-18 08:32:45 -04:00
jpolo1224 c2ee554b56 GS/Vulkan: gate the V3D feedback carry per-target, keep the draw-local note
Follow-up to #334. The carry sits inside a condition that only requires ONE of rt/ds to match the current target, so a draw that kept the RT but swapped the depth target could still inherit a stale depth feedback layout - the exact flicker mode the previous draw-local comment warned about. Gate each carry on its own target instead.

Also restore the non-Broadcom flicker warning, including the note that a vendor-scoped carry was tried and reverted once, so the global draw-local behaviour does not get removed again by mistake.

IsDeviceBroadcom now records which build actually reaches it (Linux arm64 / Raspberry Pi V3DV), since this is otherwise surprising in an Android-focused tree.
2026-07-16 05:50:04 -04:00
jpolo1224 7b033e7280 Android 2.6.7: gyro+stick combine, per-game BIOS, library online patches, ANGLE driver picker
- Gyro aim/steer now sums with the physical stick (coarse stick + fine gyro) instead of clobbering it

- Per-game stick invert/swap and D-pad-as-left-stick now scope per game (were global-only)

- Online patch browser reads the serial from the disc image, so it works from the library, not only in-game

- Per-game BIOS override, assignable from the library long-press, applied at boot with a global fallback

- ANGLE for OpenGL moved into the graphics-API driver picker, shown when OpenGL is selected (Render tab + in-game)

- Add WearyConcern1165/ExynosTools as a GPU driver download source
2026-07-15 17:04:58 -04:00
jpolo1224 f1d6360bc3 Android 2.6.4: Fast-Forward menu button, GPU driver recommendation, gyro aim-stick, cheats CRLF fix
- In-game menu: one-tap Fast-Forward in the Session tab (enables max speed + resumes; shows On state)
- Driver picker: show the detected GPU model + recommended driver source (Adreno 8xx/7xx/6xx map to a tuned Turnip pack, other GPUs to the built-in system driver) — appears in both the in-game menu and full Settings
- Gyro: Aim mode can now drive the Right or Left analog stick (per-game aware), for games that aim with the left stick such as Resident Evil 4
- Cheats: fix the per-cheat on/off toggle on PNACH files saved with CRLF/CR line endings (was failing with "unusual formatting")
2026-07-15 14:38:26 -04:00
jpolo1224 97d8c34fc5 Android 2.6.3: OSD reliability, hide games, custom RA sound, green Play button, Mali Vulkan speedup, ANGLE G77 fixes
- OSD: reload-immune visibility snapshot (fixes won't-turn-off); on/off hotkey now keeps the user's chosen stats
- Library: hide/unhide games; optional titles under shelf covers; Clear cached data in the App tab; refreshed drawer icons
- Per-game settings: fix scope stickiness after closing a game + open correctly from the shelf layout; green Play button that actually launches
- RetroAchievements: custom achievement-unlock sound picker
- Vulkan: re-enable attachment_feedback_loop_layout on Mali so accurate blending runs in-tile instead of the per-primitive barrier fallback (big speedup)
- ANGLE (GLES 3.1): emit GL_EXT_shader_io_blocks for interface blocks + alias glColorMaski to OES/EXT — fixes Mali-G77 black screen and mid-render crash
- In-game menu: Performance tab is now a yellow lightning icon
2026-07-14 23:17:40 -04:00
jpolo1224 add32e764c Android: fix ANGLE boot crash (null glDrawElementsBaseVertex on GLES 3.1 → alias to OES/EXT) + Jak 3 GameDB fixes (all regions) 2026-07-14 12:35:02 -04:00
jpolo1224 e833ef88c3 Android: re-add Setup / Change Folders to the library overflow menu (re-opens the setup wizard to change storage/disc folders) 2026-07-14 10:49:34 -04:00
jpolo1224 c305a647eb Android 2.6.1: fix controller remap regression, Black/OLED themes, Boot BIOS, menu scroll memory, RA file-type badge, sub-native resolution fix, achievement-sound reliability, GameDB (Tekken 4/5/Tag, Ar Tonelico 1&2, Aeon Flux, Musashi) 2026-07-14 10:40:16 -04:00
jpolo1224 9e0dc436f7 Android 2.6.0: ANGLE toggle, gyroscope, cheats/disc-swap, GameDB + fixes
- OpenGL-via-ANGLE renderer toggle for broken native GLES drivers, with a
  driver-keyed GL shader cache so switching drivers recompiles instead of
  feeding foreign program binaries to glProgramBinary (fixes Mali-G77 crash)
- Gyroscope input (aim/steering modes, sensitivity, smoothing, invert) shared
  between the Pad settings tab and the in-game Controls tab
- Re-add disc swap without closing the game, and per-cheat PNACH enable
- Xclipse GPU profile + Mali-G615 freeze gate; MediaTek Tekken 5 override
- GameDB: KH2, Tekken 5, Rumble Racing, MK Shaolin, Avatar
- Folder-reuse settings recovery (reverse-map INI + config mirror)
- In-game pause menu rail icons; make new settings searchable
- Resume/auto-load: wait for the renderer to present before restoring state
  and force a present after load (reduces black screen on resume)
- Boot crash guards (pad state before VM); FXAA + CAS sharpening
2026-07-14 02:34:14 -04:00
jpolo1224 917bc67865 CI: nightly rotation-signs + Discord changelog + timestamp versionCode
- ci-nightly-dualcore.sh signs nightlies with the release rotation lineage
  (debug<=API32 -> release>=API33) from repo secrets, so a nightly installs over
  the existing com.armsx2 build; falls back to a throwaway key (with a warning)
- versionCode = Unix seconds since 2023-11, monotonic and always above the manual
  10xx codes, so each nightly out-versions the last installed build
- versionName default 2.5.9 -> 2.6.0
- Discord announcement now includes the changelog (trimmed to Discord's limit)
2026-07-13 02:25:58 -04:00
jpolo1224 e2889838ab CI: dual-core (4k + 16k) + PGO nightly, push trigger, plain changelog
- tools/ci-nightly-dualcore.sh builds the core at both host page sizes and
  merges both .so into one APK so 16k-page devices load their native core
- nightly builds with PGO=optimize (committed pgo/armsx2.profdata)
- trigger the nightly on push to master (was schedule + dispatch only)
- plain-language changelog in the release body from filtered commit subjects
2026-07-13 00:42:11 -04:00
jpolo1224 6cfd8ee00b Android: settings scope toggle + search, BIOS scroll fix, UI polish
- Global/per-game settings scope toggle at the top of the settings rail,
  plus an All Settings shortcut at the bottom
- Settings search overlay + index
- BIOS manager: scrollable list so controller navigation reaches every entry
- Close-app entry with a confirm dialog
- i18n updates
2026-07-13 00:42:11 -04:00
jpolo1224 cfd50df201 GS: Mali/EmuCoreX renderer fixes + per-vendor mobile GPU profiles
- Mali Vulkan: drop the old blend-clamp band-aid; rely on dual_source_blend
  feature detection so only SRC1/blend-mix draws take the SW-blend path
  (no more forced Blending=Max on Mali)
- Fix intermittent stale-tile reads: input-attachment descriptor type for the
  texture-barrier feedback path, vendor-scoped to Mali
- broken_mad_deinterlace weave fallback for Mali-G57 FastMAD
- Add per-vendor mobile GPU profile system (Mali/Adreno/PowerVR)
- Tie test_and_sample_depth to texture_barrier (was forced on)
- Richer Vulkan device telemetry for field diagnosis
2026-07-13 00:42:11 -04:00