mirror of
https://github.com/ARMSX2/ARMSX3.git
synced 2026-08-24 16:58:52 -07:00
0.7: SPU and RSX fixes, Oboe audio, and the ports from ouroboros420 and rfandango
SPU: the ARM64 block checksum folded two thirds of every block through absolute difference, which is not injective, so adding the same value to two words left the checksum unchanged and similar job binaries hashed alike. Plain summation now. This is what Precise SPU Verification was working around, and that setting is exposed properly instead of only being reachable by hand editing the config. SPU: a block is no longer marked permanently failed when the trampoline rebuild fails. The compiled function was live, the state was not recoverable for the rest of the session, and the claim could never be retaken. RSX: render pass churn cut in heavy scenes, roughly 113 to 85 passes per frame. On a tile based GPU every pass boundary is a full tile store and reload. Two Vulkan specification violations fixed, and a read/write hazard on the render pass path. RSX: the FIFO no longer burns a core on sched_yield while idle. Android: ADPF is implemented rather than an inert setting, logcat no longer allocates and makes an IPC call per line, and Silence All Logs is available for playable titles. Audio: Oboe backend, for the per device quirks database and stream recovery on disconnect and route change. Ported from ouroboros420/rpcsx: GPU Turbo, power and thermal handling, the crash and freeze fixes, savestate and WSI surface lifetime, honest RAM VRAM budgeting, the persistent SPU object cache design, occlusion query and RSX fixes, frame pacing and tiler tuning. Ported from rfandango/rpcsx: the Turnip ZCULL deadlock fix and ARM64 SPU checksum handling. Individual commits are credited in comments at each site.
This commit is contained in:
@@ -112,3 +112,6 @@
|
|||||||
path = 3rdparty/protobuf/protobuf
|
path = 3rdparty/protobuf/protobuf
|
||||||
url = ../../protocolbuffers/protobuf.git
|
url = ../../protocolbuffers/protobuf.git
|
||||||
ignore = dirty
|
ignore = dirty
|
||||||
|
[submodule "3rdparty/oboe/oboe"]
|
||||||
|
path = 3rdparty/oboe/oboe
|
||||||
|
url = https://github.com/google/oboe.git
|
||||||
|
|||||||
Vendored
+6
@@ -141,6 +141,12 @@ else()
|
|||||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Oboe (Android only)
|
||||||
|
if(ANDROID)
|
||||||
|
message(STATUS "Using static oboe from 3rdparty")
|
||||||
|
add_subdirectory(oboe EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
|
|
||||||
# SoundTouch
|
# SoundTouch
|
||||||
add_subdirectory(SoundTouch EXCLUDE_FROM_ALL)
|
add_subdirectory(SoundTouch EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
|||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# Oboe
|
||||||
|
#
|
||||||
|
# Android-only. Oboe wraps AAudio (and OpenSL ES on older devices) and carries a
|
||||||
|
# per-device quirks database plus stream-restart handling, which is the part that
|
||||||
|
# matters on the low-end parts where plain AAudio glitches.
|
||||||
|
|
||||||
|
add_subdirectory(oboe EXCLUDE_FROM_ALL)
|
||||||
|
add_library(3rdparty::oboe ALIAS oboe)
|
||||||
+1
Submodule 3rdparty/oboe/oboe added at 0da326e4ef
+34
-3
@@ -1,3 +1,4 @@
|
|||||||
|
#include <cerrno>
|
||||||
#include "File.h"
|
#include "File.h"
|
||||||
#include "mutex.h"
|
#include "mutex.h"
|
||||||
#include "StrFmt.h"
|
#include "StrFmt.h"
|
||||||
@@ -684,8 +685,20 @@ namespace fs
|
|||||||
u64 result = 0;
|
u64 result = 0;
|
||||||
|
|
||||||
// Loop because (huge?) read can be processed partially
|
// Loop because (huge?) read can be processed partially
|
||||||
while (auto r = ::read(m_fd, buffer, count))
|
for (;;)
|
||||||
{
|
{
|
||||||
|
const auto r = ::read(m_fd, buffer, count);
|
||||||
|
|
||||||
|
// EINTR is benign -- a signal landed mid-syscall -- and must be retried, not treated
|
||||||
|
// as failure. Android app storage is FUSE-backed, where this genuinely happens, and
|
||||||
|
// the ensure() below turns it into a process abort. Ported in spirit from
|
||||||
|
// ouroboros420/rpcsx (92144f094).
|
||||||
|
if (r < 0 && errno == EINTR)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r) break; // EOF
|
||||||
ensure(r > 0); // "file::read"
|
ensure(r > 0); // "file::read"
|
||||||
count -= r;
|
count -= r;
|
||||||
result += r;
|
result += r;
|
||||||
@@ -702,8 +715,17 @@ namespace fs
|
|||||||
u64 result = 0;
|
u64 result = 0;
|
||||||
|
|
||||||
// For safety; see read()
|
// For safety; see read()
|
||||||
while (auto r = ::pread(m_fd, buffer, count, offset))
|
for (;;)
|
||||||
{
|
{
|
||||||
|
const auto r = ::pread(m_fd, buffer, count, offset);
|
||||||
|
|
||||||
|
// See read(): retry EINTR rather than aborting.
|
||||||
|
if (r < 0 && errno == EINTR)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r) break; // EOF
|
||||||
ensure(r > 0); // "file::read_at"
|
ensure(r > 0); // "file::read_at"
|
||||||
count -= r;
|
count -= r;
|
||||||
offset += r;
|
offset += r;
|
||||||
@@ -721,8 +743,17 @@ namespace fs
|
|||||||
u64 result = 0;
|
u64 result = 0;
|
||||||
|
|
||||||
// For safety; see read()
|
// For safety; see read()
|
||||||
while (auto r = ::write(m_fd, buffer, count))
|
for (;;)
|
||||||
{
|
{
|
||||||
|
const auto r = ::write(m_fd, buffer, count);
|
||||||
|
|
||||||
|
// See read(): retry EINTR rather than aborting.
|
||||||
|
if (r < 0 && errno == EINTR)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r) break;
|
||||||
ensure(r > 0); // "file::write"
|
ensure(r > 0); // "file::write"
|
||||||
count -= r;
|
count -= r;
|
||||||
result += r;
|
result += r;
|
||||||
|
|||||||
@@ -618,6 +618,22 @@ std::string jit_compiler::cpu(std::string_view _cpu)
|
|||||||
m_cpu = fallback_cpu_detection();
|
m_cpu = fallback_cpu_detection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ARCH_ARM64
|
||||||
|
// Detection reads the MIDR of whichever core happens to be running, so on big.LITTLE it
|
||||||
|
// can name a small in-order core. JIT'd code runs on every core, so scheduling for the
|
||||||
|
// smallest one is the wrong default -- fall back to the same wide out-of-order baseline
|
||||||
|
// used when detection fails outright. Only the schedule/cost model is affected: the
|
||||||
|
// instruction set still comes from setMAttrs (HWCAP-gated), so this can never emit an
|
||||||
|
// illegal instruction. Ported from ouroboros420/rpcsx (cc3a18e29), widened to the
|
||||||
|
// A5xx little cores that modern SoCs actually ship.
|
||||||
|
if (m_cpu == "cortex-a34" || m_cpu == "cortex-a35" || m_cpu == "cortex-a53" ||
|
||||||
|
m_cpu == "cortex-a55" || m_cpu == "cortex-a510" || m_cpu == "cortex-a520")
|
||||||
|
{
|
||||||
|
jit_log.notice("CPU detection named a little core ('%s'); using cortex-a78 as the schedule baseline.", m_cpu);
|
||||||
|
m_cpu = "cortex-a78";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (m_cpu == "sandybridge" ||
|
if (m_cpu == "sandybridge" ||
|
||||||
m_cpu == "ivybridge" ||
|
m_cpu == "ivybridge" ||
|
||||||
m_cpu == "haswell" ||
|
m_cpu == "haswell" ||
|
||||||
@@ -754,6 +770,29 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, st
|
|||||||
fmt::throw_exception("LLVM Emergency Exit Invoked: '%s'", out);
|
fmt::throw_exception("LLVM Emergency Exit Invoked: '%s'", out);
|
||||||
}, nullptr);
|
}, nullptr);
|
||||||
|
|
||||||
|
// A separate handler from the fatal one -- LLVM installs and dispatches the two
|
||||||
|
// independently. Without this, an allocation failure inside LLVM (SmallVector growth
|
||||||
|
// while codegenning the enormous PPU symbol-resolver module, say) writes "LLVM ERROR:
|
||||||
|
// out of memory" to fd 2 -- which goes nowhere in an Android app -- and calls abort():
|
||||||
|
// a signal-6 death with nothing whatsoever in the log. Route it through the same
|
||||||
|
// recoverable path as the fatal handler, so a guarded compile survives and anything
|
||||||
|
// else at least says why it died. Allocating inside a bad-alloc handler is
|
||||||
|
// best-effort, but the failures here are huge single allocations, so a short log
|
||||||
|
// string still succeeds. Ported from ouroboros420/rpcsx (39a6a4c36).
|
||||||
|
llvm::remove_bad_alloc_error_handler();
|
||||||
|
llvm::install_bad_alloc_error_handler([](void*, const char* msg, bool)
|
||||||
|
{
|
||||||
|
const std::string_view out = msg ? msg : "";
|
||||||
|
|
||||||
|
if (g_llvm_fatal_message)
|
||||||
|
{
|
||||||
|
*g_llvm_fatal_message = out;
|
||||||
|
thread_ctrl::silent_exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt::throw_exception("LLVM Out Of Memory: '%s'", out);
|
||||||
|
}, nullptr);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
|
|||||||
@@ -2746,6 +2746,20 @@ const bool s_terminate_handler_set = []() -> bool
|
|||||||
{
|
{
|
||||||
std::set_terminate([]()
|
std::set_terminate([]()
|
||||||
{
|
{
|
||||||
|
// Re-entrancy guard. Under memory exhaustion the terminate path itself allocates
|
||||||
|
// (report_fatal_error formats a message -> operator new; with -fno-exceptions a failed
|
||||||
|
// allocation calls std::terminate again), which recurses forever and buries the real
|
||||||
|
// crash under a stack of aborts. If terminate re-enters, hard-stop without allocating
|
||||||
|
// so there is exactly one clean tombstone.
|
||||||
|
// Ported from ouroboros420/rpcsx (281654906).
|
||||||
|
static atomic_t<int> s_terminating{0};
|
||||||
|
|
||||||
|
if (s_terminating.exchange(1) != 0)
|
||||||
|
{
|
||||||
|
::signal(SIGABRT, SIG_DFL);
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
if (IsDebuggerPresent())
|
if (IsDebuggerPresent())
|
||||||
{
|
{
|
||||||
logs::listener::sync_all();
|
logs::listener::sync_all();
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ android {
|
|||||||
applicationId = "com.armsx3"
|
applicationId = "com.armsx3"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 37
|
targetSdk = 37
|
||||||
versionCode = 10
|
versionCode = 11
|
||||||
versionName = "0.6"
|
versionName = "0.7"
|
||||||
|
|
||||||
// ARMSX2's UI reads these. STORAGE_ALL_FILES gates the all-files storage path in
|
// ARMSX2's UI reads these. STORAGE_ALL_FILES gates the all-files storage path in
|
||||||
// onboarding; IN_APP_UPDATER gates the in-app GitHub-release updater.
|
// onboarding; IN_APP_UPDATER gates the in-app GitHub-release updater.
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ struct RPCSXApi {
|
|||||||
void (*pause)();
|
void (*pause)();
|
||||||
void (*openHomeMenu)();
|
void (*openHomeMenu)();
|
||||||
std::string (*getTitleId)();
|
std::string (*getTitleId)();
|
||||||
|
unsigned long long (*getFramePeriodNs)();
|
||||||
|
unsigned long long (*getFrameWorkNs)();
|
||||||
|
int (*getRsxThreadTid)();
|
||||||
std::string (*getCurrentTrophyName)();
|
std::string (*getCurrentTrophyName)();
|
||||||
bool (*surfaceEvent)(JNIEnv *env, jobject surface, jint event);
|
bool (*surfaceEvent)(JNIEnv *env, jobject surface, jint event);
|
||||||
void (*surfaceSizeChanged)(int width, int height);
|
void (*surfaceSizeChanged)(int width, int height);
|
||||||
@@ -127,6 +130,9 @@ struct RPCSXLibrary : RPCSXApi {
|
|||||||
result.pause = reinterpret_cast<decltype(pause)>(dlsym(handle, "_rpcsx_pause"));
|
result.pause = reinterpret_cast<decltype(pause)>(dlsym(handle, "_rpcsx_pause"));
|
||||||
result.openHomeMenu = reinterpret_cast<decltype(openHomeMenu)>(dlsym(handle, "_rpcsx_openHomeMenu"));
|
result.openHomeMenu = reinterpret_cast<decltype(openHomeMenu)>(dlsym(handle, "_rpcsx_openHomeMenu"));
|
||||||
result.getTitleId = reinterpret_cast<decltype(getTitleId)>(dlsym(handle, "_rpcsx_getTitleId"));
|
result.getTitleId = reinterpret_cast<decltype(getTitleId)>(dlsym(handle, "_rpcsx_getTitleId"));
|
||||||
|
result.getFramePeriodNs = reinterpret_cast<decltype(getFramePeriodNs)>(dlsym(handle, "_rpcsx_getFramePeriodNs"));
|
||||||
|
result.getFrameWorkNs = reinterpret_cast<decltype(getFrameWorkNs)>(dlsym(handle, "_rpcsx_getFrameWorkNs"));
|
||||||
|
result.getRsxThreadTid = reinterpret_cast<decltype(getRsxThreadTid)>(dlsym(handle, "_rpcsx_getRsxThreadTid"));
|
||||||
result.getCurrentTrophyName = reinterpret_cast<decltype(getCurrentTrophyName)>(dlsym(handle, "_rpcsx_getCurrentTrophyName"));
|
result.getCurrentTrophyName = reinterpret_cast<decltype(getCurrentTrophyName)>(dlsym(handle, "_rpcsx_getCurrentTrophyName"));
|
||||||
result.surfaceEvent = reinterpret_cast<decltype(surfaceEvent)>(dlsym(handle, "_rpcsx_surfaceEvent"));
|
result.surfaceEvent = reinterpret_cast<decltype(surfaceEvent)>(dlsym(handle, "_rpcsx_surfaceEvent"));
|
||||||
result.surfaceSizeChanged = reinterpret_cast<decltype(surfaceSizeChanged)>(dlsym(handle, "_rpcsx_surfaceSizeChanged"));
|
result.surfaceSizeChanged = reinterpret_cast<decltype(surfaceSizeChanged)>(dlsym(handle, "_rpcsx_surfaceSizeChanged"));
|
||||||
@@ -614,6 +620,26 @@ Java_net_rpcsx_RPCSX_supportsCustomDriverLoading(JNIEnv *env,
|
|||||||
return access("/dev/kgsl-3d0", F_OK) == 0;
|
return access("/dev/kgsl-3d0", F_OK) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force the Adreno GPU to its maximum clocks, or release it back to normal scaling.
|
||||||
|
//
|
||||||
|
// Adreno's DVFS ramps clocks up only after it has already seen load, so a scene that suddenly
|
||||||
|
// becomes GPU-bound stutters through the ramp every time it happens. Pinning the clocks removes
|
||||||
|
// that at the cost of heat and battery, which is why it is opt-in rather than a default.
|
||||||
|
//
|
||||||
|
// Nothing here talks to the emulator core: adrenotools is linked into this JNI library, and
|
||||||
|
// adrenotools_set_turbo opens /dev/kgsl-3d0 itself and silently does nothing on non-Adreno
|
||||||
|
// hardware or if the open fails. So it is safe to call unconditionally, including before the core
|
||||||
|
// is loaded. Ported from the RPCSX Android fork, which ships it default-off; kept default-off here
|
||||||
|
// for the same reason.
|
||||||
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
Java_net_rpcsx_RPCSX_setGpuTurbo(JNIEnv *, jobject, jboolean on) {
|
||||||
|
#if defined(__aarch64__)
|
||||||
|
adrenotools_set_turbo(on == JNI_TRUE);
|
||||||
|
#else
|
||||||
|
(void) on;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jstring JNICALL
|
extern "C" JNIEXPORT jstring JNICALL
|
||||||
Java_net_rpcsx_RPCSX_getVersion(JNIEnv *env, jobject) {
|
Java_net_rpcsx_RPCSX_getVersion(JNIEnv *env, jobject) {
|
||||||
// The core is dlopen()ed separately and may not be up yet -- during
|
// The core is dlopen()ed separately and may not be up yet -- during
|
||||||
@@ -959,3 +985,29 @@ Java_net_rpcsx_RPCSX_patchSetEnabled(JNIEnv *env, jobject, jstring jhash,
|
|||||||
unwrap(env, jserial),
|
unwrap(env, jserial),
|
||||||
unwrap(env, jappVersion), enabled);
|
unwrap(env, jappVersion), enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ADPF telemetry. All three return 0 when unmeasured or on a core too old to export them,
|
||||||
|
// and the Kotlin side treats 0 as "skip this update" rather than feeding the OS a bogus hint.
|
||||||
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
|
Java_net_rpcsx_RPCSX_getFramePeriodNs(JNIEnv *, jobject) {
|
||||||
|
if (rpcsxLib.getFramePeriodNs == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<jlong>(rpcsxLib.getFramePeriodNs());
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
|
Java_net_rpcsx_RPCSX_getFrameWorkNs(JNIEnv *, jobject) {
|
||||||
|
if (rpcsxLib.getFrameWorkNs == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<jlong>(rpcsxLib.getFrameWorkNs());
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
|
Java_net_rpcsx_RPCSX_getRsxThreadTid(JNIEnv *, jobject) {
|
||||||
|
if (rpcsxLib.getRsxThreadTid == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<jint>(rpcsxLib.getRsxThreadTid());
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,6 +100,28 @@ object Ps3PatchRepo {
|
|||||||
return if (n >= 0) Result.Ok(n) else Result.Parse
|
return if (n >= 0) Result.Ok(n) else Result.Parse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a patch.yml the user picked themselves.
|
||||||
|
*
|
||||||
|
* No checksum here, unlike [download]: there is no publisher digest to compare a
|
||||||
|
* local file against, and the user choosing the file IS the trust decision. The
|
||||||
|
* core still parses it, so a malformed file is rejected rather than half-applied.
|
||||||
|
*
|
||||||
|
* Merges into patches/patch.yml like every other import, so a hand-added patch
|
||||||
|
* sits alongside the downloaded database instead of replacing it.
|
||||||
|
*/
|
||||||
|
fun importLocal(context: Context, uri: android.net.Uri): Result {
|
||||||
|
val yaml = runCatching {
|
||||||
|
context.contentResolver.openInputStream(uri)?.use { stream ->
|
||||||
|
stream.bufferedReader().readText()
|
||||||
|
}
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
if (yaml.isNullOrBlank()) return Result.Network
|
||||||
|
val n = runCatching { RPCSX.instance.patchesImport(yaml) }.getOrDefault(-1)
|
||||||
|
return if (n >= 0) Result.Ok(n) else Result.Parse
|
||||||
|
}
|
||||||
|
|
||||||
/** Lowercase hex SHA-256, the form rpcs3.net sends and desktop compares against. */
|
/** Lowercase hex SHA-256, the form rpcs3.net sends and desktop compares against. */
|
||||||
private fun sha256(text: String): String =
|
private fun sha256(text: String): String =
|
||||||
java.security.MessageDigest.getInstance("SHA-256")
|
java.security.MessageDigest.getInstance("SHA-256")
|
||||||
|
|||||||
@@ -176,6 +176,16 @@ data class Ps3Settings(
|
|||||||
val audioTimeStretch: Boolean = false,
|
val audioTimeStretch: Boolean = false,
|
||||||
val audioBuffering: Boolean = true,
|
val audioBuffering: Boolean = true,
|
||||||
val audioBufferMs: Int = 34,
|
val audioBufferMs: Int = 34,
|
||||||
|
/** cubeb backend: 0 auto, 1 aaudio, 2 opensl, 3 audiotrack. Auto reaches AAudio on
|
||||||
|
* anything modern; opensl trades latency for far fewer underruns on weak devices. */
|
||||||
|
val audioCubebBackend: Int = 0,
|
||||||
|
/** Pin Adreno to max clocks: removes DVFS ramp-up stutter in GPU-bound scenes, costs heat
|
||||||
|
* and battery. Off by default, matching the RPCSX fork this came from. */
|
||||||
|
val gpuTurbo: Boolean = false,
|
||||||
|
/** Stops the core writing any log after startup. A real performance lever on games that
|
||||||
|
* log heavily, but it also destroys the only artifact a bug report can carry, so it is
|
||||||
|
* off by default and the UI says so plainly. */
|
||||||
|
val silenceAllLogs: Boolean = false,
|
||||||
val netEnabled: Boolean = false,
|
val netEnabled: Boolean = false,
|
||||||
val psnStatus: Boolean = false,
|
val psnStatus: Boolean = false,
|
||||||
val upnpEnabled: Boolean = false,
|
val upnpEnabled: Boolean = false,
|
||||||
@@ -1017,6 +1027,7 @@ data class Settings(
|
|||||||
// surface layout. Keeping them in sync stops "Stretch" looking inert.
|
// surface layout. Keeping them in sync stops "Stretch" looking inert.
|
||||||
put("PS3/Video", "Stretch To Display Area", "bool", (displayFitMode == 1).toString())
|
put("PS3/Video", "Stretch To Display Area", "bool", (displayFitMode == 1).toString())
|
||||||
put("PS3/Video", "Display Aspect Override", "int", ps3.displayAspect.coerceIn(0, 4000).toString())
|
put("PS3/Video", "Display Aspect Override", "int", ps3.displayAspect.coerceIn(0, 4000).toString())
|
||||||
|
put("PS3/Misc", "Silence All Logs", "bool", ps3.silenceAllLogs.toString())
|
||||||
put("PS3/Overlay", "Enabled", "bool", ps3.overlayEnabled.toString())
|
put("PS3/Overlay", "Enabled", "bool", ps3.overlayEnabled.toString())
|
||||||
put("PS3/Overlay", "Detail level", "enum", ps3.overlayDetail.toString())
|
put("PS3/Overlay", "Detail level", "enum", ps3.overlayDetail.toString())
|
||||||
put("PS3/Overlay", "Enable Framerate Graph", "bool", ps3.overlayFramerateGraph.toString())
|
put("PS3/Overlay", "Enable Framerate Graph", "bool", ps3.overlayFramerateGraph.toString())
|
||||||
@@ -1049,6 +1060,7 @@ data class Settings(
|
|||||||
put("PS3/Audio", "Enable Time Stretching", "bool", ps3.audioTimeStretch.toString())
|
put("PS3/Audio", "Enable Time Stretching", "bool", ps3.audioTimeStretch.toString())
|
||||||
put("PS3/Audio", "Enable Buffering", "bool", ps3.audioBuffering.toString())
|
put("PS3/Audio", "Enable Buffering", "bool", ps3.audioBuffering.toString())
|
||||||
put("PS3/Audio", "Desired Audio Buffer Duration", "int", ps3.audioBufferMs.toString())
|
put("PS3/Audio", "Desired Audio Buffer Duration", "int", ps3.audioBufferMs.toString())
|
||||||
|
put("PS3/Audio", "Cubeb Backend", "int", ps3.audioCubebBackend.toString())
|
||||||
put("PS3/Net", "Internet enabled", "enum", ps3.netEnabled.toString())
|
put("PS3/Net", "Internet enabled", "enum", ps3.netEnabled.toString())
|
||||||
put("PS3/Net", "PSN status", "enum", ps3.psnStatus.toString())
|
put("PS3/Net", "PSN status", "enum", ps3.psnStatus.toString())
|
||||||
put("PS3/Net", "UPNP Enabled", "bool", ps3.upnpEnabled.toString())
|
put("PS3/Net", "UPNP Enabled", "bool", ps3.upnpEnabled.toString())
|
||||||
@@ -1994,8 +2006,11 @@ data class Settings(
|
|||||||
put("ps3ClocksScale", ps3.clocksScale)
|
put("ps3ClocksScale", ps3.clocksScale)
|
||||||
put("ps3ResolutionScale", ps3.resolutionScale)
|
put("ps3ResolutionScale", ps3.resolutionScale)
|
||||||
put("ps3MsaaMode", ps3.msaaMode)
|
put("ps3MsaaMode", ps3.msaaMode)
|
||||||
|
put("ps3AudioCubebBackend", ps3.audioCubebBackend)
|
||||||
put("ps3ShaderMode", ps3.shaderMode)
|
put("ps3ShaderMode", ps3.shaderMode)
|
||||||
put("ps3WriteColorBuffers", ps3.writeColorBuffers)
|
put("ps3WriteColorBuffers", ps3.writeColorBuffers)
|
||||||
|
put("ps3GpuTurbo", ps3.gpuTurbo)
|
||||||
|
put("ps3SilenceAllLogs", ps3.silenceAllLogs)
|
||||||
put("ps3WriteDepthBuffer", ps3.writeDepthBuffer)
|
put("ps3WriteDepthBuffer", ps3.writeDepthBuffer)
|
||||||
put("ps3ReadColorBuffers", ps3.readColorBuffers)
|
put("ps3ReadColorBuffers", ps3.readColorBuffers)
|
||||||
put("ps3ReadDepthBuffer", ps3.readDepthBuffer)
|
put("ps3ReadDepthBuffer", ps3.readDepthBuffer)
|
||||||
@@ -2329,8 +2344,11 @@ data class Settings(
|
|||||||
clocksScale = json.optInt("ps3ClocksScale", def.ps3.clocksScale),
|
clocksScale = json.optInt("ps3ClocksScale", def.ps3.clocksScale),
|
||||||
resolutionScale = json.optInt("ps3ResolutionScale", def.ps3.resolutionScale),
|
resolutionScale = json.optInt("ps3ResolutionScale", def.ps3.resolutionScale),
|
||||||
msaaMode = json.optInt("ps3MsaaMode", def.ps3.msaaMode),
|
msaaMode = json.optInt("ps3MsaaMode", def.ps3.msaaMode),
|
||||||
|
audioCubebBackend = json.optInt("ps3AudioCubebBackend", def.ps3.audioCubebBackend),
|
||||||
shaderMode = json.optInt("ps3ShaderMode", def.ps3.shaderMode),
|
shaderMode = json.optInt("ps3ShaderMode", def.ps3.shaderMode),
|
||||||
writeColorBuffers = json.optBoolean("ps3WriteColorBuffers", def.ps3.writeColorBuffers),
|
writeColorBuffers = json.optBoolean("ps3WriteColorBuffers", def.ps3.writeColorBuffers),
|
||||||
|
gpuTurbo = json.optBoolean("ps3GpuTurbo", def.ps3.gpuTurbo),
|
||||||
|
silenceAllLogs = json.optBoolean("ps3SilenceAllLogs", def.ps3.silenceAllLogs),
|
||||||
writeDepthBuffer = json.optBoolean("ps3WriteDepthBuffer", def.ps3.writeDepthBuffer),
|
writeDepthBuffer = json.optBoolean("ps3WriteDepthBuffer", def.ps3.writeDepthBuffer),
|
||||||
readColorBuffers = json.optBoolean("ps3ReadColorBuffers", def.ps3.readColorBuffers),
|
readColorBuffers = json.optBoolean("ps3ReadColorBuffers", def.ps3.readColorBuffers),
|
||||||
readDepthBuffer = json.optBoolean("ps3ReadDepthBuffer", def.ps3.readDepthBuffer),
|
readDepthBuffer = json.optBoolean("ps3ReadDepthBuffer", def.ps3.readDepthBuffer),
|
||||||
@@ -2644,8 +2662,11 @@ data class Settings(
|
|||||||
if (current.ps3.clocksScale != base.ps3.clocksScale) j.put("ps3ClocksScale", current.ps3.clocksScale)
|
if (current.ps3.clocksScale != base.ps3.clocksScale) j.put("ps3ClocksScale", current.ps3.clocksScale)
|
||||||
if (current.ps3.resolutionScale != base.ps3.resolutionScale) j.put("ps3ResolutionScale", current.ps3.resolutionScale)
|
if (current.ps3.resolutionScale != base.ps3.resolutionScale) j.put("ps3ResolutionScale", current.ps3.resolutionScale)
|
||||||
if (current.ps3.msaaMode != base.ps3.msaaMode) j.put("ps3MsaaMode", current.ps3.msaaMode)
|
if (current.ps3.msaaMode != base.ps3.msaaMode) j.put("ps3MsaaMode", current.ps3.msaaMode)
|
||||||
|
if (current.ps3.audioCubebBackend != base.ps3.audioCubebBackend) j.put("ps3AudioCubebBackend", current.ps3.audioCubebBackend)
|
||||||
if (current.ps3.shaderMode != base.ps3.shaderMode) j.put("ps3ShaderMode", current.ps3.shaderMode)
|
if (current.ps3.shaderMode != base.ps3.shaderMode) j.put("ps3ShaderMode", current.ps3.shaderMode)
|
||||||
if (current.ps3.writeColorBuffers != base.ps3.writeColorBuffers) j.put("ps3WriteColorBuffers", current.ps3.writeColorBuffers)
|
if (current.ps3.writeColorBuffers != base.ps3.writeColorBuffers) j.put("ps3WriteColorBuffers", current.ps3.writeColorBuffers)
|
||||||
|
if (current.ps3.gpuTurbo != base.ps3.gpuTurbo) j.put("ps3GpuTurbo", current.ps3.gpuTurbo)
|
||||||
|
if (current.ps3.silenceAllLogs != base.ps3.silenceAllLogs) j.put("ps3SilenceAllLogs", current.ps3.silenceAllLogs)
|
||||||
if (current.ps3.writeDepthBuffer != base.ps3.writeDepthBuffer) j.put("ps3WriteDepthBuffer", current.ps3.writeDepthBuffer)
|
if (current.ps3.writeDepthBuffer != base.ps3.writeDepthBuffer) j.put("ps3WriteDepthBuffer", current.ps3.writeDepthBuffer)
|
||||||
if (current.ps3.readColorBuffers != base.ps3.readColorBuffers) j.put("ps3ReadColorBuffers", current.ps3.readColorBuffers)
|
if (current.ps3.readColorBuffers != base.ps3.readColorBuffers) j.put("ps3ReadColorBuffers", current.ps3.readColorBuffers)
|
||||||
if (current.ps3.readDepthBuffer != base.ps3.readDepthBuffer) j.put("ps3ReadDepthBuffer", current.ps3.readDepthBuffer)
|
if (current.ps3.readDepthBuffer != base.ps3.readDepthBuffer) j.put("ps3ReadDepthBuffer", current.ps3.readDepthBuffer)
|
||||||
@@ -2940,8 +2961,11 @@ data class Settings(
|
|||||||
clocksScale = if (overrides.has("ps3ClocksScale")) overrides.getInt("ps3ClocksScale") else base.ps3.clocksScale,
|
clocksScale = if (overrides.has("ps3ClocksScale")) overrides.getInt("ps3ClocksScale") else base.ps3.clocksScale,
|
||||||
resolutionScale = if (overrides.has("ps3ResolutionScale")) overrides.getInt("ps3ResolutionScale") else base.ps3.resolutionScale,
|
resolutionScale = if (overrides.has("ps3ResolutionScale")) overrides.getInt("ps3ResolutionScale") else base.ps3.resolutionScale,
|
||||||
msaaMode = if (overrides.has("ps3MsaaMode")) overrides.getInt("ps3MsaaMode") else base.ps3.msaaMode,
|
msaaMode = if (overrides.has("ps3MsaaMode")) overrides.getInt("ps3MsaaMode") else base.ps3.msaaMode,
|
||||||
|
audioCubebBackend = if (overrides.has("ps3AudioCubebBackend")) overrides.getInt("ps3AudioCubebBackend") else base.ps3.audioCubebBackend,
|
||||||
shaderMode = if (overrides.has("ps3ShaderMode")) overrides.getInt("ps3ShaderMode") else base.ps3.shaderMode,
|
shaderMode = if (overrides.has("ps3ShaderMode")) overrides.getInt("ps3ShaderMode") else base.ps3.shaderMode,
|
||||||
writeColorBuffers = if (overrides.has("ps3WriteColorBuffers")) overrides.getBoolean("ps3WriteColorBuffers") else base.ps3.writeColorBuffers,
|
writeColorBuffers = if (overrides.has("ps3WriteColorBuffers")) overrides.getBoolean("ps3WriteColorBuffers") else base.ps3.writeColorBuffers,
|
||||||
|
gpuTurbo = if (overrides.has("ps3GpuTurbo")) overrides.getBoolean("ps3GpuTurbo") else base.ps3.gpuTurbo,
|
||||||
|
silenceAllLogs = if (overrides.has("ps3SilenceAllLogs")) overrides.getBoolean("ps3SilenceAllLogs") else base.ps3.silenceAllLogs,
|
||||||
writeDepthBuffer = if (overrides.has("ps3WriteDepthBuffer")) overrides.getBoolean("ps3WriteDepthBuffer") else base.ps3.writeDepthBuffer,
|
writeDepthBuffer = if (overrides.has("ps3WriteDepthBuffer")) overrides.getBoolean("ps3WriteDepthBuffer") else base.ps3.writeDepthBuffer,
|
||||||
readColorBuffers = if (overrides.has("ps3ReadColorBuffers")) overrides.getBoolean("ps3ReadColorBuffers") else base.ps3.readColorBuffers,
|
readColorBuffers = if (overrides.has("ps3ReadColorBuffers")) overrides.getBoolean("ps3ReadColorBuffers") else base.ps3.readColorBuffers,
|
||||||
readDepthBuffer = if (overrides.has("ps3ReadDepthBuffer")) overrides.getBoolean("ps3ReadDepthBuffer") else base.ps3.readDepthBuffer,
|
readDepthBuffer = if (overrides.has("ps3ReadDepthBuffer")) overrides.getBoolean("ps3ReadDepthBuffer") else base.ps3.readDepthBuffer,
|
||||||
|
|||||||
@@ -468,6 +468,8 @@ val EN: Map<String, String> = mapOf(
|
|||||||
"audio.synchronization.label" to "Time Stretching",
|
"audio.synchronization.label" to "Time Stretching",
|
||||||
"audio.synchronization.description" to "Keeps audio pitch correct when the emulator runs below full speed, instead of letting sound slow and drop in pitch along with it. Costs a little CPU; turn it off if audio stutters more with it on.",
|
"audio.synchronization.description" to "Keeps audio pitch correct when the emulator runs below full speed, instead of letting sound slow and drop in pitch along with it. Costs a little CPU; turn it off if audio stutters more with it on.",
|
||||||
"audio.buffer.label" to "Audio Buffer",
|
"audio.buffer.label" to "Audio Buffer",
|
||||||
|
"audio.cubebBackend.label" to "Audio Backend (advanced)",
|
||||||
|
"audio.cubebBackend.description" to "Which system audio path the emulator uses. Auto picks AAudio, which has the lowest latency but the smallest buffers. OpenSL adds latency and is much harder to starve — try it if audio crackles or stutters on a slower device.",
|
||||||
"audio.buffer.description" to "Bigger buffer = fewer crackles/dropouts but more latency. Raise this if audio stutters on low-end devices.",
|
"audio.buffer.description" to "Bigger buffer = fewer crackles/dropouts but more latency. Raise this if audio stutters on low-end devices.",
|
||||||
// --- Recompiler (JIT) tab ---
|
// --- Recompiler (JIT) tab ---
|
||||||
"jit.recompiler.warning" to "Disabling a recompiler drops that CPU/COP onto its interpreter — much slower, for debugging only. Changes apply to the running game.",
|
"jit.recompiler.warning" to "Disabling a recompiler drops that CPU/COP onto its interpreter — much slower, for debugging only. Changes apply to the running game.",
|
||||||
@@ -525,6 +527,8 @@ val EN: Map<String, String> = mapOf(
|
|||||||
"adv.ppuRsvPriority.description" to "Gives the main CPU priority over the SPUs when competing for the same memory. Can help games that stall waiting on the PPU.",
|
"adv.ppuRsvPriority.description" to "Gives the main CPU priority over the SPUs when competing for the same memory. Can help games that stall waiting on the PPU.",
|
||||||
"adv.spuVerification.label" to "SPU Verification",
|
"adv.spuVerification.label" to "SPU Verification",
|
||||||
"adv.spuVerification.description" to "Verifies compiled SPU code against the original. Catches miscompiles; turning it off is faster but makes bad codegen silent.",
|
"adv.spuVerification.description" to "Verifies compiled SPU code against the original. Catches miscompiles; turning it off is faster but makes bad codegen silent.",
|
||||||
|
"adv.preciseSpuVerification.label" to "Precise SPU Verification",
|
||||||
|
"adv.preciseSpuVerification.description" to "Compares cached SPU code byte for byte instead of by checksum. Slower, but games stream different SPU jobs through the same memory, so a checksum collision can run one job's code on another's data. Try this for physics or logic that goes wrong without crashing, such as falling through the ground.",
|
||||||
"adv.section.fpu" to "Floating Point",
|
"adv.section.fpu" to "Floating Point",
|
||||||
"adv.section.fpu.help" to "PowerPC floating-point corner cases. Defaults are correct for almost every game \u2014 these exist for the handful that depend on exact NaN or rounding behaviour.",
|
"adv.section.fpu.help" to "PowerPC floating-point corner cases. Defaults are correct for almost every game \u2014 these exist for the handful that depend on exact NaN or rounding behaviour.",
|
||||||
"adv.ppuNan.label" to "PPU Vector NaN Handling",
|
"adv.ppuNan.label" to "PPU Vector NaN Handling",
|
||||||
@@ -596,15 +600,21 @@ val EN: Map<String, String> = mapOf(
|
|||||||
"renderer.consoleAspect.description" to "The aspect the emulated PS3 reports to the game. The console only ever signalled 4:3 or 16:9, so those are the only real options \u2014 Auto leaves it to the game. This is what the game renders for; how it is fitted to YOUR screen is the setting below.",
|
"renderer.consoleAspect.description" to "The aspect the emulated PS3 reports to the game. The console only ever signalled 4:3 or 16:9, so those are the only real options \u2014 Auto leaves it to the game. This is what the game renders for; how it is fitted to YOUR screen is the setting below.",
|
||||||
"renderer.fit.auto" to "Fit",
|
"renderer.fit.auto" to "Fit",
|
||||||
"renderer.fit.stretch" to "Stretch",
|
"renderer.fit.stretch" to "Stretch",
|
||||||
|
"perf.silenceLogs.label" to "Silence all logs",
|
||||||
|
"perf.silenceLogs.description" to "\u26a0 We cannot help you with a bug if logs are disabled \u2014 a report without a log is usually unfixable. That said, a few games log so heavily that this is worth several FPS, so it can be the difference for a game sitting just under playable. Turn it back on before reporting anything.",
|
||||||
|
"renderer.gpuTurbo.label" to "GPU Turbo (Adreno)",
|
||||||
|
"renderer.gpuTurbo.description" to "Pin the GPU to its maximum clocks instead of letting it scale on demand. Removes the stutter when a scene suddenly gets heavy, at the cost of noticeably more heat and battery. Adreno only \u2014 ignored on other GPUs.",
|
||||||
"renderer.upscale.label" to "Internal Resolution",
|
"renderer.upscale.label" to "Internal Resolution",
|
||||||
"audio.renderer.label" to "Audio Backend",
|
"audio.renderer.label" to "Audio Backend",
|
||||||
"audio.renderer.description" to "Cubeb is the only working backend on Android. Off disables audio entirely, which can claw back a little CPU on a struggling device.",
|
"audio.renderer.description" to "Cubeb is the general-purpose backend. Oboe is Android-specific and carries per-device workarounds plus automatic recovery when the audio route changes, so try it if you get crackling or dropouts. Off disables audio entirely, which claws back a little CPU on a struggling device.",
|
||||||
"patches.ps3.header" to "Community patches and graphics mods from RPCS3. These can unlock frame rates, change resolution or LOD, add widescreen, and fix game bugs. Download once, then enable per game.",
|
"patches.ps3.header" to "Community patches and graphics mods from RPCS3. These can unlock frame rates, change resolution or LOD, add widescreen, and fix game bugs. Download once, then enable per game.",
|
||||||
"patches.ps3.download" to "Download / update patch database",
|
"patches.ps3.download" to "Download / update patch database",
|
||||||
"patches.ps3.search" to "Search patches",
|
"patches.ps3.search" to "Search patches",
|
||||||
"patches.ps3.showing" to "%1 games, %2 patches \u2014 tap a game to see its patches, or search.",
|
"patches.ps3.showing" to "%1 games, %2 patches \u2014 tap a game to see its patches, or search.",
|
||||||
"patches.ps3.downloading" to "Downloading patches\u2026",
|
"patches.ps3.downloading" to "Downloading patches\u2026",
|
||||||
"patches.ps3.imported" to "patches imported",
|
"patches.ps3.imported" to "patches imported",
|
||||||
|
"patches.ps3.importFile" to "Import a patch file",
|
||||||
|
"patches.ps3.importFailed" to "Could not read that file. Pick a patch.yml exported from RPCS3.",
|
||||||
"patches.ps3.downloadFailed" to "Could not download the patch database. Check your connection.",
|
"patches.ps3.downloadFailed" to "Could not download the patch database. Check your connection.",
|
||||||
"patches.ps3.serverError" to "RPCS3's patch server returned an error.",
|
"patches.ps3.serverError" to "RPCS3's patch server returned an error.",
|
||||||
"patches.ps3.parseFailed" to "The patch database downloaded but could not be read. It may be for a newer patch format than this build supports.",
|
"patches.ps3.parseFailed" to "The patch database downloaded but could not be read. It may be for a newer patch format than this build supports.",
|
||||||
|
|||||||
@@ -813,6 +813,15 @@ open class MainActivityRuntime : ComponentActivity() {
|
|||||||
upscale.value = resolved.upscaleFloat
|
upscale.value = resolved.upscaleFloat
|
||||||
renderer.value = resolved.renderer
|
renderer.value = resolved.renderer
|
||||||
|
|
||||||
|
// GPU turbo, resolved per game like the driver below it. Applied here rather than
|
||||||
|
// through the config tree because it is not an emulator setting at all -- it is a KGSL
|
||||||
|
// ioctl straight to the Adreno kernel driver, so nothing in g_cfg would carry it.
|
||||||
|
//
|
||||||
|
// Safe unconditionally: adrenotools no-ops on non-Adreno hardware and if /dev/kgsl-3d0
|
||||||
|
// cannot be opened. Re-applied on every start so turning it off actually releases the
|
||||||
|
// clocks on the next boot rather than persisting until reboot.
|
||||||
|
runCatching { net.rpcsx.RPCSX.instance.setGpuTurbo(resolved.ps3.gpuTurbo) }
|
||||||
|
|
||||||
NativeApp.renderUpscalemultiplier(upscale.value)
|
NativeApp.renderUpscalemultiplier(upscale.value)
|
||||||
// Pin custom Vulkan driver (if any) BEFORE the renderer write —
|
// Pin custom Vulkan driver (if any) BEFORE the renderer write —
|
||||||
// the renderer JNI may trigger MTGS::ApplySettings which can
|
// the renderer JNI may trigger MTGS::ApplySettings which can
|
||||||
@@ -1772,12 +1781,38 @@ open class MainActivityRuntime : ComponentActivity() {
|
|||||||
// reinstalling clean (#376/#385). The cache is pure derived data (rebuilt on demand),
|
// reinstalling clean (#376/#385). The cache is pure derived data (rebuilt on demand),
|
||||||
// never user content, so wiping it is always safe. Skipped on first install (no prior
|
// never user content, so wiping it is always safe. Skipped on first install (no prior
|
||||||
// version recorded) — there is nothing stale to clear.
|
// version recorded) — there is nothing stale to clear.
|
||||||
|
// ONLY the GPU caches, never the compiled guest modules.
|
||||||
|
//
|
||||||
|
// This came from ARMSX2, where <dataRoot>/cache held the GS shader/pipeline cache and
|
||||||
|
// nothing else, so deleting the lot was free. In ARMSX3 that same directory is RPCS3's
|
||||||
|
// whole cache root: <dataRoot>/cache/cache/<TITLEID>/ppu-<hash>-EBOOT.BIN/ holds every
|
||||||
|
// compiled PPU module, and deleting it threw away work measured in tens of minutes per
|
||||||
|
// game -- an hour for the XMB's 390 firmware modules. Every update, on purpose, by code
|
||||||
|
// that believed it was clearing shaders. That is the "why do I have to recompile my games
|
||||||
|
// after every update" report, and it is the single worst thing about updating.
|
||||||
|
//
|
||||||
|
// The original worry stands and is preserved: a pipeline cache baked against a different
|
||||||
|
// core build can render corrupt (#376/#385). But that argument is about GPU pipeline blobs,
|
||||||
|
// not guest code. PPU objects already carry their own compatibility key in the filename --
|
||||||
|
// format version, module hash, the settings that affect codegen, and the CPU target -- so a
|
||||||
|
// build that changes any of that simply does not match them, and one that does not change
|
||||||
|
// it has no reason to discard them.
|
||||||
runCatching {
|
runCatching {
|
||||||
val prevVc = prefs.getInt("lastRunVersionCode", 0)
|
val prevVc = prefs.getInt("lastRunVersionCode", 0)
|
||||||
val curVc = BuildConfig.VERSION_CODE
|
val curVc = BuildConfig.VERSION_CODE
|
||||||
if (prevVc != 0 && prevVc != curVc) {
|
if (prevVc != 0 && prevVc != curVc) {
|
||||||
File(assetCopyRoot(applicationContext), "cache").deleteRecursively()
|
val root = File(assetCopyRoot(applicationContext), "cache")
|
||||||
android.util.Log.i("ARMSX2", "Update $prevVc -> $curVc: cleared GS shader/pipeline cache")
|
var cleared = 0
|
||||||
|
// Depth-first over the cache root, removing only directories named shaders_cache
|
||||||
|
// (RPCS3 puts one beside each title's compiled modules). walkBottomUp so a match is
|
||||||
|
// deleted whole without the walk then descending into a directory that is gone.
|
||||||
|
root.walkBottomUp()
|
||||||
|
.filter { it.isDirectory && it.name == "shaders_cache" }
|
||||||
|
.forEach { if (it.deleteRecursively()) cleared++ }
|
||||||
|
android.util.Log.i(
|
||||||
|
"ARMSX2",
|
||||||
|
"Update $prevVc -> $curVc: cleared $cleared shader cache(s); compiled modules kept",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (prevVc != curVc) prefs.edit { putInt("lastRunVersionCode", curVc) }
|
if (prevVc != curVc) prefs.edit { putInt("lastRunVersionCode", curVc) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -877,6 +877,14 @@ private fun GraphicsPane(state: EmulationMenuUiState, viewModel: EmulationMenuVi
|
|||||||
MenuSwitchRow(str("renderer.readColorBuffers.label"), settings.ps3.readColorBuffers) { v ->
|
MenuSwitchRow(str("renderer.readColorBuffers.label"), settings.ps3.readColorBuffers) { v ->
|
||||||
viewModel.updateSettings { it.copy(ps3 = it.ps3.copy(readColorBuffers = v)) }
|
viewModel.updateSettings { it.copy(ps3 = it.ps3.copy(readColorBuffers = v)) }
|
||||||
}
|
}
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
// GPU Turbo in-game: the whole point is A/B-ing it against a scene that is actually
|
||||||
|
// stuttering, which is impossible if you have to quit to Settings to flip it. Applied
|
||||||
|
// live, not just at the next renderer start.
|
||||||
|
MenuSwitchRow(str("renderer.gpuTurbo.label"), settings.ps3.gpuTurbo) { v ->
|
||||||
|
viewModel.updateSettings { it.copy(ps3 = it.ps3.copy(gpuTurbo = v)) }
|
||||||
|
runCatching { net.rpcsx.RPCSX.instance.setGpuTurbo(v) }
|
||||||
|
}
|
||||||
// Overlay artwork, switchable from in-game — trying bezels means seeing them ON the game, and
|
// Overlay artwork, switchable from in-game — trying bezels means seeing them ON the game, and
|
||||||
// having to leave for All Settings each time made that unusable. Import still lives in the
|
// having to leave for All Settings each time made that unusable. Import still lives in the
|
||||||
// settings tab (it opens a file picker); this is the picker for what is already imported.
|
// settings tab (it opens a file picker); this is the picker for what is already imported.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.armsx2.ui.patches
|
package com.armsx2.ui.patches
|
||||||
|
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -25,6 +27,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.armsx2.Ps3PatchRepo
|
import com.armsx2.Ps3PatchRepo
|
||||||
@@ -79,6 +82,30 @@ fun Ps3PatchesTab(serial: String = "") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
// No MIME filter: patch.yml is served as text/plain, application/octet-stream or
|
||||||
|
// nothing at all depending on where it came from, and filtering hides the file
|
||||||
|
// the user is looking straight at.
|
||||||
|
val patchPicker = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocument()
|
||||||
|
) { uri ->
|
||||||
|
if (uri == null) return@rememberLauncherForActivityResult
|
||||||
|
busy = true
|
||||||
|
message = null
|
||||||
|
scope.launch {
|
||||||
|
val r = withContext(Dispatchers.IO) { Ps3PatchRepo.importLocal(context, uri) }
|
||||||
|
busy = false
|
||||||
|
message = when (r) {
|
||||||
|
is Ps3PatchRepo.Result.Ok -> {
|
||||||
|
reload()
|
||||||
|
"${r.count} " + str2("patches.ps3.imported")
|
||||||
|
}
|
||||||
|
Ps3PatchRepo.Result.Parse -> str2("patches.ps3.parseFailed")
|
||||||
|
else -> str2("patches.ps3.importFailed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(serial) { reload() }
|
LaunchedEffect(serial) { reload() }
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
@@ -119,6 +146,17 @@ fun Ps3PatchesTab(serial: String = "") {
|
|||||||
Text(str("patches.ps3.download"))
|
Text(str("patches.ps3.download"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Local import sits next to the download because the two produce the same
|
||||||
|
// result -- patchesImport merges either source into patches/patch.yml.
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { if (!busy) patchPicker.launch(arrayOf("*/*")) },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.controllerFocusable("patches.import", RoundedCornerShape(13.dp)),
|
||||||
|
) {
|
||||||
|
Text(str("patches.ps3.importFile"))
|
||||||
|
}
|
||||||
|
|
||||||
if (busy) {
|
if (busy) {
|
||||||
Spacer(Modifier.height(10.dp))
|
Spacer(Modifier.height(10.dp))
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|||||||
@@ -71,12 +71,14 @@ fun AudioTab(state: MutableState<Settings>) {
|
|||||||
// Audio backend. Only Cubeb and Null are real on Android -- XAudio2 is
|
// Audio backend. Only Cubeb and Null are real on Android -- XAudio2 is
|
||||||
// Windows-only and FAudio is not built here -- so offering the other two
|
// Windows-only and FAudio is not built here -- so offering the other two
|
||||||
// would just be a way to silently kill audio.
|
// would just be a way to silently kill audio.
|
||||||
|
// Indices here are positions in Rpcs3Settings.AUDIO_RENDERERS: Cubeb=2, Oboe=4, Null=0.
|
||||||
|
val rendererIndices = listOf(2, 4, 0)
|
||||||
SegmentedRow(
|
SegmentedRow(
|
||||||
label = str("audio.renderer.label"),
|
label = str("audio.renderer.label"),
|
||||||
options = listOf("Cubeb", str("common.off")),
|
options = listOf("Cubeb", "Oboe", str("common.off")),
|
||||||
selectedIndex = if (s.ps3.audioRenderer == 0) 1 else 0,
|
selectedIndex = rendererIndices.indexOf(s.ps3.audioRenderer).coerceAtLeast(0),
|
||||||
description = str("audio.renderer.description"),
|
description = str("audio.renderer.description"),
|
||||||
onChange = { apply(s.copy(ps3 = s.ps3.copy(audioRenderer = if (it == 0) 2 else 0))) },
|
onChange = { apply(s.copy(ps3 = s.ps3.copy(audioRenderer = rendererIndices[it]))) },
|
||||||
)
|
)
|
||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
SegmentedGridRow(
|
SegmentedGridRow(
|
||||||
@@ -100,8 +102,25 @@ fun AudioTab(state: MutableState<Settings>) {
|
|||||||
onChange = { apply(s.copy(ps3 = s.ps3.copy(audioChannels = intArrayOf(0, 1, 2, 6, 7)[it]))) },
|
onChange = { apply(s.copy(ps3 = s.ps3.copy(audioChannels = intArrayOf(0, 1, 2, 6, 7)[it]))) },
|
||||||
)
|
)
|
||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
|
// The replacement for PCSX2's OpenSL ES toggle, which was removed from here on the
|
||||||
|
// reasoning that "RPCS3 picks its backend via Audio Renderer". That is not the same knob:
|
||||||
|
// Audio Renderer chooses Cubeb or Null, while THIS chooses which backend cubeb then talks
|
||||||
|
// to. Android builds all three, and cubeb's auto order takes AAudio on anything modern, so
|
||||||
|
// OpenSL had quietly become unreachable rather than superseded.
|
||||||
|
//
|
||||||
|
// It matters because AAudio's low-latency path takes the smallest buffers the device will
|
||||||
|
// grant, and those are the first thing to underrun once the emulator drops below full
|
||||||
|
// speed -- the audio stutter reported on low-end Mali devices, which faster hardware never
|
||||||
|
// shows. OpenSL trades latency for buffers that are much harder to starve.
|
||||||
|
SegmentedGridRow(
|
||||||
|
label = str("audio.cubebBackend.label"),
|
||||||
|
options = listOf(str("common.auto"), "AAudio", "OpenSL", "AudioTrack"),
|
||||||
|
selectedIndex = s.ps3.audioCubebBackend.coerceIn(0, 3),
|
||||||
|
columns = 4,
|
||||||
|
description = str("audio.cubebBackend.description"),
|
||||||
|
onChange = { apply(s.copy(ps3 = s.ps3.copy(audioCubebBackend = it))) },
|
||||||
|
)
|
||||||
// Removed: SPU2 is the PS2's sound chip; its NEON reverb path does not exist here.
|
// Removed: SPU2 is the PS2's sound chip; its NEON reverb path does not exist here.
|
||||||
// Removed: PCSX2's OpenSL ES toggle. RPCS3 picks its backend via Audio Renderer (Cubeb on Android).
|
|
||||||
// Removed: PCSX2 SPU2 lightweight mixing mode. No RPCS3 counterpart.
|
// Removed: PCSX2 SPU2 lightweight mixing mode. No RPCS3 counterpart.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,15 @@ fun FixesTab(state: MutableState<Settings>) {
|
|||||||
s.ps3.spuVerification,
|
s.ps3.spuVerification,
|
||||||
description = str("adv.spuVerification.description"),
|
description = str("adv.spuVerification.description"),
|
||||||
) { apply(s.copy(ps3 = s.ps3.copy(spuVerification = it))) }
|
) { apply(s.copy(ps3 = s.ps3.copy(spuVerification = it))) }
|
||||||
|
SettingsDivider()
|
||||||
|
// Distinct from the toggle above: that one chooses whether to verify at all, this one
|
||||||
|
// chooses how. The field was already serialised and written to the config tree but had
|
||||||
|
// no control, so the only way to reach it was a raw core override.
|
||||||
|
ToggleRow(
|
||||||
|
str("adv.preciseSpuVerification.label"),
|
||||||
|
s.ps3.preciseSpuVerification,
|
||||||
|
description = str("adv.preciseSpuVerification.description"),
|
||||||
|
) { apply(s.copy(ps3 = s.ps3.copy(preciseSpuVerification = it))) }
|
||||||
}
|
}
|
||||||
|
|
||||||
CollapsibleSection(str("adv.section.fpu")) {
|
CollapsibleSection(str("adv.section.fpu")) {
|
||||||
|
|||||||
@@ -192,6 +192,40 @@ fun PerformanceTab(state: MutableState<Settings>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
|
// ---- GPU Turbo (Adreno) -------------------------------------------------
|
||||||
|
// A KGSL ioctl that pins the GPU to maximum clocks instead of letting DVFS ramp. Lives
|
||||||
|
// here rather than with the driver picker because what it actually trades is heat and
|
||||||
|
// battery for frametime stability -- that is a performance decision, not a display one.
|
||||||
|
SegmentedRow(
|
||||||
|
label = str("renderer.gpuTurbo.label"),
|
||||||
|
options = listOf(str("common.off"), str("common.on")),
|
||||||
|
selectedIndex = if (s.ps3.gpuTurbo) 1 else 0,
|
||||||
|
description = str("renderer.gpuTurbo.description"),
|
||||||
|
onChange = {
|
||||||
|
val on = it == 1
|
||||||
|
apply(s.copy(ps3 = s.ps3.copy(gpuTurbo = on)))
|
||||||
|
// Apply live as well as at the next renderer start, so it is testable without
|
||||||
|
// rebooting the game.
|
||||||
|
runCatching { net.rpcsx.RPCSX.instance.setGpuTurbo(on) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SettingsDivider()
|
||||||
|
// ---- Silence all logs ---------------------------------------------------
|
||||||
|
// A genuine performance lever, not a debug knob: a few games log thousands of lines a
|
||||||
|
// second and every one costs a write. Measured at 5-7 FPS on LittleBigPlanet 2, which
|
||||||
|
// can be the difference between unplayable and playable.
|
||||||
|
//
|
||||||
|
// Default OFF, deliberately. A bug report without a log is usually unfixable, and the
|
||||||
|
// description says so in as many words -- the setting is here for people who already
|
||||||
|
// know the trade and want the frames.
|
||||||
|
SegmentedRow(
|
||||||
|
label = str("perf.silenceLogs.label"),
|
||||||
|
options = listOf(str("common.off"), str("common.on")),
|
||||||
|
selectedIndex = if (s.ps3.silenceAllLogs) 1 else 0,
|
||||||
|
description = str("perf.silenceLogs.description"),
|
||||||
|
onChange = { apply(s.copy(ps3 = s.ps3.copy(silenceAllLogs = it == 1))) },
|
||||||
|
)
|
||||||
|
SettingsDivider()
|
||||||
// Affinity Control Mode — opt-in CPU pinning for the EE/VU/GS threads. Android normally
|
// Affinity Control Mode — opt-in CPU pinning for the EE/VU/GS threads. Android normally
|
||||||
// leaves them unpinned on purpose (EAS puts the busiest thread on the prime core, and
|
// leaves them unpinned on purpose (EAS puts the busiest thread on the prime core, and
|
||||||
// pinning VU to a mid-tier big core measured ~1.4x slower), so this is EXPERIMENTAL and
|
// pinning VU to a mid-tier big core measured ~1.4x slower), so this is EXPERIMENTAL and
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ fun RendererBackendSection(state: MutableState<Settings>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GPU Turbo used to live here, on the reasoning that it belongs with the driver. It does
|
||||||
|
// technically -- it is a KGSL ioctl, not an emulator setting -- but the driver picker sits
|
||||||
|
// inside "Display & Resolution", so the net effect was a power/thermal lever filed under
|
||||||
|
// display options where nobody could find it. It is on the Performance tab now.
|
||||||
|
|
||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 6.dp, vertical = 8.dp),
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 6.dp, vertical = 8.dp),
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ internal val SETTINGS_SEARCH_INDEX: List<SettingsSearchEntry> = listOf(
|
|||||||
SettingsSearchEntry("renderer.section.overlayArt", true, SettingsCategory.Graphics),
|
SettingsSearchEntry("renderer.section.overlayArt", true, SettingsCategory.Graphics),
|
||||||
SettingsSearchEntry("renderer.section.shaderChain", true, SettingsCategory.Graphics),
|
SettingsSearchEntry("renderer.section.shaderChain", true, SettingsCategory.Graphics),
|
||||||
SettingsSearchEntry("renderer.section.rsxAccuracy", true, SettingsCategory.Graphics),
|
SettingsSearchEntry("renderer.section.rsxAccuracy", true, SettingsCategory.Graphics),
|
||||||
|
SettingsSearchEntry("renderer.gpuTurbo.label", true, SettingsCategory.Performance),
|
||||||
|
SettingsSearchEntry("perf.silenceLogs.label", true, SettingsCategory.Performance),
|
||||||
SettingsSearchEntry("renderer.writeColorBuffers.label", true, SettingsCategory.Graphics),
|
SettingsSearchEntry("renderer.writeColorBuffers.label", true, SettingsCategory.Graphics),
|
||||||
SettingsSearchEntry("renderer.writeDepthBuffer.label", true, SettingsCategory.Graphics),
|
SettingsSearchEntry("renderer.writeDepthBuffer.label", true, SettingsCategory.Graphics),
|
||||||
SettingsSearchEntry("renderer.readColorBuffers.label", true, SettingsCategory.Graphics),
|
SettingsSearchEntry("renderer.readColorBuffers.label", true, SettingsCategory.Graphics),
|
||||||
@@ -93,6 +95,7 @@ internal val SETTINGS_SEARCH_INDEX: List<SettingsSearchEntry> = listOf(
|
|||||||
SettingsSearchEntry("audio.synchronization.label", true, SettingsCategory.Audio),
|
SettingsSearchEntry("audio.synchronization.label", true, SettingsCategory.Audio),
|
||||||
SettingsSearchEntry("audio.buffer.label", true, SettingsCategory.Audio),
|
SettingsSearchEntry("audio.buffer.label", true, SettingsCategory.Audio),
|
||||||
SettingsSearchEntry("audio.renderer.label", true, SettingsCategory.Audio),
|
SettingsSearchEntry("audio.renderer.label", true, SettingsCategory.Audio),
|
||||||
|
SettingsSearchEntry("audio.cubebBackend.label", true, SettingsCategory.Audio),
|
||||||
SettingsSearchEntry("audio.format.label", true, SettingsCategory.Audio),
|
SettingsSearchEntry("audio.format.label", true, SettingsCategory.Audio),
|
||||||
SettingsSearchEntry("audio.channels.label", true, SettingsCategory.Audio),
|
SettingsSearchEntry("audio.channels.label", true, SettingsCategory.Audio),
|
||||||
SettingsSearchEntry("pad.section.playerRumble", true, SettingsCategory.Controls),
|
SettingsSearchEntry("pad.section.playerRumble", true, SettingsCategory.Controls),
|
||||||
@@ -163,6 +166,7 @@ internal val SETTINGS_SEARCH_INDEX: List<SettingsSearchEntry> = listOf(
|
|||||||
SettingsSearchEntry("adv.accurateRsxRsv.label", true, SettingsCategory.Advanced),
|
SettingsSearchEntry("adv.accurateRsxRsv.label", true, SettingsCategory.Advanced),
|
||||||
SettingsSearchEntry("adv.ppuRsvPriority.label", true, SettingsCategory.Advanced),
|
SettingsSearchEntry("adv.ppuRsvPriority.label", true, SettingsCategory.Advanced),
|
||||||
SettingsSearchEntry("adv.spuVerification.label", true, SettingsCategory.Advanced),
|
SettingsSearchEntry("adv.spuVerification.label", true, SettingsCategory.Advanced),
|
||||||
|
SettingsSearchEntry("adv.preciseSpuVerification.label", true, SettingsCategory.Advanced),
|
||||||
SettingsSearchEntry("adv.section.fpu", true, SettingsCategory.Advanced),
|
SettingsSearchEntry("adv.section.fpu", true, SettingsCategory.Advanced),
|
||||||
SettingsSearchEntry("adv.ppuNan.label", true, SettingsCategory.Advanced),
|
SettingsSearchEntry("adv.ppuNan.label", true, SettingsCategory.Advanced),
|
||||||
SettingsSearchEntry("adv.accurateDfma.label", true, SettingsCategory.Advanced),
|
SettingsSearchEntry("adv.accurateDfma.label", true, SettingsCategory.Advanced),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user