mirror of
https://git.ryujinx.app/kenji-nx/ryujinx.git
synced 2025-12-16 04:37:02 +00:00
changed comments to english
This commit is contained in:
parent
d28cfb1c94
commit
77dea2787f
10 changed files with 79 additions and 81 deletions
|
|
@ -17,7 +17,7 @@
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<!-- Notifications + special-use Foreground Service (Android 14+) -->
|
<!-- Notifications + special-use Foreground Service (Android 14+) -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<!-- Erforderlich für startForeground() -->
|
<!-- Required for startForeground() -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
|
@ -88,8 +88,6 @@
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:stopWithTask="true"
|
android:stopWithTask="true"
|
||||||
android:foregroundServiceType="mediaPlayback" />
|
android:foregroundServiceType="mediaPlayback" />
|
||||||
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
|
|
||||||
private val mainHandler = Handler(Looper.getMainLooper())
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
// ---- Foreground-Service Binding ----
|
// ---- Foreground service binding ----
|
||||||
private var emuBound = false
|
private var emuBound = false
|
||||||
private var emuBinder: EmulationService.LocalBinder? = null
|
private var emuBinder: EmulationService.LocalBinder? = null
|
||||||
private var _startedViaService = false
|
private var _startedViaService = false
|
||||||
|
|
@ -52,7 +52,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
emuBound = true
|
emuBound = true
|
||||||
ghLog("EmulationService bound")
|
ghLog("EmulationService bound")
|
||||||
|
|
||||||
// Falls Start bereits vorbereitet wurde und noch kein Loop läuft → jetzt im Service starten
|
// If startup is already prepared and no loop is running yet → start it in the service now
|
||||||
if (_isStarted && !_startedViaService && _guestThread == null) {
|
if (_isStarted && !_startedViaService && _guestThread == null) {
|
||||||
startRunLoopInService()
|
startRunLoopInService()
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize-Stabilizer
|
// Resize stabilizer
|
||||||
private var stabilizerActive = false
|
private var stabilizerActive = false
|
||||||
|
|
||||||
// last known Android rotation (0,1,2,3)
|
// last known Android rotation (0,1,2,3)
|
||||||
|
|
@ -143,7 +143,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
|
|
||||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||||
ghLog("surfaceCreated")
|
ghLog("surfaceCreated")
|
||||||
// Früh binden, damit der Service schon steht, bevor wir starten
|
// Bind early so the service is ready before we start
|
||||||
ensureServiceStartedAndBound()
|
ensureServiceStartedAndBound()
|
||||||
rebindNativeWindow(force = true)
|
rebindNativeWindow(force = true)
|
||||||
}
|
}
|
||||||
|
|
@ -152,26 +152,26 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
ghLog("surfaceChanged ${width}x$height")
|
ghLog("surfaceChanged ${width}x$height")
|
||||||
if (_isClosed) return
|
if (_isClosed) return
|
||||||
|
|
||||||
// IMMER neu binden – auch wenn die Größe gleich bleibt
|
// ALWAYS rebind—even if the size stays the same
|
||||||
rebindNativeWindow(force = true)
|
rebindNativeWindow(force = true)
|
||||||
|
|
||||||
val sizeChanged = (_width != width || _height != height)
|
val sizeChanged = (_width != width || _height != height)
|
||||||
_width = width
|
_width = width
|
||||||
_height = height
|
_height = height
|
||||||
|
|
||||||
// Service sicherstellen & Renderstart
|
// Ensure service is running & start rendering
|
||||||
ensureServiceStartedAndBound()
|
ensureServiceStartedAndBound()
|
||||||
start(holder)
|
start(holder)
|
||||||
|
|
||||||
// Resize stabilisieren (übernimmt plausibles final size set)
|
// Stabilize resize (applies plausible final size)
|
||||||
startStabilizedResize(expectedRotation = lastRotation)
|
startStabilizedResize(expectedRotation = lastRotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
ghLog("surfaceDestroyed → shutdownBinding()")
|
ghLog("surfaceDestroyed → shutdownBinding()")
|
||||||
// Immer binden lösen (verhindert Leaks beim Task-Swipe)
|
// Always unbind (prevents leaks when swiping away the task)
|
||||||
shutdownBinding()
|
shutdownBinding()
|
||||||
// Eigentliche Emu-Beendigung passiert via close() / Exit Game
|
// Actual emulation shutdown happens via close() / Exit Game
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowVisibilityChanged(visibility: Int) {
|
override fun onWindowVisibilityChanged(visibility: Int) {
|
||||||
|
|
@ -214,12 +214,12 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
private fun start(surfaceHolder: SurfaceHolder) {
|
private fun start(surfaceHolder: SurfaceHolder) {
|
||||||
if (_isStarted) return
|
if (_isStarted) return
|
||||||
|
|
||||||
// NICHT gleich _isStarted = true → erst alles vorbereiten
|
// Do NOT set _isStarted = true immediately → prepare everything first
|
||||||
rebindNativeWindow(force = true)
|
rebindNativeWindow(force = true)
|
||||||
|
|
||||||
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel
|
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel
|
||||||
|
|
||||||
// Input initialisieren
|
// Initialize input
|
||||||
KenjinxNative.inputInitialize(width, height)
|
KenjinxNative.inputInitialize(width, height)
|
||||||
_inputInitialized = true
|
_inputInitialized = true
|
||||||
|
|
||||||
|
|
@ -235,7 +235,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
KenjinxNative.setSurfaceRotationByAndroidRotation(currentRot ?: 0)
|
KenjinxNative.setSurfaceRotationByAndroidRotation(currentRot ?: 0)
|
||||||
try { KenjinxNative.deviceSetWindowHandle(currentWindowHandle) } catch (_: Throwable) {}
|
try { KenjinxNative.deviceSetWindowHandle(currentWindowHandle) } catch (_: Throwable) {}
|
||||||
|
|
||||||
// Sanfter Kick nur wenn Renderer READY **und** Input init
|
// Gentle kick only when renderer is READY **and** input is initialized
|
||||||
if (width > 0 && height > 0 &&
|
if (width > 0 && height > 0 &&
|
||||||
MainActivity.mainViewModel?.rendererReady == true &&
|
MainActivity.mainViewModel?.rendererReady == true &&
|
||||||
_inputInitialized
|
_inputInitialized
|
||||||
|
|
@ -247,10 +247,10 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
val qs = org.kenjinx.android.viewmodels.QuickSettings(mainViewModel.activity)
|
val qs = org.kenjinx.android.viewmodels.QuickSettings(mainViewModel.activity)
|
||||||
try { KenjinxNative.graphicsSetFullscreenStretch(qs.stretchToFullscreen) } catch (_: Throwable) {}
|
try { KenjinxNative.graphicsSetFullscreenStretch(qs.stretchToFullscreen) } catch (_: Throwable) {}
|
||||||
|
|
||||||
// Host gilt nun als „gestartet“
|
// Host is now considered 'started'
|
||||||
_isStarted = true
|
_isStarted = true
|
||||||
|
|
||||||
// Immer bevorzugt im Service starten; wenn Bind noch nicht fertig → kurz warten, dann fallback
|
// Prefer starting in the service; if binding isn't ready yet → wait briefly, then fall back
|
||||||
if (emuBound) {
|
if (emuBound) {
|
||||||
startRunLoopInService()
|
startRunLoopInService()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -260,7 +260,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
if (emuBound) {
|
if (emuBound) {
|
||||||
startRunLoopInService()
|
startRunLoopInService()
|
||||||
} else {
|
} else {
|
||||||
// Fallback: lokaler Thread (sollte selten passieren)
|
// Fallback: local thread (should be rare)
|
||||||
ghLog("Fallback: starting RunLoop in local thread")
|
ghLog("Fallback: starting RunLoop in local thread")
|
||||||
_guestThread = thread(start = true, name = "KenjinxGuest") { runGame() }
|
_guestThread = thread(start = true, name = "KenjinxGuest") { runGame() }
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +303,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
|
|
||||||
KenjinxNative.uiHandlerSetResponse(false, "")
|
KenjinxNative.uiHandlerSetResponse(false, "")
|
||||||
|
|
||||||
// Emulation im Service stoppen (falls dort gestartet)
|
// Stop emulation in the service (if started there)
|
||||||
try {
|
try {
|
||||||
if (emuBound && _startedViaService) {
|
if (emuBound && _startedViaService) {
|
||||||
emuBinder?.stopEmulation {
|
emuBinder?.stopEmulation {
|
||||||
|
|
@ -312,14 +312,14 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
}
|
}
|
||||||
} catch (_: Throwable) { }
|
} catch (_: Throwable) { }
|
||||||
|
|
||||||
// Fallback: lokaler Thread beenden
|
// Fallback: stop local thread
|
||||||
try { _updateThread?.join(200) } catch (_: Throwable) {}
|
try { _updateThread?.join(200) } catch (_: Throwable) {}
|
||||||
try { _renderingThreadWatcher?.join(200) } catch (_: Throwable) {}
|
try { _renderingThreadWatcher?.join(200) } catch (_: Throwable) {}
|
||||||
|
|
||||||
// Bindung lösen
|
// Unbind
|
||||||
shutdownBinding()
|
shutdownBinding()
|
||||||
|
|
||||||
// Service explizit beenden (falls noch läuft)
|
// Explicitly stop the service (if still running)
|
||||||
try {
|
try {
|
||||||
mainViewModel.activity.stopService(Intent(mainViewModel.activity, EmulationService::class.java))
|
mainViewModel.activity.stopService(Intent(mainViewModel.activity, EmulationService::class.java))
|
||||||
} catch (_: Throwable) { }
|
} catch (_: Throwable) { }
|
||||||
|
|
@ -429,7 +429,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||||
|
|
||||||
attempts++
|
attempts++
|
||||||
|
|
||||||
// 1 stabiler Tick oder max. 12 Versuche
|
// One stable tick or max. 12 attempts
|
||||||
if ((stableCount >= 1 || attempts >= 12) && w > 0 && h > 0) {
|
if ((stableCount >= 1 || attempts >= 12) && w > 0 && h > 0) {
|
||||||
ghLog("resize stabilized after $attempts ticks → ${w}x$h")
|
ghLog("resize stabilized after $attempts ticks → ${w}x$h")
|
||||||
safeSetSize(w, h)
|
safeSetSize(w, h)
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ interface KenjinxNativeJna : Library {
|
||||||
fun amiiboLoadBin(bytes: ByteArray, length: Int): Boolean
|
fun amiiboLoadBin(bytes: ByteArray, length: Int): Boolean
|
||||||
fun amiiboClear()
|
fun amiiboClear()
|
||||||
|
|
||||||
// AUDIO (neu): direkte JNA-Brücke zu C#-Exports
|
// AUDIO (new): direct JNA bridge to C# exports
|
||||||
fun audioSetPaused(paused: Boolean)
|
fun audioSetPaused(paused: Boolean)
|
||||||
fun audioSetMuted(muted: Boolean)
|
fun audioSetMuted(muted: Boolean)
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +115,7 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance {
|
||||||
fun loggingSetEnabled(logLevel: LogLevel, enabled: Boolean) =
|
fun loggingSetEnabled(logLevel: LogLevel, enabled: Boolean) =
|
||||||
loggingSetEnabled(logLevel.ordinal, enabled)
|
loggingSetEnabled(logLevel.ordinal, enabled)
|
||||||
|
|
||||||
// --- Rendering: Single-Thread-Option & sichere Wrapper --------------------
|
// --- Rendering: single-thread option & safe wrappers --------------------
|
||||||
|
|
||||||
// 0 = Auto, 1 = SingleThread (Disable Threaded), 2 = Threaded
|
// 0 = Auto, 1 = SingleThread (Disable Threaded), 2 = Threaded
|
||||||
private const val THREADING_AUTO = 0
|
private const val THREADING_AUTO = 0
|
||||||
|
|
@ -142,7 +142,7 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance {
|
||||||
try { jnaInstance.graphicsSetPresentEnabled(enabled) } catch (_: Throwable) { /* ignore */ }
|
try { jnaInstance.graphicsSetPresentEnabled(enabled) } catch (_: Throwable) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sichere deviceResize-Implementierung (reines Rendering)
|
// Safe deviceResize implementation (rendering-only)
|
||||||
override fun deviceResize(width: Int, height: Int) {
|
override fun deviceResize(width: Int, height: Int) {
|
||||||
try {
|
try {
|
||||||
graphicsRendererSetSize(width, height)
|
graphicsRendererSetSize(width, height)
|
||||||
|
|
@ -150,7 +150,7 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance {
|
||||||
} catch (_: Throwable) { /* ignore */ }
|
} catch (_: Throwable) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Robustes graphicsInitialize mit QCOM-Heuristik + Fallback → SingleThread
|
// Robust graphicsInitialize with QCOM heuristic + fallback → SingleThread
|
||||||
override fun graphicsInitialize(
|
override fun graphicsInitialize(
|
||||||
rescale: Float,
|
rescale: Float,
|
||||||
maxAnisotropy: Float,
|
maxAnisotropy: Float,
|
||||||
|
|
@ -165,8 +165,8 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance {
|
||||||
val requested = backendThreading
|
val requested = backendThreading
|
||||||
val isQcom = "qcom".equals(android.os.Build.HARDWARE, true)
|
val isQcom = "qcom".equals(android.os.Build.HARDWARE, true)
|
||||||
|
|
||||||
// Heuristik: Auf QCOM bei „Auto“ zunächst SingleThread probieren,
|
// Heuristic: On QCOM with “Auto”, first try SingleThread;
|
||||||
// explizit gesetzte Werte bleiben unberührt.
|
// explicitly set values remain untouched.
|
||||||
val firstChoice =
|
val firstChoice =
|
||||||
if (isQcom && requested == THREADING_AUTO) THREADING_SINGLE else requested
|
if (isQcom && requested == THREADING_AUTO) THREADING_SINGLE else requested
|
||||||
|
|
||||||
|
|
@ -212,7 +212,7 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- optionale Wrapper für Audio (safer logging) ---
|
// --- optional wrappers for audio (safer logging) ---
|
||||||
override fun audioSetPaused(paused: Boolean) {
|
override fun audioSetPaused(paused: Boolean) {
|
||||||
try { jnaInstance.audioSetPaused(paused) }
|
try { jnaInstance.audioSetPaused(paused) }
|
||||||
catch (t: Throwable) { Log.w("KenjinxNative", "audioSetPaused unavailable", t) }
|
catch (t: Throwable) { Log.w("KenjinxNative", "audioSetPaused unavailable", t) }
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class MainActivity : BaseActivity() {
|
||||||
var storageHelper: SimpleStorageHelper? = null
|
var storageHelper: SimpleStorageHelper? = null
|
||||||
lateinit var uiHandler: UiHandler
|
lateinit var uiHandler: UiHandler
|
||||||
|
|
||||||
// Persistenz für Zombie-Erkennung
|
// Persistence for zombie detection
|
||||||
private val PREFS = "emu_core"
|
private val PREFS = "emu_core"
|
||||||
private val KEY_EMU_RUNNING = "emu_running"
|
private val KEY_EMU_RUNNING = "emu_running"
|
||||||
|
|
||||||
|
|
@ -297,7 +297,7 @@ class MainActivity : BaseActivity() {
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
// Apply alignment
|
// Apply orientation preference
|
||||||
applyOrientationPreference()
|
applyOrientationPreference()
|
||||||
|
|
||||||
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
||||||
|
|
@ -361,9 +361,9 @@ class MainActivity : BaseActivity() {
|
||||||
|
|
||||||
// --- Audio foreground/background gating ---
|
// --- Audio foreground/background gating ---
|
||||||
private fun setAudioForegroundState(inForeground: Boolean) {
|
private fun setAudioForegroundState(inForeground: Boolean) {
|
||||||
// bevorzugt: pausieren statt nur muten
|
// Prefer: pause instead of just mute
|
||||||
try { KenjinxNative.audioSetPaused(!inForeground) } catch (_: Throwable) {}
|
try { KenjinxNative.audioSetPaused(!inForeground) } catch (_: Throwable) {}
|
||||||
// fallback: Master-Mute
|
// Fallback: master mute
|
||||||
try { KenjinxNative.audioSetMuted(!inForeground) } catch (_: Throwable) {}
|
try { KenjinxNative.audioSetMuted(!inForeground) } catch (_: Throwable) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -392,7 +392,7 @@ class MainActivity : BaseActivity() {
|
||||||
setPresentEnabled(false, "onStop")
|
setPresentEnabled(false, "onStop")
|
||||||
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
|
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
|
||||||
}
|
}
|
||||||
// WICHTIG: Bindung sicher lösen (verhindert Leak)
|
// IMPORTANT: unbind safely (prevents leak)
|
||||||
try { mainViewModel?.gameHost?.shutdownBinding() } catch (_: Throwable) {}
|
try { mainViewModel?.gameHost?.shutdownBinding() } catch (_: Throwable) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -472,7 +472,7 @@ class MainActivity : BaseActivity() {
|
||||||
|
|
||||||
if (hasFocus && isActive) {
|
if (hasFocus && isActive) {
|
||||||
setAudioForegroundState(true)
|
setAudioForegroundState(true)
|
||||||
// NEU: zuerst sicherstellen, dass die Bindung existiert
|
// NEW: first ensure that the binding exists
|
||||||
try { mainViewModel?.gameHost?.ensureServiceStartedAndBound() } catch (_: Throwable) {}
|
try { mainViewModel?.gameHost?.ensureServiceStartedAndBound() } catch (_: Throwable) {}
|
||||||
|
|
||||||
setPresentEnabled(false, "focus gained → pre-rebind")
|
setPresentEnabled(false, "focus gained → pre-rebind")
|
||||||
|
|
@ -506,7 +506,7 @@ class MainActivity : BaseActivity() {
|
||||||
try { displayManager.unregisterDisplayListener(displayListener) } catch (_: Throwable) {}
|
try { displayManager.unregisterDisplayListener(displayListener) } catch (_: Throwable) {}
|
||||||
try { unregisterReceiver(serviceStopReceiver) } catch (_: Throwable) {}
|
try { unregisterReceiver(serviceStopReceiver) } catch (_: Throwable) {}
|
||||||
|
|
||||||
// NEU: Bindung aufräumen (verhindert Leak beim Task-Swipe)
|
// NEW: clean up binding (prevents leak when swiping away the task)
|
||||||
try { mainViewModel?.gameHost?.shutdownBinding() } catch (_: Throwable) {}
|
try { mainViewModel?.gameHost?.shutdownBinding() } catch (_: Throwable) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -617,7 +617,7 @@ class MainActivity : BaseActivity() {
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val legacyPath = prefs.getString("gameFolder", null)
|
val legacyPath = prefs.getString("gameFolder", null)
|
||||||
if (!legacyPath.isNullOrEmpty()) {
|
if (!legacyPath.isNullOrEmpty()) {
|
||||||
// Ohne SAF-URI kein Tree-Listing möglich
|
// Without a SAF URI, tree listing is not possible
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
@ -649,7 +649,7 @@ class MainActivity : BaseActivity() {
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
handler.removeCallbacks(enablePresentWhenReady)
|
handler.removeCallbacks(enablePresentWhenReady)
|
||||||
handler.removeCallbacks(reattachWindowWhenReady)
|
handler.removeCallbacks(reattachWindowWhenReady)
|
||||||
// NEU: falls die Activity stirbt → Bindung garantiert lösen
|
// NEW: if the activity dies → ensure unbinding
|
||||||
try { mainViewModel?.gameHost?.shutdownBinding() } catch (_: Throwable) {}
|
try { mainViewModel?.gameHost?.shutdownBinding() } catch (_: Throwable) {}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import java.util.concurrent.Future
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Foreground-Service für stabile Emulation im Hintergrund.
|
* Foreground service for stable emulation in the background.
|
||||||
* Manifest: android:foregroundServiceType="mediaPlayback"
|
* Manifest: android:foregroundServiceType="mediaPlayback"
|
||||||
*/
|
*/
|
||||||
class EmulationService : Service() {
|
class EmulationService : Service() {
|
||||||
|
|
@ -41,7 +41,7 @@ class EmulationService : Service() {
|
||||||
private lateinit var executor: ExecutorService
|
private lateinit var executor: ExecutorService
|
||||||
private var future: Future<*>? = null
|
private var future: Future<*>? = null
|
||||||
private val running = AtomicBoolean(false)
|
private val running = AtomicBoolean(false)
|
||||||
// Nur wenn eine Emulation wirklich lief, dürfen wir nativ „hard close“ machen
|
// Only if an emulation actually ran, we are allowed to perform a native “hard close”
|
||||||
private val startedOnce = AtomicBoolean(false)
|
private val startedOnce = AtomicBoolean(false)
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
|
@ -70,7 +70,7 @@ class EmulationService : Service() {
|
||||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
super.onTaskRemoved(rootIntent)
|
super.onTaskRemoved(rootIntent)
|
||||||
try { future?.cancel(true) } catch (_: Throwable) {}
|
try { future?.cancel(true) } catch (_: Throwable) {}
|
||||||
// Nur schließen, wenn zuvor gestartet
|
// Only close if it was previously started
|
||||||
hardCloseNativeIfStarted("onTaskRemoved")
|
hardCloseNativeIfStarted("onTaskRemoved")
|
||||||
running.set(false)
|
running.set(false)
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
|
|
@ -87,17 +87,17 @@ class EmulationService : Service() {
|
||||||
try { sendBroadcast(Intent(ACTION_STOPPED).setPackage(packageName)) } catch (_: Throwable) {}
|
try { sendBroadcast(Intent(ACTION_STOPPED).setPackage(packageName)) } catch (_: Throwable) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Steuerung via Binder ----
|
// ---- Control via Binder ----
|
||||||
|
|
||||||
private fun startEmulation(runLoopBlock: () -> Unit) {
|
private fun startEmulation(runLoopBlock: () -> Unit) {
|
||||||
// Nur einen RunLoop zulassen
|
// Allow only a single run loop
|
||||||
if (!running.compareAndSet(false, true)) return
|
if (!running.compareAndSet(false, true)) return
|
||||||
|
|
||||||
future = executor.submit {
|
future = executor.submit {
|
||||||
try {
|
try {
|
||||||
// *** Kein Preflight-HardClose mehr! *** (crasht beim allerersten Start)
|
// *** No preflight hard-close anymore! *** (crashes on the very first start)
|
||||||
startedOnce.set(true)
|
startedOnce.set(true)
|
||||||
runLoopBlock() // blockiert bis Emulation endet
|
runLoopBlock() // blocks until emulation ends
|
||||||
} finally {
|
} finally {
|
||||||
startedOnce.set(false)
|
startedOnce.set(false)
|
||||||
running.set(false)
|
running.set(false)
|
||||||
|
|
@ -125,12 +125,12 @@ class EmulationService : Service() {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Native Cleanup nur wenn jemals gestartet ----
|
// ---- Native cleanup only if it was ever started ----
|
||||||
private fun hardCloseNativeIfStarted(reason: String) {
|
private fun hardCloseNativeIfStarted(reason: String) {
|
||||||
if (!startedOnce.get()) return
|
if (!startedOnce.get()) return
|
||||||
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
|
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
|
||||||
try { KenjinxNative.deviceCloseEmulation() } catch (_: Throwable) {}
|
try { KenjinxNative.deviceCloseEmulation() } catch (_: Throwable) {}
|
||||||
// KEIN graphicsSetPresentEnabled(false) hier – führt bei kaltem Start zu NRE in VulkanRenderer.ReleaseSurface()
|
// NO graphicsSetPresentEnabled(false) here — causes NRE in VulkanRenderer.ReleaseSurface() on a cold start
|
||||||
// android.util.Log.d("EmuService", "hardCloseNativeIfStarted: $reason")
|
// android.util.Log.d("EmuService", "hardCloseNativeIfStarted: $reason")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ class MainViewModel(val activity: MainActivity) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
semaphore.acquire()
|
semaphore.acquire()
|
||||||
launchOnUiThread {
|
launchOnUiThread {
|
||||||
// We are only able to initialize the emulation context on the main thread
|
// We can only initialize the emulation context on the main thread
|
||||||
val tzId = TimeZone.getDefault().id
|
val tzId = TimeZone.getDefault().id
|
||||||
success = KenjinxNative.deviceInitialize(
|
success = KenjinxNative.deviceInitialize(
|
||||||
settings.memoryManagerMode.ordinal,
|
settings.memoryManagerMode.ordinal,
|
||||||
|
|
@ -300,7 +300,7 @@ class MainViewModel(val activity: MainActivity) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
semaphore.acquire()
|
semaphore.acquire()
|
||||||
launchOnUiThread {
|
launchOnUiThread {
|
||||||
// We are only able to initialize the emulation context on the main thread
|
// We can only initialize the emulation context on the main thread
|
||||||
val tzId = TimeZone.getDefault().id
|
val tzId = TimeZone.getDefault().id
|
||||||
success = KenjinxNative.deviceInitialize(
|
success = KenjinxNative.deviceInitialize(
|
||||||
settings.memoryManagerMode.ordinal,
|
settings.memoryManagerMode.ordinal,
|
||||||
|
|
|
||||||
|
|
@ -957,7 +957,7 @@ namespace LibKenjinx
|
||||||
|
|
||||||
// ===== PresentAllowed / Surface Control (JNI) =====
|
// ===== PresentAllowed / Surface Control (JNI) =====
|
||||||
|
|
||||||
// alias für ältere Aufrufe, falls vorhanden
|
// alias for older calls, if present
|
||||||
[UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetPresent")]
|
[UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetPresent")]
|
||||||
public static void JniGraphicsRendererSetPresent(bool enabled)
|
public static void JniGraphicsRendererSetPresent(bool enabled)
|
||||||
{
|
{
|
||||||
|
|
@ -975,7 +975,7 @@ namespace LibKenjinx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// neuer Name: passt zu KenjinxNative.graphicsSetPresentEnabled(...)
|
// new name: matches KenjinxNative.graphicsSetPresentEnabled(...)
|
||||||
[UnmanagedCallersOnly(EntryPoint = "graphicsSetPresentEnabled")]
|
[UnmanagedCallersOnly(EntryPoint = "graphicsSetPresentEnabled")]
|
||||||
public static void JniGraphicsSetPresentEnabled(bool enabled)
|
public static void JniGraphicsSetPresentEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
|
|
@ -1003,7 +1003,7 @@ namespace LibKenjinx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// von MainActivity/GameHost benutzt
|
// used by MainActivity/GameHost
|
||||||
[UnmanagedCallersOnly(EntryPoint = "reattachWindowIfReady")]
|
[UnmanagedCallersOnly(EntryPoint = "reattachWindowIfReady")]
|
||||||
public static bool JniReattachWindowIfReady()
|
public static bool JniReattachWindowIfReady()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,14 @@ namespace LibKenjinx
|
||||||
else if (graphicsBackend == GraphicsBackend.Vulkan)
|
else if (graphicsBackend == GraphicsBackend.Vulkan)
|
||||||
{
|
{
|
||||||
// Prefer the platform-provided Vulkan loader (if present), fall back to default.
|
// Prefer the platform-provided Vulkan loader (if present), fall back to default.
|
||||||
var api = VulkanLoader?.GetApi() ?? Vk.GetApi();
|
var api = VulkanLoader?.GetApi() ?? Silk.NET.Vulkan.Vk.GetApi();
|
||||||
|
|
||||||
Renderer = new VulkanRenderer(
|
Renderer = new VulkanRenderer(
|
||||||
api,
|
api,
|
||||||
(instance, _) =>
|
(instance, _) =>
|
||||||
{
|
{
|
||||||
// use provided CreateSurface delegate (Android path will create ANativeWindow surface)
|
// Use provided CreateSurface delegate (Android path will create ANativeWindow surface)
|
||||||
return new SurfaceKHR(createSurfaceFunc == null ? null : (ulong?)createSurfaceFunc(instance.Handle));
|
return new Silk.NET.Vulkan.SurfaceKHR(createSurfaceFunc == null ? null : (ulong?)createSurfaceFunc(instance.Handle));
|
||||||
},
|
},
|
||||||
() => requiredExtensions,
|
() => requiredExtensions,
|
||||||
null);
|
null);
|
||||||
|
|
@ -226,7 +226,7 @@ namespace LibKenjinx
|
||||||
_swapBuffersCallback = swapBuffersCallback;
|
_swapBuffersCallback = swapBuffersCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Convenience-Wrapper für Vulkan re-attach (von JNI nutzbar) =====
|
// ===== Convenience wrapper for Vulkan re-attach (usable from JNI) =====
|
||||||
public static bool TryReattachSurface()
|
public static bool TryReattachSurface()
|
||||||
{
|
{
|
||||||
if (Renderer is VulkanRenderer vr)
|
if (Renderer is VulkanRenderer vr)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
|
|
||||||
// JNI/Lifecycle-Flag
|
// JNI / lifecycle flag
|
||||||
internal volatile bool PresentAllowed = true;
|
internal volatile bool PresentAllowed = true;
|
||||||
|
|
||||||
public uint ProgramCount { get; set; } = 0;
|
public uint ProgramCount { get; set; } = 0;
|
||||||
|
|
@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
internal Lock BackgroundQueueLock { get; private set; }
|
internal Lock BackgroundQueueLock { get; private set; }
|
||||||
internal Lock QueueLock { get; private set; }
|
internal Lock QueueLock { get; private set; }
|
||||||
|
|
||||||
// NEU: SurfaceLock, um Create/Destroy/Queries zu serialisieren
|
// NEW: SurfaceLock to serialize create/destroy/queries
|
||||||
internal Lock SurfaceLock { get; private set; }
|
internal Lock SurfaceLock { get; private set; }
|
||||||
|
|
||||||
internal MemoryAllocator MemoryAllocator { get; private set; }
|
internal MemoryAllocator MemoryAllocator { get; private set; }
|
||||||
|
|
@ -506,7 +506,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
Queue = queue;
|
Queue = queue;
|
||||||
QueueLock = new();
|
QueueLock = new();
|
||||||
|
|
||||||
// Init Locks
|
// Init locks
|
||||||
SurfaceLock = new();
|
SurfaceLock = new();
|
||||||
if (maxQueueCount >= 2)
|
if (maxQueueCount >= 2)
|
||||||
{
|
{
|
||||||
|
|
@ -1021,7 +1021,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return !(IsMoltenVk || IsQualcommProprietary);
|
return !(IsMoltenVk || IsQualcommProprietary);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Surface/Present Lifecycle helpers =====
|
// ===== Surface/Present lifecycle helpers =====
|
||||||
|
|
||||||
public unsafe bool RecreateSurface()
|
public unsafe bool RecreateSurface()
|
||||||
{
|
{
|
||||||
|
|
@ -1052,7 +1052,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// retry später
|
// retry later
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private ScalingFilter _currentScalingFilter;
|
private ScalingFilter _currentScalingFilter;
|
||||||
private bool _colorSpacePassthroughEnabled;
|
private bool _colorSpacePassthroughEnabled;
|
||||||
|
|
||||||
// Gate für alle vk*Surface*-Queries
|
// Gate for all vk*Surface* queries
|
||||||
private volatile bool _allowSurfaceQueries = true;
|
private volatile bool _allowSurfaceQueries = true;
|
||||||
|
|
||||||
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
|
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
|
||||||
|
|
@ -221,7 +221,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
var surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled);
|
var surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled);
|
||||||
var extent = ChooseSwapExtent(capabilities);
|
var extent = ChooseSwapExtent(capabilities);
|
||||||
|
|
||||||
// Guard gegen 0x0-Extent direkt nach Resume
|
// Guard against 0x0 extent right after resume
|
||||||
if (extent.Width == 0 || extent.Height == 0)
|
if (extent.Width == 0 || extent.Height == 0)
|
||||||
{
|
{
|
||||||
_swapchainIsDirty = true;
|
_swapchainIsDirty = true;
|
||||||
|
|
@ -239,10 +239,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
var usage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit;
|
var usage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit;
|
||||||
if (!PlatformInfo.IsBionic)
|
if (!PlatformInfo.IsBionic)
|
||||||
{
|
{
|
||||||
usage |= ImageUsageFlags.StorageBit; // nur Desktop erlaubt Storage für swapchain
|
usage |= ImageUsageFlags.StorageBit; // desktop only: allow storage on swapchain
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auf Android: Identity; sonst der vom Treiber empfohlene CurrentTransform
|
// On Android: use identity; otherwise use the driver-recommended CurrentTransform
|
||||||
var preTransform = PlatformInfo.IsBionic
|
var preTransform = PlatformInfo.IsBionic
|
||||||
? SurfaceTransformFlagsKHR.IdentityBitKhr
|
? SurfaceTransformFlagsKHR.IdentityBitKhr
|
||||||
: capabilities.CurrentTransform;
|
: capabilities.CurrentTransform;
|
||||||
|
|
@ -411,7 +411,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public unsafe override void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
|
public unsafe override void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
|
||||||
{
|
{
|
||||||
// Falls Surface bereits neu ist, Queries aber noch gesperrt → freigeben.
|
// If the surface is already new but queries are still disabled → re-enable them.
|
||||||
if (!_allowSurfaceQueries && _surface.Handle != 0)
|
if (!_allowSurfaceQueries && _surface.Handle != 0)
|
||||||
{
|
{
|
||||||
_allowSurfaceQueries = true;
|
_allowSurfaceQueries = true;
|
||||||
|
|
@ -423,7 +423,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wenn Größe noch nicht da ist, Swapchain später neu aufbauen
|
// If size is not yet available, rebuild swapchain later
|
||||||
if (_width <= 0 || _height <= 0)
|
if (_width <= 0 || _height <= 0)
|
||||||
{
|
{
|
||||||
RecreateSwapchain();
|
RecreateSwapchain();
|
||||||
|
|
@ -431,7 +431,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazy-Init/Recovery
|
// Lazy init / recovery
|
||||||
if (_swapchain.Handle == 0 || _imageAvailableSemaphores == null || _renderFinishedSemaphores == null)
|
if (_swapchain.Handle == 0 || _imageAvailableSemaphores == null || _renderFinishedSemaphores == null)
|
||||||
{
|
{
|
||||||
try { CreateSwapchain(); } catch { /* try again next frame */ }
|
try { CreateSwapchain(); } catch { /* try again next frame */ }
|
||||||
|
|
@ -473,7 +473,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
else if (acquireResult == Result.ErrorSurfaceLostKhr)
|
else if (acquireResult == Result.ErrorSurfaceLostKhr)
|
||||||
{
|
{
|
||||||
// Im Hintergrund nicht sofort neu erstellen – freigeben und zurück
|
// In background do not recreate immediately—release and return
|
||||||
_gd.ReleaseSurface();
|
_gd.ReleaseSurface();
|
||||||
swapBuffersCallback?.Invoke();
|
swapBuffersCallback?.Invoke();
|
||||||
return;
|
return;
|
||||||
|
|
@ -491,13 +491,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
var cbs = _gd.CommandBufferPool.Rent();
|
var cbs = _gd.CommandBufferPool.Rent();
|
||||||
|
|
||||||
// --- Layout/Stages je nach Pfad korrekt setzen ---
|
// --- Set layout/stages correctly depending on path ---
|
||||||
bool allowStorageDst = !PlatformInfo.IsBionic; // Android: kein Storage auf Swapchain
|
bool allowStorageDst = !PlatformInfo.IsBionic; // Android: no storage on swapchain
|
||||||
bool useComputeDst = allowStorageDst && _scalingFilter != null;
|
bool useComputeDst = allowStorageDst && _scalingFilter != null;
|
||||||
|
|
||||||
if (useComputeDst)
|
if (useComputeDst)
|
||||||
{
|
{
|
||||||
// Compute schreibt in das Swapchain-Image → General + ShaderWrite
|
// Compute writes to the swapchain image → General + ShaderWrite
|
||||||
Transition(
|
Transition(
|
||||||
cbs.CommandBuffer,
|
cbs.CommandBuffer,
|
||||||
swapchainImage,
|
swapchainImage,
|
||||||
|
|
@ -510,7 +510,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Renderpass schreibt in das Swapchain-Image → ColorAttachmentOptimal
|
// Render pass writes to the swapchain image → ColorAttachmentOptimal
|
||||||
Transition(
|
Transition(
|
||||||
cbs.CommandBuffer,
|
cbs.CommandBuffer,
|
||||||
swapchainImage,
|
swapchainImage,
|
||||||
|
|
@ -616,7 +616,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transition zu Present – Stages/Access je nach vorherigem Pfad
|
// Transition to Present — stages/access depending on previous path
|
||||||
if (useComputeDst)
|
if (useComputeDst)
|
||||||
{
|
{
|
||||||
Transition(
|
Transition(
|
||||||
|
|
@ -643,7 +643,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
|
|
||||||
var waitSems = new Silk.NET.Vulkan.Semaphore[] { _imageAvailableSemaphores[semaphoreIndex] };
|
var waitSems = new Silk.NET.Vulkan.Semaphore[] { _imageAvailableSemaphores[semaphoreIndex] };
|
||||||
var waitStages = new PipelineStageFlags[] { PipelineStageFlags.ColorAttachmentOutputBit }; // wichtig auf Android
|
var waitStages = new PipelineStageFlags[] { PipelineStageFlags.ColorAttachmentOutputBit }; // important on Android
|
||||||
var signalSems = new Silk.NET.Vulkan.Semaphore[] { _renderFinishedSemaphores[semaphoreIndex] };
|
var signalSems = new Silk.NET.Vulkan.Semaphore[] { _renderFinishedSemaphores[semaphoreIndex] };
|
||||||
_gd.CommandBufferPool.Return(cbs, waitSems, waitStages, signalSems);
|
_gd.CommandBufferPool.Return(cbs, waitSems, waitStages, signalSems);
|
||||||
|
|
||||||
|
|
@ -835,8 +835,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
// We don't need to use width and height as we can get the size from the surface.
|
// We don't need to use width and height as we can get the size from the surface.
|
||||||
_swapchainIsDirty = true;
|
_swapchainIsDirty = true;
|
||||||
|
|
||||||
// Nach Resume sicherstellen, dass Surface-Queries wieder erlaubt sind,
|
// After resume, ensure surface queries are enabled again
|
||||||
// falls vorher OnSurfaceLost() das Gate geschlossen hat.
|
// if OnSurfaceLost() previously closed the gate.
|
||||||
if (_surface.Handle != 0)
|
if (_surface.Handle != 0)
|
||||||
{
|
{
|
||||||
SetSurfaceQueryAllowed(true);
|
SetSurfaceQueryAllowed(true);
|
||||||
|
|
@ -846,7 +846,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
public override void ChangeVSyncMode(VSyncMode vSyncMode)
|
public override void ChangeVSyncMode(VSyncMode vSyncMode)
|
||||||
{
|
{
|
||||||
_vSyncMode = vSyncMode;
|
_vSyncMode = vSyncMode;
|
||||||
//present mode may change, so mark the swapchain for recreation
|
// Present mode may change, so mark the swapchain for recreation
|
||||||
_swapchainIsDirty = true;
|
_swapchainIsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -904,7 +904,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
lock (_gd.SurfaceLock)
|
lock (_gd.SurfaceLock)
|
||||||
{
|
{
|
||||||
// harte Aufräumaktion, damit nach Resume nichts „altes“ übrig ist
|
// Hard cleanup so nothing stale remains after resume
|
||||||
_swapchainIsDirty = true;
|
_swapchainIsDirty = true;
|
||||||
SetSurfaceQueryAllowed(false);
|
SetSurfaceQueryAllowed(false);
|
||||||
|
|
||||||
|
|
@ -953,7 +953,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
|
|
||||||
_surface = new SurfaceKHR(0);
|
_surface = new SurfaceKHR(0);
|
||||||
_width = _height = 0; // erzwingt späteren sauberen Recreate-Pfad
|
_width = _height = 0; // forces a clean recreate path later
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue