mirror of
https://git.ryujinx.app/kenji-nx/ryujinx.git
synced 2025-12-16 13:37:03 +00:00
fixed screen stretch when switching between landscape and portrait mode
This commit is contained in:
parent
316dc3a9b3
commit
8418025b82
3 changed files with 156 additions and 102 deletions
|
|
@ -2,6 +2,9 @@ package org.kenjinx.android
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import androidx.compose.runtime.MutableState
|
||||
|
|
@ -12,6 +15,7 @@ import kotlin.concurrent.thread
|
|||
@SuppressLint("ViewConstructor")
|
||||
class GameHost(context: Context?, private val mainViewModel: MainViewModel) : SurfaceView(context),
|
||||
SurfaceHolder.Callback {
|
||||
|
||||
private var isProgressHidden: Boolean = false
|
||||
private var progress: MutableState<String>? = null
|
||||
private var progressValue: MutableState<Float>? = null
|
||||
|
|
@ -27,19 +31,20 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||
private var _isStarted: Boolean = false
|
||||
private val _nativeWindow: NativeWindow
|
||||
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
// Stabilizer-State
|
||||
private var stabilizerActive = false
|
||||
|
||||
var currentSurface: Long = -1
|
||||
private set
|
||||
|
||||
val currentWindowHandle: Long
|
||||
get() {
|
||||
return _nativeWindow.nativePointer
|
||||
}
|
||||
get() = _nativeWindow.nativePointer
|
||||
|
||||
init {
|
||||
holder.addCallback(this)
|
||||
|
||||
_nativeWindow = NativeWindow(this)
|
||||
|
||||
mainViewModel.gameHost = this
|
||||
}
|
||||
|
||||
|
|
@ -47,45 +52,35 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||
// no-op
|
||||
}
|
||||
|
||||
fun setProgress(info : String, progressVal: Float) {
|
||||
fun setProgress(info: String, progressVal: Float) {
|
||||
showLoading?.apply {
|
||||
progressValue?.apply {
|
||||
this.value = progressVal
|
||||
}
|
||||
|
||||
progress?.apply {
|
||||
this.value = info
|
||||
}
|
||||
progressValue?.apply { this.value = progressVal }
|
||||
progress?.apply { this.value = info }
|
||||
}
|
||||
}
|
||||
|
||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||
if (_isClosed)
|
||||
return
|
||||
if (_isClosed) return
|
||||
|
||||
if (_width != width || _height != height) {
|
||||
val sizeChanged = (_width != width || _height != height)
|
||||
|
||||
if (sizeChanged) {
|
||||
currentSurface = _nativeWindow.requeryWindowHandle()
|
||||
|
||||
_nativeWindow.swapInterval = 0
|
||||
}
|
||||
|
||||
_width = width
|
||||
_height = height
|
||||
|
||||
// Start renderer (if not already started)
|
||||
start(holder)
|
||||
|
||||
KenjinxNative.graphicsRendererSetSize(
|
||||
width,
|
||||
height
|
||||
)
|
||||
|
||||
if (_isStarted) {
|
||||
KenjinxNative.inputSetClientSize(width, height)
|
||||
}
|
||||
// Do not set size immediately → Stabilizer takes over
|
||||
startStabilizedResize(expectedRotation = null)
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
// no-op
|
||||
// no-op (renderer lives in its own thread; close via close())
|
||||
}
|
||||
|
||||
fun close() {
|
||||
|
|
@ -95,46 +90,41 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||
|
||||
KenjinxNative.uiHandlerSetResponse(false, "")
|
||||
|
||||
_updateThread?.join()
|
||||
_renderingThreadWatcher?.join()
|
||||
try { _updateThread?.join(200) } catch (_: Throwable) {}
|
||||
try { _renderingThreadWatcher?.join(200) } catch (_: Throwable) {}
|
||||
}
|
||||
|
||||
private fun start(surfaceHolder: SurfaceHolder) {
|
||||
if (_isStarted)
|
||||
return
|
||||
|
||||
if (_isStarted) return
|
||||
_isStarted = true
|
||||
|
||||
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel
|
||||
|
||||
// Initialize input
|
||||
KenjinxNative.inputInitialize(width, height)
|
||||
|
||||
val id = mainViewModel.physicalControllerManager?.connect()
|
||||
mainViewModel.motionSensorManager?.setControllerId(id ?: -1)
|
||||
|
||||
KenjinxNative.graphicsRendererSetSize(
|
||||
surfaceHolder.surfaceFrame.width(),
|
||||
surfaceHolder.surfaceFrame.height()
|
||||
)
|
||||
// NO graphicsRendererSetSize here – we set it via the stabilizer!
|
||||
|
||||
NativeHelpers.instance.setIsInitialOrientationFlipped(mainViewModel.activity.display?.rotation == 3)
|
||||
|
||||
_guestThread = thread(start = true) {
|
||||
_guestThread = thread(start = true, name = "KenjinxGuest") {
|
||||
runGame()
|
||||
}
|
||||
|
||||
_updateThread = thread(start = true) {
|
||||
_updateThread = thread(start = true, name = "KenjinxInput/Stats") {
|
||||
var c = 0
|
||||
val helper = NativeHelpers.instance
|
||||
while (_isStarted) {
|
||||
KenjinxNative.inputUpdate()
|
||||
Thread.sleep(1)
|
||||
c++
|
||||
if (c >= 1000) {
|
||||
if (progressValue?.value == -1f)
|
||||
if (progressValue?.value == -1f) {
|
||||
progress?.apply {
|
||||
this.value =
|
||||
"Loading ${if (mainViewModel.isMiiEditorLaunched) "Mii Editor" else game!!.titleName}"
|
||||
this.value = "Loading ${if (mainViewModel.isMiiEditorLaunched) "Mii Editor" else game?.titleName ?: ""}"
|
||||
}
|
||||
}
|
||||
c = 0
|
||||
mainViewModel.updateStats(
|
||||
|
|
@ -149,7 +139,6 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||
|
||||
private fun runGame() {
|
||||
KenjinxNative.graphicsRendererRunLoop()
|
||||
|
||||
game?.close()
|
||||
}
|
||||
|
||||
|
|
@ -161,17 +150,115 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||
this.showLoading = showLoading
|
||||
this.progressValue = progressValue
|
||||
this.progress = progress
|
||||
|
||||
showLoading?.apply {
|
||||
showLoading.value = !isProgressHidden
|
||||
}
|
||||
showLoading?.apply { value = !isProgressHidden }
|
||||
}
|
||||
|
||||
fun hideProgressIndicator() {
|
||||
isProgressHidden = true
|
||||
showLoading?.apply {
|
||||
if (value == isProgressHidden)
|
||||
value = !isProgressHidden
|
||||
if (value == isProgressHidden) value = !isProgressHidden
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sicheres Setzen der Renderer-/Input-Größe.
|
||||
*/
|
||||
@Synchronized
|
||||
private fun safeSetSize(w: Int, h: Int) {
|
||||
if (_isClosed) return
|
||||
if (w <= 0 || h <= 0) return
|
||||
try {
|
||||
Log.d("GameHost", "safeSetSize: ${w}x$h (started=$_isStarted)")
|
||||
KenjinxNative.graphicsRendererSetSize(w, h)
|
||||
if (_isStarted) {
|
||||
KenjinxNative.inputSetClientSize(w, h)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Log.e("GameHost", "safeSetSize failed: ${t.message}", t)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird von der Activity bei Rotations-/Layoutwechsel aufgerufen.
|
||||
* Reicht die aktuelle Rotation durch, damit wir ggf. Breite/Höhe tauschen können.
|
||||
*/
|
||||
fun onOrientationOrSizeChanged(rotation: Int? = null) {
|
||||
if (_isClosed) return
|
||||
startStabilizedResize(rotation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wartet kurz, bis das Surface seine finalen Maße nach der Drehung hat,
|
||||
* prüft Plausibilität (Portrait/Landscape) und setzt erst dann die Größe.
|
||||
*/
|
||||
private fun startStabilizedResize(expectedRotation: Int?) {
|
||||
if (_isClosed) return
|
||||
|
||||
// Restart if already active
|
||||
if (stabilizerActive) {
|
||||
stabilizerActive = false
|
||||
}
|
||||
stabilizerActive = true
|
||||
|
||||
var attempts = 0
|
||||
var stableCount = 0
|
||||
var lastW = -1
|
||||
var lastH = -1
|
||||
|
||||
val task = object : Runnable {
|
||||
override fun run() {
|
||||
if (!_isStarted || _isClosed) {
|
||||
stabilizerActive = false
|
||||
return
|
||||
}
|
||||
|
||||
// Prefer real frame size
|
||||
var w = holder.surfaceFrame.width()
|
||||
var h = holder.surfaceFrame.height()
|
||||
|
||||
// Fallbacks
|
||||
if (w <= 0 || h <= 0) {
|
||||
w = width
|
||||
h = height
|
||||
}
|
||||
|
||||
// If rotation is known: Force plausibility (Landscape ↔ Portrait)
|
||||
expectedRotation?.let { rot ->
|
||||
// ROTATION_90 (1) / ROTATION_270 (3) => Landscape
|
||||
val landscape = (rot == 1 || rot == 3)
|
||||
if (landscape && h > w) {
|
||||
val t = w; w = h; h = t
|
||||
} else if (!landscape && w > h) {
|
||||
val t = w; w = h; h = t
|
||||
}
|
||||
}
|
||||
|
||||
// Stability test
|
||||
if (w == lastW && h == lastH && w > 0 && h > 0) {
|
||||
stableCount++
|
||||
} else {
|
||||
stableCount = 0
|
||||
lastW = w
|
||||
lastH = h
|
||||
}
|
||||
|
||||
attempts++
|
||||
|
||||
// 2 consecutive identical measurements OR 20 attempts → apply
|
||||
if ((stableCount >= 2 || attempts >= 20) && w > 0 && h > 0) {
|
||||
Log.d("GameHost", "resize stabilized after $attempts ticks → ${w}x$h")
|
||||
safeSetSize(w, h)
|
||||
stabilizerActive = false
|
||||
return
|
||||
}
|
||||
|
||||
// continue to pollen
|
||||
if (stabilizerActive) {
|
||||
mainHandler.postDelayed(this, 16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mainHandler.post(task)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,11 @@ 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. If you have a hook in C#/C++ (swapchain/viewport new),
|
||||
// you can call it here.
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant A (Pointer → Strings via NativeHelpers).
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import org.kenjinx.android.viewmodels.MainViewModel
|
|||
import org.kenjinx.android.viewmodels.QuickSettings
|
||||
import org.kenjinx.android.viewmodels.GameModel
|
||||
import org.kenjinx.android.views.MainView
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
private var physicalControllerManager: PhysicalControllerManager =
|
||||
|
|
@ -78,47 +77,20 @@ class MainActivity : BaseActivity() {
|
|||
private external fun initVm()
|
||||
|
||||
private fun initialize() {
|
||||
if (_isInit)
|
||||
return
|
||||
if (_isInit) return
|
||||
|
||||
val appPath: String = AppPath
|
||||
|
||||
var quickSettings = QuickSettings(this)
|
||||
KenjinxNative.loggingSetEnabled(
|
||||
LogLevel.Info,
|
||||
quickSettings.enableInfoLogs
|
||||
)
|
||||
KenjinxNative.loggingSetEnabled(
|
||||
LogLevel.Stub,
|
||||
quickSettings.enableStubLogs
|
||||
)
|
||||
KenjinxNative.loggingSetEnabled(
|
||||
LogLevel.Warning,
|
||||
quickSettings.enableWarningLogs
|
||||
)
|
||||
KenjinxNative.loggingSetEnabled(
|
||||
LogLevel.Error,
|
||||
quickSettings.enableErrorLogs
|
||||
)
|
||||
KenjinxNative.loggingSetEnabled(
|
||||
LogLevel.AccessLog,
|
||||
quickSettings.enableFsAccessLogs
|
||||
)
|
||||
KenjinxNative.loggingSetEnabled(
|
||||
LogLevel.Guest,
|
||||
quickSettings.enableGuestLogs
|
||||
)
|
||||
KenjinxNative.loggingSetEnabled(
|
||||
LogLevel.Trace,
|
||||
quickSettings.enableTraceLogs
|
||||
)
|
||||
KenjinxNative.loggingSetEnabled(
|
||||
LogLevel.Debug,
|
||||
quickSettings.enableDebugLogs
|
||||
)
|
||||
KenjinxNative.loggingEnabledGraphicsLog(
|
||||
quickSettings.enableGraphicsLogs
|
||||
)
|
||||
KenjinxNative.loggingSetEnabled(LogLevel.Info, quickSettings.enableInfoLogs)
|
||||
KenjinxNative.loggingSetEnabled(LogLevel.Stub, quickSettings.enableStubLogs)
|
||||
KenjinxNative.loggingSetEnabled(LogLevel.Warning, quickSettings.enableWarningLogs)
|
||||
KenjinxNative.loggingSetEnabled(LogLevel.Error, quickSettings.enableErrorLogs)
|
||||
KenjinxNative.loggingSetEnabled(LogLevel.AccessLog, quickSettings.enableFsAccessLogs)
|
||||
KenjinxNative.loggingSetEnabled(LogLevel.Guest, quickSettings.enableGuestLogs)
|
||||
KenjinxNative.loggingSetEnabled(LogLevel.Trace, quickSettings.enableTraceLogs)
|
||||
KenjinxNative.loggingSetEnabled(LogLevel.Debug, quickSettings.enableDebugLogs)
|
||||
KenjinxNative.loggingEnabledGraphicsLog(quickSettings.enableGraphicsLogs)
|
||||
|
||||
_isInit = KenjinxNative.javaInitialize(appPath, JNIEnv.CURRENT)
|
||||
}
|
||||
|
|
@ -143,7 +115,7 @@ class MainActivity : BaseActivity() {
|
|||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
// --- Apply alignment
|
||||
// Apply alignment
|
||||
applyOrientationPreference()
|
||||
|
||||
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
||||
|
|
@ -151,7 +123,6 @@ class MainActivity : BaseActivity() {
|
|||
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
|
||||
// >>> Important: Initialize UI handler (for software keyboard/dialog)
|
||||
uiHandler = UiHandler()
|
||||
|
||||
mainViewModel = MainViewModel(this)
|
||||
|
|
@ -194,23 +165,19 @@ class MainActivity : BaseActivity() {
|
|||
@SuppressLint("RestrictedApi")
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
event.apply {
|
||||
if (physicalControllerManager.onKeyEvent(this))
|
||||
return true
|
||||
if (physicalControllerManager.onKeyEvent(this)) return true
|
||||
}
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
|
||||
ev?.apply {
|
||||
physicalControllerManager.onMotionEvent(this)
|
||||
}
|
||||
ev?.apply { physicalControllerManager.onMotionEvent(this) }
|
||||
return super.dispatchGenericMotionEvent(ev)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
isActive = false
|
||||
|
||||
if (isGameRunning) {
|
||||
mainViewModel?.performanceManager?.setTurboMode(false)
|
||||
}
|
||||
|
|
@ -218,26 +185,23 @@ class MainActivity : BaseActivity() {
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// --- Reapply alignment if necessary
|
||||
// Reapply alignment if necessary
|
||||
applyOrientationPreference()
|
||||
|
||||
handler.postDelayed(delayedHandleIntent, 10)
|
||||
isActive = true
|
||||
|
||||
if (isGameRunning) {
|
||||
if (QuickSettings(this).enableMotion)
|
||||
motionSensorManager.register()
|
||||
if (QuickSettings(this).enableMotion) motionSensorManager.register()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
isActive = false
|
||||
|
||||
if (isGameRunning) {
|
||||
mainViewModel?.performanceManager?.setTurboMode(false)
|
||||
}
|
||||
|
||||
motionSensorManager.unregister()
|
||||
}
|
||||
|
||||
|
|
@ -275,9 +239,7 @@ class MainActivity : BaseActivity() {
|
|||
val componentName = intent?.component
|
||||
val restartIntent = Intent.makeRestartActivityTask(componentName)
|
||||
|
||||
mainViewModel?.let {
|
||||
it.performanceManager?.setTurboMode(false)
|
||||
}
|
||||
mainViewModel?.let { it.performanceManager?.setTurboMode(false) }
|
||||
startActivity(restartIntent)
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue