From 28af479e2a3c194608156d622ec38c5f85cb12c0 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Sat, 26 Aug 2023 12:02:58 +0000 Subject: [PATCH] improve async loading. add game load progress --- src/LibRyujinx/Android/JniExportedMethods.cs | 5 +- src/LibRyujinx/LibRyujinx.Graphics.cs | 158 ++++++++++++------ .../app/src/main/cpp/ryujinx.cpp | 28 ++++ .../java/org/ryujinx/android/GameActivity.kt | 153 ++++++++++++----- .../main/java/org/ryujinx/android/GameHost.kt | 50 +++++- .../java/org/ryujinx/android/MainActivity.kt | 2 + .../java/org/ryujinx/android/NativeHelpers.kt | 2 + .../ryujinx/android/viewmodels/GameModel.kt | 2 +- .../android/viewmodels/HomeViewModel.kt | 36 +++- .../android/viewmodels/MainViewModel.kt | 69 ++++++-- .../org/ryujinx/android/views/HomeViews.kt | 25 ++- 11 files changed, 393 insertions(+), 137 deletions(-) diff --git a/src/LibRyujinx/Android/JniExportedMethods.cs b/src/LibRyujinx/Android/JniExportedMethods.cs index fc2a36328..e3a9ab860 100644 --- a/src/LibRyujinx/Android/JniExportedMethods.cs +++ b/src/LibRyujinx/Android/JniExportedMethods.cs @@ -49,6 +49,9 @@ namespace LibRyujinx [DllImport("libryujinxjni")] internal extern static void onFrameEnd(double time); + [DllImport("libryujinxjni")] + internal extern static void setProgressInfo(IntPtr info, float progress); + [DllImport("libryujinxjni")] internal extern static void setCurrentTransform(long native_window, int transform); @@ -287,7 +290,7 @@ namespace LibRyujinx extensions.Add(GetString(jEnv, ext)); } - if((long)driverHandle != 0) + if ((long)driverHandle != 0) { VulkanLoader = new VulkanLoader((IntPtr)(long)driverHandle); } diff --git a/src/LibRyujinx/LibRyujinx.Graphics.cs b/src/LibRyujinx/LibRyujinx.Graphics.cs index 6308a94c3..b247a5b98 100644 --- a/src/LibRyujinx/LibRyujinx.Graphics.cs +++ b/src/LibRyujinx/LibRyujinx.Graphics.cs @@ -1,10 +1,13 @@ using ARMeilleure.Translation; +using LibHac.Bcat; using LibRyujinx.Shared; using OpenTK.Graphics.OpenGL; using Ryujinx.Common.Configuration; +using Ryujinx.Cpu; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.Vulkan; using Silk.NET.Vulkan; @@ -153,64 +156,125 @@ namespace LibRyujinx device.Gpu.Renderer.Initialize(GraphicsDebugLevel.None); _gpuCancellationTokenSource = new CancellationTokenSource(); - device.Gpu.Renderer.RunLoop(() => + device.Gpu.ShaderCacheStateChanged += LoadProgressStateChangedHandler; + device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += LoadProgressStateChangedHandler; + + try { - _gpuDoneEvent.Reset(); - device.Gpu.SetGpuThread(); - device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); - Translator.IsReadyForTranslation.Set(); - - _isActive = true; - - while (_isActive) + device.Gpu.Renderer.RunLoop(() => { - if (_isStopped) - { - break; - } + _gpuDoneEvent.Reset(); + device.Gpu.SetGpuThread(); + device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + Translator.IsReadyForTranslation.Set(); - debug_break(1); + _isActive = true; - if (Ryujinx.Common.SystemInfo.SystemInfo.IsBionic) + while (_isActive) { - setRenderingThread(); - } - - if (device.WaitFifo()) - { - device.Statistics.RecordFifoStart(); - device.ProcessFrame(); - device.Statistics.RecordFifoEnd(); - } - - while (device.ConsumeFrameAvailable()) - { - device.PresentFrame(() => + if (_isStopped) { - VulkanRenderer? vk = device.Gpu.Renderer as VulkanRenderer; - if(vk == null) - { - vk = (device.Gpu.Renderer as ThreadedRenderer)?.BaseRenderer as VulkanRenderer; - } + break; + } - if(vk != null) - { - var transform = vk.CurrentTransform; + debug_break(1); - setCurrentTransform(_window, (int)transform); - } - _swapBuffersCallback?.Invoke(); - }); + if (Ryujinx.Common.SystemInfo.SystemInfo.IsBionic) + { + setRenderingThread(); + } + + if (device.WaitFifo()) + { + device.Statistics.RecordFifoStart(); + device.ProcessFrame(); + device.Statistics.RecordFifoEnd(); + } + + while (device.ConsumeFrameAvailable()) + { + device.PresentFrame(() => + { + VulkanRenderer? vk = device.Gpu.Renderer as VulkanRenderer; + if (vk == null) + { + vk = (device.Gpu.Renderer as ThreadedRenderer)?.BaseRenderer as VulkanRenderer; + } + + if (vk != null) + { + var transform = vk.CurrentTransform; + + setCurrentTransform(_window, (int)transform); + } + _swapBuffersCallback?.Invoke(); + }); + } } - } - if (device.Gpu.Renderer is ThreadedRenderer threaded) - { - threaded.FlushThreadedCommands(); - } + if (device.Gpu.Renderer is ThreadedRenderer threaded) + { + threaded.FlushThreadedCommands(); + } - _gpuDoneEvent.Set(); - }); + _gpuDoneEvent.Set(); + }); + } + finally + { + device.Gpu.ShaderCacheStateChanged -= LoadProgressStateChangedHandler; + device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= LoadProgressStateChangedHandler; + } + } + + private static void LoadProgressStateChangedHandler(T state, int current, int total) where T : Enum + { + void SetInfo(string status, float value) + { + var ptr = Marshal.StringToHGlobalAnsi(status); + + setProgressInfo(ptr, value); + + Marshal.FreeHGlobal(ptr); + } + var status = $"{current} / {total}"; + var progress = current / (float)total; + + switch (state) + { + case LoadState ptcState: + if (float.IsNaN((progress))) + progress = 0; + + switch (ptcState) + { + case LoadState.Unloaded: + case LoadState.Loading: + SetInfo($"Loading PTC {status}", progress); + break; + case LoadState.Loaded: + SetInfo($"PTC Loaded", -1); + break; + } + break; + case ShaderCacheState shaderCacheState: + switch (shaderCacheState) + { + case ShaderCacheState.Start: + case ShaderCacheState.Loading: + SetInfo($"Compiling Shaders {status}", progress); + break; + case ShaderCacheState.Packaging: + SetInfo($"Packaging Shaders {status}", progress); + break; + case ShaderCacheState.Loaded: + SetInfo($"Shaders Loaded", -1); + break; + } + break; + default: + throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"); + } } [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_swap_buffer_callback")] diff --git a/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp index 858fb9be0..278c176b5 100644 --- a/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp +++ b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp @@ -26,6 +26,9 @@ JNIEnv* _rendererEnv = nullptr; std::chrono::time_point _currentTimePoint; +std::string progressInfo = ""; +float progress = -1; + JNIEnv* getEnv(bool isRenderer){ JNIEnv* env; if(isRenderer){ @@ -130,6 +133,14 @@ jstring createString( return str; } +jstring createStringFromStdString( + JNIEnv *env, + std::string s) { + auto str = env->NewStringUTF(s.c_str()); + + return str; +} + } extern "C" @@ -168,6 +179,11 @@ void onFrameEnd(double time) { env->CallStaticVoidMethod(cl, _updateFrameTime, nano); } +extern "C" +void setProgressInfo(char* info, float progressValue) { + progressInfo = std::string (info); + progress = progressValue; +} extern "C" void setCurrentTransform(long native_window, int transform){ @@ -283,3 +299,15 @@ Java_org_ryujinx_android_NativeHelpers_setSwapInterval(JNIEnv *env, jobject thiz return nativeWindow->setSwapInterval(nativeWindow, swap_interval); } + +extern "C" +JNIEXPORT jfloat JNICALL +Java_org_ryujinx_android_NativeHelpers_getProgressValue(JNIEnv *env, jobject thiz) { + return progress; +} + +extern "C" +JNIEXPORT jstring JNICALL +Java_org_ryujinx_android_NativeHelpers_getProgressInfo(JNIEnv *env, jobject thiz) { + return createStringFromStdString(env, progressInfo); +} diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameActivity.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameActivity.kt index 1a1bb96b4..37113912f 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameActivity.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameActivity.kt @@ -21,9 +21,11 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.Button +import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -50,7 +52,9 @@ import kotlin.math.abs import kotlin.math.roundToInt class GameActivity : ComponentActivity() { - private var physicalControllerManager: PhysicalControllerManager = PhysicalControllerManager(this) + private var physicalControllerManager: PhysicalControllerManager = + PhysicalControllerManager(this) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -70,7 +74,7 @@ class GameActivity : ComponentActivity() { @SuppressLint("RestrictedApi") override fun dispatchKeyEvent(event: KeyEvent?): Boolean { event?.apply { - if(physicalControllerManager.onKeyEvent(this)) + if (physicalControllerManager.onKeyEvent(this)) return true } return super.dispatchKeyEvent(event) @@ -105,20 +109,22 @@ class GameActivity : ComponentActivity() { force60HzRefreshRate(false) } - private fun force60HzRefreshRate(enable : Boolean) { + private fun force60HzRefreshRate(enable: Boolean) { // Hack for MIUI devices since they don't support the standard Android APIs try { val setFpsIntent = Intent("com.miui.powerkeeper.SET_ACTIVITY_FPS") setFpsIntent.putExtra("package_name", "org.ryujinx.android") setFpsIntent.putExtra("isEnter", enable) sendBroadcast(setFpsIntent) - } catch (_ : Exception) { + } catch (_: Exception) { } if (enable) - display?.supportedModes?.minByOrNull { abs(it.refreshRate - 60f) }?.let { window.attributes.preferredDisplayModeId = it.modeId } + display?.supportedModes?.minByOrNull { abs(it.refreshRate - 60f) } + ?.let { window.attributes.preferredDisplayModeId = it.modeId } else - display?.supportedModes?.maxByOrNull { it.refreshRate }?.let { window.attributes.preferredDisplayModeId = it.modeId } + display?.supportedModes?.maxByOrNull { it.refreshRate } + ?.let { window.attributes.preferredDisplayModeId = it.modeId } } private fun setFullScreen(fullscreen: Boolean) { @@ -139,6 +145,7 @@ class GameActivity : ComponentActivity() { } } } + @Composable fun GameView(mainViewModel: MainViewModel) { Box(modifier = Modifier.fillMaxSize()) { @@ -170,6 +177,20 @@ class GameActivity : ComponentActivity() { mutableStateOf(false) } + val showLoading = remember { + mutableStateOf(true) + } + + val progressValue = remember { + mutableStateOf(0.0f) + } + + val progress = remember { + mutableStateOf("Loading") + } + + mainViewModel.setProgressStates(showLoading, progressValue, progress) + // touch surface Surface(color = Color.Transparent, modifier = Modifier .fillMaxSize() @@ -213,47 +234,54 @@ class GameActivity : ComponentActivity() { } }) { } - GameController.Compose(mainViewModel) - Row( - modifier = Modifier - .align(Alignment.BottomCenter) - .padding(8.dp) - ) { - IconButton(modifier = Modifier.padding(4.dp), onClick = { - showMore.value = true - }) { - Icon( - imageVector = CssGgIcons.ToolbarBottom, - contentDescription = "Open Panel" - ) - } - } + if (!showLoading.value) { + GameController.Compose(mainViewModel) - if(showMore.value){ - Popup(alignment = Alignment.BottomCenter, onDismissRequest = {showMore.value = false}) { - Surface(modifier = Modifier.padding(16.dp), - shape = MaterialTheme.shapes.medium) { - Row(modifier = Modifier.padding(8.dp)) { - IconButton(modifier = Modifier.padding(4.dp), onClick = { - showMore.value = false - showController.value = !showController.value - mainViewModel.controller?.setVisible(showController.value) - }) { - Icon( - imageVector = Icons.videoGame(), - contentDescription = "Toggle Virtual Pad" - ) - } - IconButton(modifier = Modifier.padding(4.dp), onClick = { - showMore.value = false - enableVsync.value = !enableVsync.value - RyujinxNative().graphicsRendererSetVsync(enableVsync.value) - }) { - Icon( - imageVector = Icons.vSync(), - tint = if(enableVsync.value) Color.Green else Color.Red, - contentDescription = "Toggle VSync" - ) + Row( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(8.dp) + ) { + IconButton(modifier = Modifier.padding(4.dp), onClick = { + showMore.value = true + }) { + Icon( + imageVector = CssGgIcons.ToolbarBottom, + contentDescription = "Open Panel" + ) + } + } + + if (showMore.value) { + Popup( + alignment = Alignment.BottomCenter, + onDismissRequest = { showMore.value = false }) { + Surface( + modifier = Modifier.padding(16.dp), + shape = MaterialTheme.shapes.medium + ) { + Row(modifier = Modifier.padding(8.dp)) { + IconButton(modifier = Modifier.padding(4.dp), onClick = { + showMore.value = false + showController.value = !showController.value + mainViewModel.controller?.setVisible(showController.value) + }) { + Icon( + imageVector = Icons.videoGame(), + contentDescription = "Toggle Virtual Pad" + ) + } + IconButton(modifier = Modifier.padding(4.dp), onClick = { + showMore.value = false + enableVsync.value = !enableVsync.value + RyujinxNative().graphicsRendererSetVsync(enableVsync.value) + }) { + Icon( + imageVector = Icons.vSync(), + tint = if (enableVsync.value) Color.Green else Color.Red, + contentDescription = "Toggle VSync" + ) + } } } } @@ -268,6 +296,39 @@ class GameActivity : ComponentActivity() { showBackNotice.value = true } + if (showLoading.value) { + Card( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(0.5f) + .align(Alignment.Center), + shape = MaterialTheme.shapes.medium + ) { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + ) { + Text(text = progress.value) + + if (progressValue.value > -1) + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + progress = progressValue.value + ) + else + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + ) + } + + } + } + if (showBackNotice.value) { AlertDialog(onDismissRequest = { showBackNotice.value = false }) { Column { diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt index 8a15e3a05..f19fc93da 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt @@ -5,6 +5,7 @@ import android.content.Context import android.os.Build import android.view.SurfaceHolder import android.view.SurfaceView +import androidx.compose.runtime.MutableState import org.ryujinx.android.viewmodels.GameModel import org.ryujinx.android.viewmodels.MainViewModel import org.ryujinx.android.viewmodels.QuickSettings @@ -12,6 +13,10 @@ 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? = null + private var progressValue: MutableState? = null + private var showLoading: MutableState? = null private var game: GameModel? = null private var _isClosed: Boolean = false private var _renderingThreadWatcher: Thread? = null @@ -29,6 +34,8 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su holder.addCallback(this) nativeWindow = NativeWindow(this) + + mainViewModel.gameHost = this } override fun surfaceCreated(holder: SurfaceHolder) { @@ -75,7 +82,6 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su } private fun start(surfaceHolder: SurfaceHolder) { - mainViewModel.gameHost = this if(_isStarted) return @@ -106,11 +112,31 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su _updateThread = thread(start = true) { var c = 0 + val helper = NativeHelpers() while (_isStarted) { _nativeRyujinx.inputUpdate() Thread.sleep(1) + + showLoading?.apply { + if(value){ + var value = helper.getProgressValue() + + if(value != -1f) + progress?.apply { + this.value = helper.getProgressInfo() + } + + progressValue?.apply { + this.value = value + } + } + } c++ if (c >= 1000) { + if(helper.getProgressValue() == -1f) + progress?.apply { + this.value = "Loading ${game!!.titleName}" + } c = 0 mainViewModel.updateStats(_nativeRyujinx.deviceGetGameFifo(), _nativeRyujinx.deviceGetGameFrameRate(), _nativeRyujinx.deviceGetGameFrameTime()) } @@ -143,4 +169,26 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su game?.close() } + + fun setProgressStates( + showLoading: MutableState?, + progressValue: MutableState?, + progress: MutableState? + ) { + this.showLoading = showLoading + this.progressValue = progressValue + this.progress = progress + + showLoading?.apply { + showLoading.value = !isProgressHidden + } + } + + fun hideProgressIndicator() { + isProgressHidden = true + showLoading?.apply { + if (value == isProgressHidden) + value = !isProgressHidden + } + } } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt index d9817f69a..5f3b95481 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt @@ -34,6 +34,8 @@ class MainActivity : ComponentActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { mainViewModel?.performanceManager?.updateRenderingSessionTime(gameTime) } + + mainViewModel?.gameHost?.hideProgressIndicator() } } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt index af46708a2..eead88176 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt @@ -26,4 +26,6 @@ class NativeHelpers { external fun getMaxSwapInterval(nativeWindow: Long): Int external fun getMinSwapInterval(nativeWindow: Long): Int external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int + external fun getProgressInfo() : String + external fun getProgressValue() : Float } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt index 1a0490827..bf166b1c9 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt @@ -8,7 +8,7 @@ import org.ryujinx.android.RyujinxNative class GameModel(var file: DocumentFile, val context: Context) { - private var descriptor: ParcelFileDescriptor? = null + var descriptor: ParcelFileDescriptor? = null var fileName: String? var fileSize = 0.0 var titleName: String? = null diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt index b17e45d1e..e8666757f 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt @@ -11,11 +11,13 @@ import com.anggrayudi.storage.file.extension import com.anggrayudi.storage.file.getAbsolutePath import com.anggrayudi.storage.file.search import org.ryujinx.android.MainActivity +import kotlin.concurrent.thread class HomeViewModel( val activity: MainActivity? = null, val mainViewModel: MainViewModel? = null ) { + private var isLoading: Boolean = false private var gameList: SnapshotStateList? = null private var loadedCache: List = listOf() private var gameFolderPath: DocumentFile? = null @@ -68,23 +70,39 @@ class HomeViewModel( fun reloadGameList() { var storage = activity?.storageHelper ?: return + + if(isLoading) + return val folder = gameFolderPath ?: return + + isLoading = true val files = mutableListOf() - for (file in folder.search(false, DocumentFileType.FILE)) { - if (file.extension == "xci" || file.extension == "nsp") - activity.let { - files.add(GameModel(file, it)) + thread { + try { + for (file in folder.search(false, DocumentFileType.FILE)) { + if (file.extension == "xci" || file.extension == "nsp") + activity.let { + files.add(GameModel(file, it)) + } } + + loadedCache = files.toList() + + isLoading = false + + applyFilter() + } + finally { + isLoading = false + } } - - loadedCache = files.toList() - - applyFilter() } private fun applyFilter() { + if(isLoading) + return gameList?.clear() gameList?.addAll(loadedCache) } @@ -93,4 +111,4 @@ class HomeViewModel( gameList = list applyFilter() } -} \ No newline at end of file +} diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt index c6c157f8d..c7d64ecc0 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt @@ -7,6 +7,9 @@ import android.os.Build import android.os.PerformanceHintManager import androidx.compose.runtime.MutableState import androidx.navigation.NavHostController +import com.anggrayudi.storage.extension.launchOnUiThread +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Semaphore import org.ryujinx.android.GameActivity import org.ryujinx.android.GameController import org.ryujinx.android.GameHost @@ -25,13 +28,20 @@ import java.io.File class MainViewModel(val activity: MainActivity) { var physicalControllerManager: PhysicalControllerManager? = null var gameModel: GameModel? = null - var gameHost: GameHost? = null var controller: GameController? = null var performanceManager: PerformanceManager? = null var selected: GameModel? = null private var gameTimeState: MutableState? = null private var gameFpsState: MutableState? = null private var fifoState: MutableState? = null + private var progress: MutableState? = null + private var progressValue: MutableState? = null + private var showLoading: MutableState? = null + var gameHost: GameHost? = null + set(value) { + field = value + field?.setProgressStates(showLoading, progressValue, progress) + } var navController : NavHostController? = null var homeViewModel: HomeViewModel = HomeViewModel(activity, this) @@ -55,7 +65,7 @@ class MainViewModel(val activity: MainActivity) { val descriptor = game.open() - if(descriptor == 0) + if (descriptor == 0) return false gameModel = game @@ -69,7 +79,7 @@ class MainViewModel(val activity: MainActivity) { BackendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal }) - if(!success) + if (!success) return false val nativeHelpers = NativeHelpers() @@ -120,27 +130,39 @@ class MainViewModel(val activity: MainActivity) { nativeInterop.VkRequiredExtensions!!, driverHandle ) - if(!success) + if (!success) return false - success = nativeRyujinx.deviceInitialize( - settings.isHostMapped, - settings.useNce, - SystemLanguage.AmericanEnglish.ordinal, - RegionCode.USA.ordinal, - settings.enableVsync, - settings.enableDocked, - settings.enablePtc, - false, - "UTC", - settings.ignoreMissingServices - ) - if(!success) + val semaphore = Semaphore(1, 0) + runBlocking { + semaphore.acquire() + launchOnUiThread { + // We are only able to initialize the emulation context on the main thread + success = nativeRyujinx.deviceInitialize( + settings.isHostMapped, + settings.useNce, + SystemLanguage.AmericanEnglish.ordinal, + RegionCode.USA.ordinal, + settings.enableVsync, + settings.enableDocked, + settings.enablePtc, + false, + "UTC", + settings.ignoreMissingServices + ) + + semaphore.release() + } + semaphore.acquire() + semaphore.release() + } + + if (!success) return false success = nativeRyujinx.deviceLoadDescriptor(descriptor, game.isXci()) - if(!success) + if (!success) return false return true @@ -180,4 +202,15 @@ class MainViewModel(val activity: MainActivity) { val intent = Intent(activity, GameActivity::class.java) activity.startActivity(intent) } + + fun setProgressStates( + showLoading: MutableState, + progressValue: MutableState, + progress: MutableState + ) { + this.showLoading = showLoading + this.progressValue = progressValue + this.progress = progress + gameHost?.setProgressStates(showLoading, progressValue, progress) + } } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt index 5a8e44167..8f22d9e24 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt @@ -61,14 +61,13 @@ import androidx.compose.ui.zIndex import androidx.navigation.NavHostController import coil.compose.AsyncImage import com.anggrayudi.storage.extension.launchOnUiThread -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import org.ryujinx.android.MainActivity import org.ryujinx.android.R import org.ryujinx.android.viewmodels.GameModel import org.ryujinx.android.viewmodels.HomeViewModel import java.io.File import java.util.Locale +import kotlin.concurrent.thread import kotlin.math.roundToInt class HomeViews { @@ -380,20 +379,18 @@ class HomeViews { .combinedClickable( onClick = { if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000") { - runBlocking { - launch { - showLoading.value = true - val success = - viewModel.mainViewModel?.loadGame(gameModel) ?: false - if (success) { - launchOnUiThread { - viewModel.mainViewModel?.navigateToGame() - } - } else { - gameModel.close() + thread { + showLoading.value = true + val success = + viewModel.mainViewModel?.loadGame(gameModel) ?: false + if (success) { + launchOnUiThread { + viewModel.mainViewModel?.navigateToGame() } - showLoading.value = false + } else { + gameModel.close() } + showLoading.value = false } } },