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.
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.
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.
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.
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.
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.
There was exactly one previous log: RPCSX.log became RPCSX.old.log and the
previous old was deleted. That loses the capture reliably, because of how
people send it -- play, stop, relaunch the app to reach the file, and the
relaunch rotates the wanted session into .old; relaunch once more, to find it
or to share it or because the launcher restored the app, and it is gone.
Three captures have been lost this way (#87, #91, and the Call of Duty audio
one). In two of them what arrived was a 47-line log ending before the game had
booted, which is the replacement session rather than the one played.
Two defences, because generations alone would not have saved those:
1. A session that produced almost nothing does not get a slot. The logs that
did the damage were boot-only -- a few KB, stopping within a second of
launch -- and pushing one of those down the chain is what evicted the real
capture. Below the threshold the file is discarded instead. A session with
'Silence All Logs' on still writes far more than this (~190 KB for a
29-minute one), so that setting does not trip it.
2. Three generations instead of one, so an ordinary mistake costs nothing.
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.
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.
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).