From b9ed11da208d43f97509f3d9cc5fc8872dc05489 Mon Sep 17 00:00:00 2001 From: jpolo1224 Date: Mon, 24 Aug 2026 13:24:10 -0400 Subject: [PATCH] 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. --- .../armsx3-ui/app/src/main/cpp/native-lib.cpp | 14 ++++++ .../src/main/java/com/armsx2/SecondScreen.kt | 3 -- .../ui/emulation/EmulationMenuScreen.kt | 6 --- .../ui/emulation/EmulationMenuViewModel.kt | 9 ++-- .../app/src/main/java/net/rpcsx/RPCSX.kt | 7 +++ android/src/rpcsx-android.cpp | 10 +++++ .../Emu/RSX/Overlays/overlay_perf_metrics.cpp | 45 +++++++++++++++++++ rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.h | 28 ++++++++++++ 8 files changed, 110 insertions(+), 12 deletions(-) diff --git a/android/armsx3-ui/app/src/main/cpp/native-lib.cpp b/android/armsx3-ui/app/src/main/cpp/native-lib.cpp index cd69be98a..5fbb277b8 100644 --- a/android/armsx3-ui/app/src/main/cpp/native-lib.cpp +++ b/android/armsx3-ui/app/src/main/cpp/native-lib.cpp @@ -47,6 +47,7 @@ struct RPCSXApi { void (*surfaceSizeChanged)(int width, int height); void (*setPadSensor)(int port, int x, int y, int z, int g); int (*getPadRumble)(int port); + void (*setThermals)(float cpu, float gpu, float battery, bool show); bool (*usbDeviceEvent)(int fd, int vendorId, int productId, int event); bool (*installFw)(JNIEnv *env, int fd, long progressId); bool (*isInstallableFile)(jint fd); @@ -161,6 +162,7 @@ struct RPCSXLibrary : RPCSXApi { result.surfaceSizeChanged = reinterpret_cast(dlsym(handle, "_rpcsx_surfaceSizeChanged")); result.setPadSensor = reinterpret_cast(dlsym(handle, "_rpcsx_setPadSensor")); result.getPadRumble = reinterpret_cast(dlsym(handle, "_rpcsx_getPadRumble")); + result.setThermals = reinterpret_cast(dlsym(handle, "_rpcsx_setThermals")); result.usbDeviceEvent = reinterpret_cast(dlsym(handle, "_rpcsx_usbDeviceEvent")); result.installFw = reinterpret_cast(dlsym(handle, "_rpcsx_installFw")); result.isInstallableFile = reinterpret_cast(dlsym(handle, "_rpcsx_isInstallableFile")); @@ -506,6 +508,18 @@ extern "C" JNIEXPORT jint JNICALL Java_net_rpcsx_RPCSX_getPadRumble( return rpcsxLib.getPadRumble(port); } +// Device temperatures for the perf overlay. Discovery is the app's job -- Android exposes no +// supported API for SoC temperatures, so it reads the thermal sysfs, whose zone naming and units +// are vendor-specific -- and this only carries the result across. +extern "C" JNIEXPORT void JNICALL Java_net_rpcsx_RPCSX_setThermals( + JNIEnv *, jobject, jfloat cpu, jfloat gpu, jfloat battery, jboolean show) { + if (rpcsxLib.setThermals == nullptr) { + return; + } + + rpcsxLib.setThermals(cpu, gpu, battery, show == JNI_TRUE); +} + extern "C" JNIEXPORT void JNICALL Java_net_rpcsx_RPCSX_surfaceSizeChanged( JNIEnv *, jobject, jint width, jint height) { if (rpcsxLib.surfaceSizeChanged == nullptr) { diff --git a/android/armsx3-ui/app/src/main/java/com/armsx2/SecondScreen.kt b/android/armsx3-ui/app/src/main/java/com/armsx2/SecondScreen.kt index b435fc89f..67b531362 100644 --- a/android/armsx3-ui/app/src/main/java/com/armsx2/SecondScreen.kt +++ b/android/armsx3-ui/app/src/main/java/com/armsx2/SecondScreen.kt @@ -216,9 +216,6 @@ object SecondScreen { row1.addView(action(I18n.get("touch.stateAction.load")) { MainActivityRuntime.instance?.loadState() }, rowLp()) - row1.addView(action(I18n.get("secondScreen.fastForward")) { - MainActivityRuntime.instance?.toggleFastForward() - }, rowLp()) row2.addView(action(I18n.get("secondScreen.pause")) { // Same toggle the on-screen pause button uses. if (MainActivityRuntime.eState.value == EmuState.PAUSED) MainActivityRuntime.resume() diff --git a/android/armsx3-ui/app/src/main/java/com/armsx2/ui/emulation/EmulationMenuScreen.kt b/android/armsx3-ui/app/src/main/java/com/armsx2/ui/emulation/EmulationMenuScreen.kt index c2eb83f79..e7ae6184b 100644 --- a/android/armsx3-ui/app/src/main/java/com/armsx2/ui/emulation/EmulationMenuScreen.kt +++ b/android/armsx3-ui/app/src/main/java/com/armsx2/ui/emulation/EmulationMenuScreen.kt @@ -636,12 +636,6 @@ private fun SessionPane(state: EmulationMenuUiState, viewModel: EmulationMenuVie ActionGrid( actions = listOf( MenuAction(str("action.resume"), str("action.play"), "▶", Success, viewModel::resume), - MenuAction( - str("action.fastForward"), - if (MainActivityRuntime.fastForwardToggleActive) str("action.fastForward.on") else str("action.fastForward.detail"), - "⏩", - if (MainActivityRuntime.fastForwardToggleActive) Success else null, - ) { MainActivityRuntime.instance?.toggleFastForward(); viewModel.resume() }, MenuAction(str("memcard.restart"), str("action.reset"), "↻", null, MainActivityRuntime::restart), MenuAction(str("action.swapDisc"), str("action.swapDisc.detail"), "⏏", null, MainActivityRuntime::promptSwapDisc), MenuAction(str("action.close"), MainActivityRuntime.currentGame.value?.title.orEmpty(), "■", Danger) { diff --git a/android/armsx3-ui/app/src/main/java/com/armsx2/ui/emulation/EmulationMenuViewModel.kt b/android/armsx3-ui/app/src/main/java/com/armsx2/ui/emulation/EmulationMenuViewModel.kt index 73bef8d12..2259efed8 100644 --- a/android/armsx3-ui/app/src/main/java/com/armsx2/ui/emulation/EmulationMenuViewModel.kt +++ b/android/armsx3-ui/app/src/main/java/com/armsx2/ui/emulation/EmulationMenuViewModel.kt @@ -381,9 +381,12 @@ class EmulationMenuViewModel(application: Application) : AndroidViewModel(applic } private fun actionCount(tab: EmulationMenuTab): Int = when (tab) { - // MUST match SessionPane's action list length. This was 4 against a list of 5, so the pad - // could never reach Close at all. - EmulationMenuTab.Session -> 5 + // MUST match SessionPane's action list length, AND activateSelection's Session branch + // below -- all three are indexed by the same number and nothing checks they agree. + // Fast-forward used to sit at index 1 in the grid but had no entry in activateSelection, + // so every index from 1 up dispatched to its neighbour: a pad press on Fast Forward + // restarted the game, and Close could not be activated at all. + EmulationMenuTab.Session -> 4 EmulationMenuTab.Graphics -> 2 EmulationMenuTab.Fixes -> 0 EmulationMenuTab.Performance -> 3 diff --git a/android/armsx3-ui/app/src/main/java/net/rpcsx/RPCSX.kt b/android/armsx3-ui/app/src/main/java/net/rpcsx/RPCSX.kt index bc29097ba..fc8b17a6d 100644 --- a/android/armsx3-ui/app/src/main/java/net/rpcsx/RPCSX.kt +++ b/android/armsx3-ui/app/src/main/java/net/rpcsx/RPCSX.kt @@ -111,6 +111,13 @@ class RPCSX { /** What the game is asking the rumble motors to do: (large shl 8) or small, each 0..255. */ external fun getPadRumble(port: Int): Int + + /** + * Device temperatures for the perf overlay, in degrees Celsius, or Thermals.NONE for a + * reading that could not be taken. Discovery is the app's job -- Android has no supported + * API for SoC temperatures -- so the core is only ever told the answer. + */ + external fun setThermals(cpu: Float, gpu: Float, battery: Float, show: Boolean) external fun usbDeviceEvent(fd: Int, vendorId: Int, productId: Int, event: Int): Boolean external fun processCompilationQueue(): Boolean external fun startMainThreadProcessor(): Boolean diff --git a/android/src/rpcsx-android.cpp b/android/src/rpcsx-android.cpp index 6fbc215d0..a7d5f7e97 100644 --- a/android/src/rpcsx-android.cpp +++ b/android/src/rpcsx-android.cpp @@ -19,6 +19,7 @@ #include "Emu/Io/pad_config_types.h" #include "Emu/RSX/Null/NullGSRender.h" #include "Emu/RSX/Overlays/overlay_manager.h" +#include "Emu/RSX/Overlays/overlay_perf_metrics.h" #include "Emu/RSX/Overlays/overlay_save_dialog.h" #include "Emu/RSX/Overlays/overlay_trophy_notification.h" #include "Emu/RSX/Overlays/overlay_utils.h" @@ -4307,6 +4308,15 @@ extern "C" void _rpcsx_setPadSensor(int port, int x, int y, int z, int g) { // whenever it likes, and there is no notification to hook. The caller reads it on a // timer and drives the phone's vibrator. Returns 0 when nothing is running, so a // caller that keeps polling after the game stops simply sees silence. +// Device temperatures, pushed from the app layer -- see the note in overlay_perf_metrics.h for +// why discovery lives there and not here. Values are degrees Celsius, or the 'none' sentinel. +extern "C" void _rpcsx_setThermals(float cpu, float gpu, float battery, bool show) { + rsx::overlays::thermals::g_cpu = cpu; + rsx::overlays::thermals::g_gpu = gpu; + rsx::overlays::thermals::g_battery = battery; + rsx::overlays::thermals::g_show = show; +} + extern "C" int _rpcsx_getPadRumble(int port) { std::lock_guard lock(g_virtual_pad_mutex); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp b/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp index da644f145..8ca290789 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp @@ -640,6 +640,15 @@ namespace rsx fmt::append(perf_text, "%s%s", perf_text.empty() ? "" : "\n", lsfg); } + // Device temperatures, for the same reason and in the same place: appended after + // the switch so every detail level gets them without four format strings and + // their positional arguments having to agree. Empty when the option is off or no + // zone was readable, so the overlay is unchanged for anyone it cannot serve. + if (const std::string temps = thermals::status_text(); !temps.empty()) + { + fmt::append(perf_text, "%s%s", perf_text.empty() ? "" : "\n", temps); + } + m_body.set_text(perf_text); if (perf_text.empty()) @@ -940,6 +949,42 @@ namespace rsx return compiled_resources; } + namespace thermals + { + atomic_t g_cpu{none}; + atomic_t g_gpu{none}; + atomic_t g_battery{none}; + atomic_t g_show{false}; + + std::string status_text() + { + if (!g_show) + { + return {}; + } + + std::string out; + + // Whole degrees. A tenth is noise on a sysfs zone that updates every couple of + // seconds, and this is read at a glance. + const auto add = [&out](const char* label, f32 c) + { + if (c <= none) + { + return; + } + + fmt::append(out, "%s%s %d\u00b0C", out.empty() ? "" : " ", label, static_cast(c)); + }; + + add("CPU", g_cpu); + add("GPU", g_gpu); + add("BAT", g_battery); + + return out; + } + } + extern void reset_performance_overlay() { if (!g_cfg.misc.use_native_interface) diff --git a/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.h b/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.h index 2b676e591..9ac640dc9 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.h @@ -113,5 +113,33 @@ namespace rsx }; void reset_performance_overlay(); + + // Device temperatures, pushed in from the Android app layer. + // + // The core cannot read these and should not learn how: 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, whose zone naming, ordering and + // even units are vendor-specific. That discovery belongs in the app, which already deals + // in Android specifics; this side just holds what it was told and prints it. + // + // Atomic because the writer is a UI-thread poll and the reader is the render thread. + // Relaxed is fine: three independent display values with no ordering relationship to each + // other or to anything else, and the worst a torn read can do is show one stale number + // for one frame. + namespace thermals + { + // Below any real temperature, so one comparison separates "could not read" from + // "cold" without carrying a second flag per value. A device with no readable zone is + // a normal outcome, not an error. + constexpr f32 none = -1000.0f; + + extern atomic_t g_cpu; + extern atomic_t g_gpu; + extern atomic_t g_battery; + extern atomic_t g_show; + + // "45°C GPU 43°C BAT 31°C", or empty when off or nothing is readable. + std::string status_text(); + } } }