diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt index 29753acb6..e944b2cf5 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt @@ -6,6 +6,7 @@ import com.sun.jna.Native import org.kenjinx.android.viewmodels.GameInfo import java.util.Collections import android.view.Surface +import android.util.Log interface KenjinxNativeJna : Library { fun deviceInitialize( @@ -69,6 +70,14 @@ interface KenjinxNativeJna : Library { fun deviceCloseEmulation() fun deviceReinitEmulation() fun deviceSignalEmulationClose() + + // >>> Rendering-bezogene Ergänzungen für den Toggle: + fun deviceWaitForGpuDone(timeoutMs: Int) + fun deviceRecreateSwapchain() + fun graphicsSetBackendThreading(mode: Int) + fun graphicsSetPresentEnabled(enabled: Boolean) + // <<< + fun userGetOpenedUser(): String fun userGetUserPicture(userId: String): String fun userSetUserPicture(userId: String, picture: String) @@ -108,6 +117,104 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance { fun loggingSetEnabled(logLevel: LogLevel, enabled: Boolean) = loggingSetEnabled(logLevel.ordinal, enabled) + // --- Rendering: Single-Thread-Option & sichere Wrapper -------------------- + + // 0 = Auto, 1 = SingleThread (Disable Threaded), 2 = Threaded + private const val THREADING_AUTO = 0 + private const val THREADING_SINGLE = 1 + private const val THREADING_THREADED = 2 + + override fun graphicsSetBackendThreading(mode: Int) { + try { + jnaInstance.graphicsSetBackendThreading(mode) + } catch (_: Throwable) { + Log.w("KenjinxNative", "graphicsSetBackendThreading not available") + } + } + + override fun deviceRecreateSwapchain() { + try { jnaInstance.deviceRecreateSwapchain() } catch (_: Throwable) { /* ignore */ } + } + + override fun deviceWaitForGpuDone(timeoutMs: Int) { + try { jnaInstance.deviceWaitForGpuDone(timeoutMs) } catch (_: Throwable) { /* ignore */ } + } + + override fun graphicsSetPresentEnabled(enabled: Boolean) { + try { jnaInstance.graphicsSetPresentEnabled(enabled) } catch (_: Throwable) { /* ignore */ } + } + + // Sichere deviceResize-Implementierung (reines Rendering) + override fun deviceResize(width: Int, height: Int) { + try { + graphicsRendererSetSize(width, height) + inputSetClientSize(width, height) + } catch (_: Throwable) { /* ignore */ } + } + + // Robustes graphicsInitialize mit QCOM-Heuristik + Fallback → SingleThread + override fun graphicsInitialize( + rescale: Float, + maxAnisotropy: Float, + fastGpuTime: Boolean, + fast2DCopy: Boolean, + enableMacroJit: Boolean, + enableMacroHLE: Boolean, + enableShaderCache: Boolean, + enableTextureRecompression: Boolean, + backendThreading: Int + ): Boolean { + val requested = backendThreading + val isQcom = "qcom".equals(android.os.Build.HARDWARE, true) + + // Heuristik: Auf QCOM bei „Auto“ zunächst SingleThread probieren, + // explizit gesetzte Werte bleiben unberührt. + val firstChoice = + if (isQcom && requested == THREADING_AUTO) THREADING_SINGLE else requested + + Log.i( + "KenjinxNative", + "graphicsInitialize: request=$requested firstChoice=$firstChoice hw=${android.os.Build.HARDWARE}" + ) + + return try { + jnaInstance.graphicsInitialize( + rescale, + maxAnisotropy, + fastGpuTime, + fast2DCopy, + enableMacroJit, + enableMacroHLE, + enableShaderCache, + enableTextureRecompression, + firstChoice + ) + } catch (t: Throwable) { + Log.e( + "KenjinxNative", + "graphicsInitialize failed (firstChoice=$firstChoice). Fallback → SingleThread", + t + ) + try { + jnaInstance.graphicsInitialize( + rescale, + maxAnisotropy, + fastGpuTime, + fast2DCopy, + enableMacroJit, + enableMacroHLE, + enableShaderCache, + enableTextureRecompression, + THREADING_SINGLE + ) + } catch (t2: Throwable) { + Log.e("KenjinxNative", "graphicsInitialize fallback failed", t2) + false + } + } + } + // ------------------------------------------------------------------------- + @JvmStatic fun frameEnded() = MainActivity.frameEnded() @@ -124,6 +231,7 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance { val text = NativeHelpers.instance.getStringJava(infoPtr) MainActivity.mainViewModel?.gameHost?.setProgress(text, progress) } + @JvmStatic fun onSurfaceSizeChanged(width: Int, height: Int) { // No-Op: Placeholder – Hook if needed. diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/MainViewModel.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/MainViewModel.kt index d7bcb18fa..e81b49109 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/MainViewModel.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/MainViewModel.kt @@ -117,13 +117,16 @@ class MainViewModel(val activity: MainActivity) { settings.overrideSettings(forceNceAndPptc) } + // 0=Auto, 1=SingleThread, 2=Threaded + val backendMode = if (settings.disableThreadedRendering) 1 else 2 + var success = KenjinxNative.graphicsInitialize( enableMacroHLE = settings.enableMacroHLE, enableShaderCache = settings.enableShaderCache, enableTextureRecompression = settings.enableTextureRecompression, rescale = settings.resScale, maxAnisotropy = settings.maxAnisotropy, - backendThreading = org.kenjinx.android.BackendThreading.Auto.ordinal + backendThreading = backendMode ) if (!success) @@ -227,13 +230,16 @@ class MainViewModel(val activity: MainActivity) { val settings = QuickSettings(activity) + // 0=Auto, 1=SingleThread, 2=Threaded + val backendMode = if (settings.disableThreadedRendering) 1 else 2 + var success = KenjinxNative.graphicsInitialize( enableMacroHLE = settings.enableMacroHLE, enableShaderCache = settings.enableShaderCache, enableTextureRecompression = settings.enableTextureRecompression, rescale = settings.resScale, maxAnisotropy = settings.maxAnisotropy, - backendThreading = org.kenjinx.android.BackendThreading.Auto.ordinal + backendThreading = backendMode ) if (!success) diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/QuickSettings.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/QuickSettings.kt index 762407ecb..1f3ceb1ff 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/QuickSettings.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/QuickSettings.kt @@ -63,6 +63,9 @@ class QuickSettings(val activity: Activity) { var enableDebugLogs: Boolean var enableGraphicsLogs: Boolean + // --- NEU: Threaded Rendering Toggle (persistiert) + var disableThreadedRendering: Boolean + private var sharedPref: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) init { @@ -108,6 +111,9 @@ class QuickSettings(val activity: Activity) { enableTraceLogs = sharedPref.getBoolean("enableStubLogs", false) enableDebugLogs = sharedPref.getBoolean("enableDebugLogs", false) enableGraphicsLogs = sharedPref.getBoolean("enableGraphicsLogs", false) + + // --- NEU laden + disableThreadedRendering = sharedPref.getBoolean("disableThreadedRendering", false) } fun save() { @@ -151,6 +157,9 @@ class QuickSettings(val activity: Activity) { putBoolean("enableTraceLogs", enableTraceLogs) putBoolean("enableDebugLogs", enableDebugLogs) putBoolean("enableGraphicsLogs", enableGraphicsLogs) + + // --- NEU speichern + putBoolean("disableThreadedRendering", disableThreadedRendering) } } diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/SettingViews.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/SettingViews.kt index 2d0f4f565..b6a23833e 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/SettingViews.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/SettingViews.kt @@ -47,6 +47,7 @@ import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -70,8 +71,8 @@ import org.kenjinx.android.viewmodels.DataResetState import org.kenjinx.android.viewmodels.FirmwareInstallState import org.kenjinx.android.viewmodels.KeyInstallState import org.kenjinx.android.viewmodels.MainViewModel -import org.kenjinx.android.viewmodels.MemoryConfiguration import org.kenjinx.android.viewmodels.SettingsViewModel +import org.kenjinx.android.viewmodels.MemoryConfiguration import org.kenjinx.android.viewmodels.MemoryManagerMode import org.kenjinx.android.viewmodels.VSyncMode import org.kenjinx.android.widgets.ActionButton @@ -89,6 +90,9 @@ import org.kenjinx.android.viewmodels.QuickSettings.OverlayMenuPosition // ← N import org.kenjinx.android.SystemLanguage import org.kenjinx.android.RegionCode +// >>> NEU: KenjinxNative für das Live-Umschalten des Threading-Backends +import org.kenjinx.android.KenjinxNative + class SettingViews { companion object { const val EXPANSTION_TRANSITION_DURATION = 450 @@ -159,6 +163,12 @@ class SettingViews { mutableFloatStateOf(QuickSettings(mainViewModel.activity).overlayMenuOpacity.coerceIn(0f, 1f)) } + // --- NEU: Disable Threaded Rendering (aus QuickSettings laden) + val disableThreadedRendering = remember { + mutableStateOf(QuickSettings(mainViewModel.activity).disableThreadedRendering) + } + val threadToggleInitialized = remember { mutableStateOf(false) } + if (!loaded.value) { settingsViewModel.initializeState( memoryManagerMode, @@ -1321,6 +1331,34 @@ class SettingViews { enableShaderCache.SwitchSelector(label = "Shader Cache") enableTextureRecompression.SwitchSelector(label = "Texture Recompression") enableMacroHLE.SwitchSelector(label = "Macro HLE") + + // --- NEU: Toggle für Single-Thread-Renderer + disableThreadedRendering.SwitchSelector(label = "Disable Threaded Rendering") + + // Reaktion auf Toggle: persistieren + sanftes Reconfigure + LaunchedEffect(disableThreadedRendering.value) { + if (!threadToggleInitialized.value) { + threadToggleInitialized.value = true + } else { + val qs = QuickSettings(mainViewModel.activity) + qs.disableThreadedRendering = disableThreadedRendering.value + qs.save() + + val mode = if (disableThreadedRendering.value) 1 /*Single*/ else 2 /*Threaded*/ + + thread { + try { + KenjinxNative.graphicsSetPresentEnabled(false) + KenjinxNative.deviceWaitForGpuDone(500) + KenjinxNative.graphicsSetBackendThreading(mode) + KenjinxNative.deviceRecreateSwapchain() + } finally { + KenjinxNative.graphicsSetPresentEnabled(true) + } + } + } + } + stretchToFullscreen.SwitchSelector(label = "Stretch to Fullscreen") ResolutionScaleDropdown( selectedScale = resScale.floatValue,