177 Commits
Author SHA1 Message Date
jpolo1224 4bf82b6df0 Touch editor: dock the panel away from the widget being edited
I left this out when porting the collapse toggle, on the grounds that ARMSX2
found docking alone did not answer the complaint. That was right about docking
being insufficient and wrong about it being unnecessary -- the two cover
different halves of the same problem. Collapse answers 'I cannot select what
is under you in the first place'; docking answers 'I have selected it and now
you are sitting on top of it'.

Selecting a widget in the top half moves the panel to the bottom, and back
again otherwise. Halves rather than real overlap maths, on purpose: a panel
that darts about as rectangles graze each other is less predictable than one
that is simply never on the side being worked on.

The stored drag offset means 'away from the anchored edge', so it flips sign
with the anchor. ARMSX3 anchors the panel top-centre and offsets downward, so
while docked to the bottom a +dy the user had nudged in would otherwise push
the panel straight off the screen.

Note for testing: this is only visible when selecting a widget in the TOP
half. Most on-screen controls live in the lower half, so selecting a face
button correctly leaves the panel where it is -- which is indistinguishable
from nothing happening. Use a shoulder button.
2026-08-24 17:03:07 -04:00
jpolo1224 03b5bde716 Sharpening in the in-game menu, named for the upscaler in use
The slider was missing from the in-game menu entirely -- it only existed in
the settings screen. That is the wrong way round: the quick menu is where the
upscaler gets changed mid-game, and having to leave the game to tune what you
just switched to defeats the point of it being there.

Shown only for FSR and SGSR, since nearest and bilinear have nothing to
sharpen.

The label follows the selection in both places, because the number does not
mean the same thing to each: it is an RCAS stop to FSR and an edge factor to
SGSR, and a slider named for the upscaler that is not running is simply
wrong. One control rather than two, deliberately -- both upscalers want the
same thing from the user, and a second slider only lets them disagree.
2026-08-24 16:55:56 -04:00
jpolo1224 81346ebb8b Touch editor: let the panel get out of the way
Ported from ARMSX2, which built it against a user complaint worth restating:
the panel covers the thing you are trying to edit, and the only remedy on
offer was to drag it away, every time.

ARMSX2's first answer was auto-docking the panel to the opposite half of the
screen from the selected widget. It works, and it did NOT fix the complaint --
selecting a widget under the panel means touching through the panel first, so
a reactive fix cannot help with an obstruction that happens strictly before
there is anything to react to.

What fixed it was a collapse toggle: one tap leaves the grip strip and
uncovers everything beneath it, one tap brings the controls back. It is first
in the grip row so it sits in the same place whether the panel is open or
shut, and it is neither persisted nor carried between sessions -- it is a
momentary 'let me see under this', and an editor that opened to a panel with
no controls on it would look broken.

Column is an inline composable, so the collapsed path returns out of it and
genuinely stops emitting the rest rather than drawing it invisibly.

Auto-dock is not ported. It is the half that demonstrably did not solve the
problem, it needs an anchor flip this panel does not have (top-anchored with a
downward offset, where ARMSX2 switches edges), and the offset sign trap that
comes with it is real. Worth revisiting only if collapse turns out to be
insufficient.
2026-08-24 16:48:02 -04:00
jpolo1224 f53a76c0fc SGSR upscaling
Snapdragon Game Super Resolution 1.0, mobile variant: a single-pass
edge-directed spatial upscaler Qualcomm wrote for Adreno. Against FSR1 it is
one dispatch instead of two and one target instead of two, which is what makes
it worth having on a phone -- cheaper, not better.

Licensing is the reason this is a reimplementation rather than a port.
Suggested by CamilleLaVey, who made the same filter work in Eden, but Eden's
glue is GPL-3.0-or-later and RPCS3 is GPL-2.0-ONLY, so none of it is used --
the same blocker that stopped the LSFG adoption, and permission cannot fix it
because Eden has other contributors. What IS used is Qualcomm's BSD-3-Clause
release, which is GPL-2.0 compatible, with the copyright notice kept. The crop
mapping and the widened sharpness range are reimplemented from a description
of what they do, which is not copyrightable.

Qualcomm ship it as a fragment shader over a fullscreen triangle; this is a
compute pass because that is what the VK device layer already schedules. The
interpolated texcoord becomes a UV from the invocation id and the fragment
output becomes an imageStore, with a bounds check because a dispatch rounds up
to whole workgroups.

The push constant offsets were read out of the compiled SPIR-V rather than
derived from the struct -- 0/8/16/24/32/40, 44 bytes -- because a mismatch
there produces garbage that looks exactly like a shader bug. glslc also
type-checks the GLSL, which the native build cannot: shaders here are compiled
at runtime, so a broken one builds fine and fails on device.

No vendor gate, deliberately: its requirements are a strict subset of FSR1's
(textureGather with a constant component and no offset, an rgba8 storage
image, one descriptor set), so anywhere FSR1 runs, this runs. It no-ops when
the frame is already at or above output resolution, which is correct and
indistinguishable from broken, so the setting text says so.

Wired at all five places the mode is represented: the enum (appended, never
inserted -- it is serialised by ordinal in savestates), the fmt_class_string
case (a missing one serialises as 'unknown' and the mode is silently never
selectable), the VK dispatch, both Kotlin pickers, and the persisted clamp
that would otherwise rewrite the new value straight back to FSR.
2026-08-24 16:46:30 -04:00
jpolo1224 2fc3566a7b Add the Thermals source file itself
It was written but never staged: 'commit -a' does not pick up an untracked
file, so the temperature feature went in as its wiring only -- the settings
row, the startup hook and the native push all referencing a class that is not
in the repository. Builds here kept working because the file exists on disk;
a fresh clone would not have compiled.
2026-08-24 16:09:04 -04:00
jpolo1224 8bc2da9d3f Android: 0.9.4.4 (versionCode 38)
Resumes the public version line after 0.9.4.3. The 0.9.4.4 through 0.9.5.7
bumps were internal test builds for the Xillia 2 hang hunt and were never
released; versionCode keeps climbing past them so a tester carrying one can
still update rather than having to uninstall.
2026-08-24 16:01:28 -04:00
jpolo1224 cd1fd0e585 Android: 0.9.5.7 (versionCode 37) 2026-08-24 15:37:35 -04:00
jpolo1224 bbf7520fc9 Android: 0.9.5.6 (versionCode 36) 2026-08-24 15:31:31 -04:00
jpolo1224 aa04822135 Android: 0.9.5.5 (versionCode 35) 2026-08-24 15:26:30 -04:00
jpolo1224 59dc158841 Android: 0.9.5.4 (versionCode 34) 2026-08-24 15:19:49 -04:00
jpolo1224 d1f7060c19 Android: 0.9.5.3 (versionCode 33) 2026-08-24 15:09:33 -04:00
jpolo1224 db38d6d737 Android: 0.9.5.2 (versionCode 32) 2026-08-24 15:02:59 -04:00
jpolo1224 595f8009cc Android: 0.9.5.1 (versionCode 31) 2026-08-24 14:56:10 -04:00
jpolo1224 bfc0b5d73e Android: 0.9.5.0 (versionCode 30) 2026-08-24 14:49:27 -04:00
jpolo1224 c3cc0c9687 Android: 0.9.4.9 (versionCode 29) 2026-08-24 14:35:17 -04:00
jpolo1224 f130141eb3 Android: 0.9.4.8 (versionCode 28) 2026-08-24 14:22:27 -04:00
jpolo1224 df14be53f7 Android: 0.9.4.7 (versionCode 27) 2026-08-24 13:33:52 -04:00
jpolo1224 6ab1a4b456 Show device temperatures on the performance overlay
Ported from ARMSX2. Android has no supported API for SoC temperatures --
HardwarePropertiesManager is gated behind the signature-level DEVICE_POWER --
so the only route is the thermal sysfs, which is readable without permission
on essentially every device but is not a contract: zone count, order, naming
and even the unit are all vendor-specific. So the app discovers zones once by
name, tolerates every failure by having no reading, and never displays a value
it could not actually read. A device that exposes nothing shows nothing, which
is a normal outcome rather than an error.

The core cannot read these and should not learn how, so the app pushes them in
through _rpcsx_setThermals into atomics beside the overlay. The overlay line is
appended after the detail-level switch, the same way the frame generation line
is, so all four levels get it without their format strings and positional
arguments having to agree.

The sentinel is -1000.0f on both sides. ARMSX2 spells the Kotlin half
Float.MIN_VALUE, which in Kotlin is the smallest POSITIVE float (1.4e-45) and
so arrives as a real temperature of 0 degrees rather than as absent; worth
correcting there too.

Default on, poll interval configurable 1-5s (default 2s) since sensor overhead
was the concern raised when this was asked for. No realtime option: a
temperature that moves slower than a second is not worth the syscalls.
2026-08-24 13:27:11 -04:00
jpolo1224 b9ed11da20 Drop fast-forward from the in-game menu and the second screen
It stays a feature -- the hotkey, the touch button and the OSD indicator are
untouched -- it just no longer occupies a tile in either panel.

Removing it also repairs the Session grid's controller dispatch, which was
broken. EmulationMenuViewModel indexes three things by the same number: the
grid in SessionPane, actionCount(), and activateSelection(). Fast-forward sat
at index 1 in the grid and had no entry in activateSelection, so every index
from 1 up dispatched to its neighbour -- a pad press on Fast Forward restarted
the game, Restart swapped the disc, Swap Disc closed the game, and Close could
not be activated at all. actionCount had been corrected to 5 to let the pad
reach the last tile, which made the misalignment reachable rather than fixing
it. With the tile gone the grid is resume/restart/swap/close, exactly what
activateSelection already dispatched, and actionCount goes to 4.

Touch users are unaffected: the grid is tapped by index through onSelect, so
it was always correct there. This only ever misfired for a controller.
2026-08-24 13:24:10 -04:00
jpolo1224 58ac171af5 Android: 0.9.4.6 (versionCode 26) 2026-08-24 12:38:37 -04:00
jpolo1224 dd373795bb Android: 0.9.4.5 (versionCode 25) 2026-08-24 11:43:52 -04:00
jpolo1224 851c3191f2 Android: 0.9.4.4 (versionCode 24) 2026-08-24 10:23:00 -04:00
jpolo1224 dca685b67d Android: 0.9.4.3 (versionCode 23) 2026-08-23 02:51:07 -04:00
jpolo1224 78650f32d8 Savers: always run gl1_init(), never trust a stale g_started
port_new opened with "if (g_started) return 1". That was defensible while
gl1's state was whatever the previous saver left behind, but nativeInit now
calls gl1_lost() before every create, so gl1 is guaranteed DOWN on entry.
Reporting success there would hand the caller a saver with no shim under it.

So the gl1_lost() added alongside the leak fix did not just close the leak,
it turned that early return from redundant into wrong -- its safety rested
on a call-graph property that same change removed. All six ports now tear
down a stale run through port_free and always run gl1_init(). Each needs a
forward declaration of port_free, which is defined below port_new.

No behaviour change on any path reachable today: port_free always clears
g_started, so the guard never fires. The point is that a saver added later,
or an upstream cleanup that returns early, should fail in its own saver
rather than poison the next one -- the same reason port_free tears gl1 down
unconditionally.

Adapted from the ARMSX2 change (be674a64c5).
2026-08-23 02:44:04 -04:00
jpolo1224 24951bb863 Fix animated background locking users out of the app
Ported from the ARMSX2 fix; ARMSX3 had the same bug, plus a sixth saver.

A saver that dies natively made the app unlaunchable. The choice is a
persisted pref read on the library screen -- the first screen -- so the
crash repeated on every launch and Settings was never reachable to turn it
off. The only escape was clearing app data, which takes memory cards and
save states with it.

Cause: gl1's state is a file-scope global holding GL object names, and
gl1_init() early-returns on g.ready. Skyrocket and Lattice defer initSaver()
to port_resize, so a create-then-teardown with no surface size left
g_started false and their port_free returned BEFORE gl1_shutdown(); flux,
plasma and solarwinds leaked it the other way, since returning 0 when
initSaver() leaves readyToDraw clear means port_free is never called at all.
Either way g.ready stayed set with names from a destroyed EGL context, and
the next saver -- new view, new context -- drew against them. Drivers answer
that with anything from a black screen to a segfault.

Every port now gives gl1 back on every exit, unconditionally, so the rule
holds by construction rather than by arguing about which paths are
reachable. gl1_lost() already existed here but was never called; nativeInit
calls it now as the standing invariant, so a new context cannot inherit old
GL names even if a saver added later forgets.

Containment, independent of the fix: native GL can always find a new way to
die, so the setting arms itself with a synchronous commit() before the
render thread starts and disarms when that thread exits in an orderly way.
Still armed at startup means the last run died with a saver up -- the
background switches off and a toast names it. A crash or a kill cannot reach
the disarm; that is the entire signal.

Also guards Thread.start(): it asks for a 16MB stack (Skyrocket declares a
3MB starmap as a local) and an OutOfMemoryError there is an uncaught throw
on the main thread -- the same lockout with no native crash involved.
2026-08-23 02:15:41 -04:00