changed comments to english

This commit is contained in:
BeZide93 2025-11-01 23:39:44 +01:00
parent d28cfb1c94
commit 77dea2787f
10 changed files with 79 additions and 81 deletions

View file

@ -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>

View file

@ -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)

View file

@ -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) }

View file

@ -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()
} }

View file

@ -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")
} }

View file

@ -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,

View file

@ -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()
{ {

View file

@ -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)

View file

@ -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;
} }
} }

View file

@ -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
} }
} }