From 7f043730642281233b7f73b05c0cd6f641132b3c Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Tue, 2 Sep 2025 10:36:29 -0400 Subject: [PATCH] fix crash on settings change Now changing settings globally or per game doesn't crash the app. Works on the fly, without crashing. --- app/src/main/cpp/native-lib.cpp | 110 ++++++++++++++++++ .../psx2/GameSettingsDialogFragment.java | 43 +++++-- .../java/com/izzy2lost/psx2/MainActivity.java | 16 ++- .../java/com/izzy2lost/psx2/NativeApp.java | 24 +++- .../psx2/SettingsDialogFragment.java | 80 +++++-------- 5 files changed, 212 insertions(+), 61 deletions(-) diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index cad7982..14de71d 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -735,6 +735,108 @@ Java_com_izzy2lost_psx2_NativeApp_renderGpu(JNIEnv *env, jclass clazz, MTGS::ApplySettings(); } +// Apply a set of global settings in one shot to avoid repeated ApplySettings calls +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_applyGlobalSettingsBatch(JNIEnv* env, jclass, + jint renderer, + jfloat upscaleMultiplier, + jint aspectRatio, + jint blendingAccuracy, + jboolean widescreenPatches, + jboolean noInterlacingPatches, + jboolean loadTextures, + jboolean asyncTextureLoading, + jboolean hudVisible) +{ + // Clamp/normalize + if (upscaleMultiplier < 1.0f) upscaleMultiplier = 1.0f; + if (upscaleMultiplier > 12.0f) upscaleMultiplier = 12.0f; + if (blendingAccuracy < 0) blendingAccuracy = 0; if (blendingAccuracy > 5) blendingAccuracy = 5; + if (aspectRatio < 0) aspectRatio = 0; if (aspectRatio > 4) aspectRatio = 4; // 0..4 valid + + // Update in-memory settings layer + // Renderer may be -1 (Auto) or 12/13/14; store and set into EmuConfig for immediate effect + s_settings_interface.SetIntValue("EmuCore/GS", "Renderer", (int)renderer); + EmuConfig.GS.Renderer = static_cast(renderer); + + s_settings_interface.SetFloatValue("EmuCore/GS", "upscale_multiplier", upscaleMultiplier); + + // Aspect ratio as string per existing helpers + const char* aspect_ratio_names[] = { "Stretch", "Auto 4:3/3:2", "4:3", "16:9", "10:7" }; + s_settings_interface.SetStringValue("EmuCore/GS", "AspectRatio", aspect_ratio_names[aspectRatio]); + + // Blending accuracy numeric string 0..5 + s_settings_interface.SetStringValue("EmuCore/GS", "accurate_blending_unit", + StringUtil::StdStringFromFormat("%d", (int)blendingAccuracy).c_str()); + + // Widescreen, interlacing, textures + s_settings_interface.SetBoolValue("EmuCore", "EnableWideScreenPatches", (widescreenPatches == JNI_TRUE)); + s_settings_interface.SetBoolValue("EmuCore", "EnableNoInterlacingPatches", (noInterlacingPatches == JNI_TRUE)); + s_settings_interface.SetBoolValue("EmuCore/GS", "LoadTextureReplacements", (loadTextures == JNI_TRUE)); + s_settings_interface.SetBoolValue("EmuCore/GS", "LoadTextureReplacementsAsync", (asyncTextureLoading == JNI_TRUE)); + + // HUD/OSD bundle + const bool hv = (hudVisible == JNI_TRUE); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowSpeed", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowFPS", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowVPS", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowCPU", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowGPU", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowResolution", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowGSStats", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowIndicators", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowSettings", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowInputs", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowFrameTimes", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowVersion", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowHardwareInfo", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowVideoCapture", hv); + s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowInputRec", hv); + + // Apply once + if (VMManager::HasValidVM()) + VMManager::ApplySettings(); + if (MTGS::IsOpen()) + MTGS::ApplySettings(); +} + +// Apply per-game settings quickly without touching global-only fields +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_applyPerGameSettingsBatch(JNIEnv* env, jclass, + jint renderer, + jfloat upscaleMultiplier, + jint blendingAccuracy, + jboolean widescreenPatches, + jboolean noInterlacingPatches, + jboolean enablePatches, + jboolean enableCheats) +{ + if (upscaleMultiplier < 1.0f) upscaleMultiplier = 1.0f; + if (upscaleMultiplier > 12.0f) upscaleMultiplier = 12.0f; + if (blendingAccuracy < 0) blendingAccuracy = 0; if (blendingAccuracy > 5) blendingAccuracy = 5; + + // Renderer (allow -1/12/13/14) + s_settings_interface.SetIntValue("EmuCore/GS", "Renderer", (int)renderer); + EmuConfig.GS.Renderer = static_cast(renderer); + + // Core per-game options + s_settings_interface.SetFloatValue("EmuCore/GS", "upscale_multiplier", upscaleMultiplier); + s_settings_interface.SetStringValue("EmuCore/GS", "accurate_blending_unit", + StringUtil::StdStringFromFormat("%d", (int)blendingAccuracy).c_str()); + s_settings_interface.SetBoolValue("EmuCore", "EnableWideScreenPatches", (widescreenPatches == JNI_TRUE)); + s_settings_interface.SetBoolValue("EmuCore", "EnableNoInterlacingPatches", (noInterlacingPatches == JNI_TRUE)); + s_settings_interface.SetBoolValue("EmuCore", "EnablePatches", (enablePatches == JNI_TRUE)); + s_settings_interface.SetBoolValue("EmuCore", "EnableCheats", (enableCheats == JNI_TRUE)); + + // Apply once + if (VMManager::HasValidVM()) + VMManager::ApplySettings(); + if (MTGS::IsOpen()) + MTGS::ApplySettings(); +} + extern "C" JNIEXPORT void JNICALL Java_com_izzy2lost_psx2_NativeApp_onNativeSurfaceCreated(JNIEnv *env, jclass clazz) { @@ -772,6 +874,14 @@ Java_com_izzy2lost_psx2_NativeApp_onNativeSurfaceDestroyed(JNIEnv *env, jclass c } +extern "C" +JNIEXPORT jint JNICALL +Java_com_izzy2lost_psx2_NativeApp_getCurrentRenderer(JNIEnv*, jclass) +{ + return static_cast(EmuConfig.GS.Renderer); +} + + std::optional Host::AcquireRenderWindow(bool recreate_window) { float _fScale = 1.0; diff --git a/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java index 184c896..0a3d3a5 100644 --- a/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java @@ -15,7 +15,6 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -// Use Material 3 dialog builder for per‑game settings dialog import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.fragment.app.DialogFragment; @@ -200,18 +199,42 @@ public class GameSettingsDialogFragment extends DialogFragment { .setView(view) .setNegativeButton("Cancel", (d, w) -> d.dismiss()) .setPositiveButton("Save", (d, w) -> { - // Apply blending to runtime as well for immediate effect - NativeApp.setBlendingAccuracy(spBlendingAccuracy.getSelectedItemPosition()); + final int blendLevel = spBlendingAccuracy.getSelectedItemPosition(); + final int rendererIdx = spRenderer.getSelectedItemPosition(); + final int resIdx = spResolution.getSelectedItemPosition(); + final boolean wide = swWidescreenPatches.isChecked(); + final boolean noInt = swNoInterlacingPatches.isChecked(); + final boolean enablePatches = swEnablePatchCodes.isChecked(); + final boolean enableCheats = swEnableCheats.isChecked(); // Persist per-game INI explicitly (supports Auto as well) writeGameSettingsIni(ctx, gameSerial, gameCrc, - spBlendingAccuracy.getSelectedItemPosition(), - spRenderer.getSelectedItemPosition(), - spResolution.getSelectedItemPosition(), - swWidescreenPatches.isChecked(), - swNoInterlacingPatches.isChecked(), - /*enablePatches*/ swEnablePatchCodes.isChecked(), - swEnableCheats.isChecked()); + blendLevel, rendererIdx, resIdx, wide, noInt, + /*enablePatches*/ enablePatches, enableCheats); + + // Live-apply per-game settings in one batch + try { + int renderer; + // rendererIdx: 0=Auto,1=Vulkan,2=OpenGL,3=Software + if (rendererIdx == 0) renderer = -1; + else if (rendererIdx == 1) renderer = 14; + else if (rendererIdx == 2) renderer = 12; + else renderer = 13; + + float scale = Math.max(1, Math.min(8, resIdx + 1)); + NativeApp.applyPerGameSettingsBatch(renderer, scale, blendLevel, wide, noInt, enablePatches, enableCheats); + } catch (Throwable t) { + android.util.Log.e("GameSettings", "Per-game batch apply failed: " + t.getMessage()); + } + + // Refresh quick UI (renderer label) to reflect runtime renderer + try { + android.app.Activity a = getActivity(); + if (a instanceof MainActivity) { + ((MainActivity) a).runOnUiThread(() -> ((MainActivity) a).refreshQuickUi()); + } + } catch (Throwable ignored) {} + d.dismiss(); }) .setNeutralButton("Reset to Global", (d, w) -> { diff --git a/app/src/main/java/com/izzy2lost/psx2/MainActivity.java b/app/src/main/java/com/izzy2lost/psx2/MainActivity.java index e0b8745..1f4fa1f 100644 --- a/app/src/main/java/com/izzy2lost/psx2/MainActivity.java +++ b/app/src/main/java/com/izzy2lost/psx2/MainActivity.java @@ -825,10 +825,24 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF private void updateRendererButtonLabel() { MaterialButton btn_bios = findViewById(R.id.btn_bios); if (btn_bios != null) { - btn_bios.setText(rendererShortLabel(getCurrentRendererPref())); + int current = getCurrentRendererPref(); + try { + // Prefer runtime renderer from core to reflect per-game overrides + int runtime = NativeApp.getCurrentRenderer(); + // Only accept expected values + if (runtime == -1 || runtime == 12 || runtime == 13 || runtime == 14) { + current = runtime; + } + } catch (Throwable ignored) {} + btn_bios.setText(rendererShortLabel(current)); } } + // Public minimal UI refresh hook for dialogs + public void refreshQuickUi() { + updateRendererButtonLabel(); + } + private void setRendererAndSave(int renderer) { SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE); prefs.edit().putInt("renderer", renderer).apply(); diff --git a/app/src/main/java/com/izzy2lost/psx2/NativeApp.java b/app/src/main/java/com/izzy2lost/psx2/NativeApp.java index 3381b7c..bd97d04 100644 --- a/app/src/main/java/com/izzy2lost/psx2/NativeApp.java +++ b/app/src/main/java/com/izzy2lost/psx2/NativeApp.java @@ -37,7 +37,6 @@ public class NativeApp { public static native void initialize(String path, int apiVer); public static native String getGameTitle(String path); - // New: Resolve game title from a URI (content:// or file://). Implement in native when available. public static native String getGameTitleFromUri(String gameUri); public static native String getGameSerial(); public static native float getFPS(); @@ -78,6 +77,29 @@ public class NativeApp { public static native void setShadeBoostContrast(int contrast); public static native void setShadeBoostSaturation(int saturation); + // Apply multiple settings in one atomic batch (safer live updates) + public static native void applyGlobalSettingsBatch(int renderer, + float upscaleMultiplier, + int aspectRatio, + int blendingAccuracy, + boolean widescreenPatches, + boolean noInterlacingPatches, + boolean loadTextures, + boolean asyncTextureLoading, + boolean hudVisible); + + // Apply per-game settings (subset) in one batch + public static native void applyPerGameSettingsBatch(int renderer, + float upscaleMultiplier, + int blendingAccuracy, + boolean widescreenPatches, + boolean noInterlacingPatches, + boolean enablePatches, + boolean enableCheats); + + // Query current runtime renderer from the core (reflects global/per-game) + public static native int getCurrentRenderer(); + // Per-game settings public static native void saveGameSettings(String filename, int blendingAccuracy, int renderer, int resolution, boolean widescreenPatches, diff --git a/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java index 104fec0..9f44230 100644 --- a/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java @@ -15,7 +15,6 @@ import android.content.res.ColorStateList; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -// Use MaterialAlertDialogBuilder for Material 3 dialogs. import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.fragment.app.DialogFragment; import androidx.core.widget.CompoundButtonCompat; @@ -39,8 +38,8 @@ public class SettingsDialogFragment extends DialogFragment { float scale = prefs.getFloat("upscale_multiplier", 1.0f); int aspectRatio = prefs.getInt("aspect_ratio", 1); int blendingAccuracy = prefs.getInt("blending_accuracy", 1); // 0..5 - boolean widescreenPatches = prefs.getBoolean("widescreen_patches", false); - boolean noInterlacingPatches = prefs.getBoolean("no_interlacing_patches", false); + boolean widescreenPatches = prefs.getBoolean("widescreen_patches", true); + boolean noInterlacingPatches = prefs.getBoolean("no_interlacing_patches", true); boolean loadTextures = prefs.getBoolean("load_textures", false); boolean asyncTextureLoading = prefs.getBoolean("async_texture_loading", true); boolean hudVisible = prefs.getBoolean("hud_visible", false); @@ -184,8 +183,8 @@ public class SettingsDialogFragment extends DialogFragment { int savedRenderer = prefs.getInt("renderer", RENDERER_AUTO); float savedScale = prefs.getFloat("upscale_multiplier", 1.0f); int savedAspectRatio = prefs.getInt("aspect_ratio", 1); // 1 = Auto 4:3/3:2 (recommended) - boolean savedWidescreen = prefs.getBoolean("widescreen_patches", false); - boolean savedNoInterlacing = prefs.getBoolean("no_interlacing_patches", false); + boolean savedWidescreen = prefs.getBoolean("widescreen_patches", true); + boolean savedNoInterlacing = prefs.getBoolean("no_interlacing_patches", true); boolean savedLoadTextures = prefs.getBoolean("load_textures", false); boolean savedAsyncTextureLoading = prefs.getBoolean("async_texture_loading", true); boolean savedHud = prefs.getBoolean("hud_visible", false); @@ -217,7 +216,7 @@ public class SettingsDialogFragment extends DialogFragment { MaterialAlertDialogBuilder b = new MaterialAlertDialogBuilder(requireContext(), com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog); - b.setTitle("Graphics Settings") + b.setTitle("Global Settings") .setView(view) .setNegativeButton("Cancel", (d, w) -> d.dismiss()) .setPositiveButton("Save", (d, w) -> { @@ -236,52 +235,35 @@ public class SettingsDialogFragment extends DialogFragment { boolean asyncTextureLoading = swAsyncTextureLoading.isChecked(); boolean hudVisible = (swDevHud != null && swDevHud.isChecked()); - // Debug logging - android.util.Log.d("SettingsDialog", "Saving renderer setting: " + renderer + - " (-1=Auto, 12=OpenGL, 13=Software, 14=Vulkan)"); - - // Save renderer setting first with commit() to ensure immediate write - prefs.edit().putInt("renderer", renderer).apply(); - android.util.Log.d("SettingsDialog", "Renderer setting saved successfully"); - - // Small delay to ensure setting is persisted + // Persist settings to SharedPreferences + int blendingLevel = spBlending.getSelectedItemPosition(); + prefs.edit() + .putInt("renderer", renderer) + .putFloat("upscale_multiplier", scale) + .putInt("aspect_ratio", aspectRatio) + .putInt("blending_accuracy", blendingLevel) + .putBoolean("widescreen_patches", widescreenPatches) + .putBoolean("no_interlacing_patches", noInterlacingPatches) + .putBoolean("load_textures", loadTextures) + .putBoolean("async_texture_loading", asyncTextureLoading) + .putBoolean("hud_visible", hudVisible) + .apply(); + + // Apply in one batch to avoid repeated ApplySettings calls try { - Thread.sleep(100); - } catch (InterruptedException ignored) {} - - // Apply renderer change (might crash, but setting is already saved) - try { - NativeApp.renderGpu(renderer); - android.util.Log.d("SettingsDialog", "Applied renderer change to: " + renderer); - } catch (Exception e) { - android.util.Log.e("SettingsDialog", "Failed to apply renderer: " + e.getMessage()); - // If renderer change fails, we'll still have the setting saved - // for next app restart + NativeApp.applyGlobalSettingsBatch(renderer, scale, aspectRatio, blendingLevel, + widescreenPatches, noInterlacingPatches, loadTextures, asyncTextureLoading, hudVisible); + } catch (Throwable t) { + android.util.Log.e("SettingsDialog", "Batch apply failed: " + t.getMessage()); } - - // Persist all other settings - int blendingLevel = spBlending.getSelectedItemPosition(); - prefs.edit() - .putFloat("upscale_multiplier", scale) - .putInt("aspect_ratio", aspectRatio) - .putInt("blending_accuracy", blendingLevel) - .putBoolean("widescreen_patches", widescreenPatches) - .putBoolean("no_interlacing_patches", noInterlacingPatches) - .putBoolean("load_textures", loadTextures) - .putBoolean("async_texture_loading", asyncTextureLoading) - .putBoolean("hud_visible", hudVisible) - .apply(); - - // Apply other settings - NativeApp.renderUpscalemultiplier(scale); - NativeApp.setAspectRatio(aspectRatio); - NativeApp.setBlendingAccuracy(blendingLevel); - NativeApp.setWidescreenPatches(widescreenPatches); - NativeApp.setNoInterlacingPatches(noInterlacingPatches); - NativeApp.setLoadTextures(loadTextures); - NativeApp.setAsyncTextureLoading(asyncTextureLoading); - NativeApp.setHudVisible(hudVisible); + // Refresh quick UI (renderer label) if hosting activity is MainActivity + try { + android.app.Activity a = getActivity(); + if (a instanceof MainActivity) { + ((MainActivity) a).runOnUiThread(() -> ((MainActivity) a).refreshQuickUi()); + } + } catch (Throwable ignored) {} }); return b.create();