improve async loading. add game load progress

This commit is contained in:
Emmanuel Hansen 2023-08-26 12:02:58 +00:00
parent c0091c838d
commit 28af479e2a
11 changed files with 393 additions and 137 deletions

View File

@ -49,6 +49,9 @@ namespace LibRyujinx
[DllImport("libryujinxjni")] [DllImport("libryujinxjni")]
internal extern static void onFrameEnd(double time); internal extern static void onFrameEnd(double time);
[DllImport("libryujinxjni")]
internal extern static void setProgressInfo(IntPtr info, float progress);
[DllImport("libryujinxjni")] [DllImport("libryujinxjni")]
internal extern static void setCurrentTransform(long native_window, int transform); internal extern static void setCurrentTransform(long native_window, int transform);
@ -287,7 +290,7 @@ namespace LibRyujinx
extensions.Add(GetString(jEnv, ext)); extensions.Add(GetString(jEnv, ext));
} }
if((long)driverHandle != 0) if ((long)driverHandle != 0)
{ {
VulkanLoader = new VulkanLoader((IntPtr)(long)driverHandle); VulkanLoader = new VulkanLoader((IntPtr)(long)driverHandle);
} }

View File

@ -1,10 +1,13 @@
using ARMeilleure.Translation; using ARMeilleure.Translation;
using LibHac.Bcat;
using LibRyujinx.Shared; using LibRyujinx.Shared;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Cpu;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan; using Ryujinx.Graphics.Vulkan;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
@ -153,64 +156,125 @@ namespace LibRyujinx
device.Gpu.Renderer.Initialize(GraphicsDebugLevel.None); device.Gpu.Renderer.Initialize(GraphicsDebugLevel.None);
_gpuCancellationTokenSource = new CancellationTokenSource(); _gpuCancellationTokenSource = new CancellationTokenSource();
device.Gpu.Renderer.RunLoop(() => device.Gpu.ShaderCacheStateChanged += LoadProgressStateChangedHandler;
device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += LoadProgressStateChangedHandler;
try
{ {
_gpuDoneEvent.Reset(); device.Gpu.Renderer.RunLoop(() =>
device.Gpu.SetGpuThread();
device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
Translator.IsReadyForTranslation.Set();
_isActive = true;
while (_isActive)
{ {
if (_isStopped) _gpuDoneEvent.Reset();
{ device.Gpu.SetGpuThread();
break; device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
} Translator.IsReadyForTranslation.Set();
debug_break(1); _isActive = true;
if (Ryujinx.Common.SystemInfo.SystemInfo.IsBionic) while (_isActive)
{ {
setRenderingThread(); if (_isStopped)
}
if (device.WaitFifo())
{
device.Statistics.RecordFifoStart();
device.ProcessFrame();
device.Statistics.RecordFifoEnd();
}
while (device.ConsumeFrameAvailable())
{
device.PresentFrame(() =>
{ {
VulkanRenderer? vk = device.Gpu.Renderer as VulkanRenderer; break;
if(vk == null) }
{
vk = (device.Gpu.Renderer as ThreadedRenderer)?.BaseRenderer as VulkanRenderer;
}
if(vk != null) debug_break(1);
{
var transform = vk.CurrentTransform;
setCurrentTransform(_window, (int)transform); if (Ryujinx.Common.SystemInfo.SystemInfo.IsBionic)
} {
_swapBuffersCallback?.Invoke(); 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) if (device.Gpu.Renderer is ThreadedRenderer threaded)
{ {
threaded.FlushThreadedCommands(); threaded.FlushThreadedCommands();
} }
_gpuDoneEvent.Set(); _gpuDoneEvent.Set();
}); });
}
finally
{
device.Gpu.ShaderCacheStateChanged -= LoadProgressStateChangedHandler;
device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= LoadProgressStateChangedHandler;
}
}
private static void LoadProgressStateChangedHandler<T>(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")] [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_swap_buffer_callback")]

View File

@ -26,6 +26,9 @@ JNIEnv* _rendererEnv = nullptr;
std::chrono::time_point<std::chrono::steady_clock, std::chrono::nanoseconds> _currentTimePoint; std::chrono::time_point<std::chrono::steady_clock, std::chrono::nanoseconds> _currentTimePoint;
std::string progressInfo = "";
float progress = -1;
JNIEnv* getEnv(bool isRenderer){ JNIEnv* getEnv(bool isRenderer){
JNIEnv* env; JNIEnv* env;
if(isRenderer){ if(isRenderer){
@ -130,6 +133,14 @@ jstring createString(
return str; return str;
} }
jstring createStringFromStdString(
JNIEnv *env,
std::string s) {
auto str = env->NewStringUTF(s.c_str());
return str;
}
} }
extern "C" extern "C"
@ -168,6 +179,11 @@ void onFrameEnd(double time) {
env->CallStaticVoidMethod(cl, _updateFrameTime, env->CallStaticVoidMethod(cl, _updateFrameTime,
nano); nano);
} }
extern "C"
void setProgressInfo(char* info, float progressValue) {
progressInfo = std::string (info);
progress = progressValue;
}
extern "C" extern "C"
void setCurrentTransform(long native_window, int transform){ 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); 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);
}

View File

@ -21,9 +21,11 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -50,7 +52,9 @@ import kotlin.math.abs
import kotlin.math.roundToInt import kotlin.math.roundToInt
class GameActivity : ComponentActivity() { class GameActivity : ComponentActivity() {
private var physicalControllerManager: PhysicalControllerManager = PhysicalControllerManager(this) private var physicalControllerManager: PhysicalControllerManager =
PhysicalControllerManager(this)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -70,7 +74,7 @@ class GameActivity : ComponentActivity() {
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
override fun dispatchKeyEvent(event: KeyEvent?): Boolean { override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
event?.apply { event?.apply {
if(physicalControllerManager.onKeyEvent(this)) if (physicalControllerManager.onKeyEvent(this))
return true return true
} }
return super.dispatchKeyEvent(event) return super.dispatchKeyEvent(event)
@ -105,20 +109,22 @@ class GameActivity : ComponentActivity() {
force60HzRefreshRate(false) 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 // Hack for MIUI devices since they don't support the standard Android APIs
try { try {
val setFpsIntent = Intent("com.miui.powerkeeper.SET_ACTIVITY_FPS") val setFpsIntent = Intent("com.miui.powerkeeper.SET_ACTIVITY_FPS")
setFpsIntent.putExtra("package_name", "org.ryujinx.android") setFpsIntent.putExtra("package_name", "org.ryujinx.android")
setFpsIntent.putExtra("isEnter", enable) setFpsIntent.putExtra("isEnter", enable)
sendBroadcast(setFpsIntent) sendBroadcast(setFpsIntent)
} catch (_ : Exception) { } catch (_: Exception) {
} }
if (enable) 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 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) { private fun setFullScreen(fullscreen: Boolean) {
@ -139,6 +145,7 @@ class GameActivity : ComponentActivity() {
} }
} }
} }
@Composable @Composable
fun GameView(mainViewModel: MainViewModel) { fun GameView(mainViewModel: MainViewModel) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
@ -170,6 +177,20 @@ class GameActivity : ComponentActivity() {
mutableStateOf(false) 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 // touch surface
Surface(color = Color.Transparent, modifier = Modifier Surface(color = Color.Transparent, modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -213,47 +234,54 @@ class GameActivity : ComponentActivity() {
} }
}) { }) {
} }
GameController.Compose(mainViewModel) if (!showLoading.value) {
Row( GameController.Compose(mainViewModel)
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){ Row(
Popup(alignment = Alignment.BottomCenter, onDismissRequest = {showMore.value = false}) { modifier = Modifier
Surface(modifier = Modifier.padding(16.dp), .align(Alignment.BottomCenter)
shape = MaterialTheme.shapes.medium) { .padding(8.dp)
Row(modifier = Modifier.padding(8.dp)) { ) {
IconButton(modifier = Modifier.padding(4.dp), onClick = { IconButton(modifier = Modifier.padding(4.dp), onClick = {
showMore.value = false showMore.value = true
showController.value = !showController.value }) {
mainViewModel.controller?.setVisible(showController.value) Icon(
}) { imageVector = CssGgIcons.ToolbarBottom,
Icon( contentDescription = "Open Panel"
imageVector = Icons.videoGame(), )
contentDescription = "Toggle Virtual Pad" }
) }
}
IconButton(modifier = Modifier.padding(4.dp), onClick = { if (showMore.value) {
showMore.value = false Popup(
enableVsync.value = !enableVsync.value alignment = Alignment.BottomCenter,
RyujinxNative().graphicsRendererSetVsync(enableVsync.value) onDismissRequest = { showMore.value = false }) {
}) { Surface(
Icon( modifier = Modifier.padding(16.dp),
imageVector = Icons.vSync(), shape = MaterialTheme.shapes.medium
tint = if(enableVsync.value) Color.Green else Color.Red, ) {
contentDescription = "Toggle VSync" 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 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) { if (showBackNotice.value) {
AlertDialog(onDismissRequest = { showBackNotice.value = false }) { AlertDialog(onDismissRequest = { showBackNotice.value = false }) {
Column { Column {

View File

@ -5,6 +5,7 @@ import android.content.Context
import android.os.Build import android.os.Build
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import androidx.compose.runtime.MutableState
import org.ryujinx.android.viewmodels.GameModel import org.ryujinx.android.viewmodels.GameModel
import org.ryujinx.android.viewmodels.MainViewModel import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.QuickSettings import org.ryujinx.android.viewmodels.QuickSettings
@ -12,6 +13,10 @@ import kotlin.concurrent.thread
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
class GameHost(context: Context?, private val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback { 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
private var showLoading: MutableState<Boolean>? = null
private var game: GameModel? = null private var game: GameModel? = null
private var _isClosed: Boolean = false private var _isClosed: Boolean = false
private var _renderingThreadWatcher: Thread? = null private var _renderingThreadWatcher: Thread? = null
@ -29,6 +34,8 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
holder.addCallback(this) holder.addCallback(this)
nativeWindow = NativeWindow(this) nativeWindow = NativeWindow(this)
mainViewModel.gameHost = this
} }
override fun surfaceCreated(holder: SurfaceHolder) { override fun surfaceCreated(holder: SurfaceHolder) {
@ -75,7 +82,6 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
} }
private fun start(surfaceHolder: SurfaceHolder) { private fun start(surfaceHolder: SurfaceHolder) {
mainViewModel.gameHost = this
if(_isStarted) if(_isStarted)
return return
@ -106,11 +112,31 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
_updateThread = thread(start = true) { _updateThread = thread(start = true) {
var c = 0 var c = 0
val helper = NativeHelpers()
while (_isStarted) { while (_isStarted) {
_nativeRyujinx.inputUpdate() _nativeRyujinx.inputUpdate()
Thread.sleep(1) 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++ c++
if (c >= 1000) { if (c >= 1000) {
if(helper.getProgressValue() == -1f)
progress?.apply {
this.value = "Loading ${game!!.titleName}"
}
c = 0 c = 0
mainViewModel.updateStats(_nativeRyujinx.deviceGetGameFifo(), _nativeRyujinx.deviceGetGameFrameRate(), _nativeRyujinx.deviceGetGameFrameTime()) mainViewModel.updateStats(_nativeRyujinx.deviceGetGameFifo(), _nativeRyujinx.deviceGetGameFrameRate(), _nativeRyujinx.deviceGetGameFrameTime())
} }
@ -143,4 +169,26 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
game?.close() game?.close()
} }
fun setProgressStates(
showLoading: MutableState<Boolean>?,
progressValue: MutableState<Float>?,
progress: MutableState<String>?
) {
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
}
}
} }

View File

@ -34,6 +34,8 @@ class MainActivity : ComponentActivity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
mainViewModel?.performanceManager?.updateRenderingSessionTime(gameTime) mainViewModel?.performanceManager?.updateRenderingSessionTime(gameTime)
} }
mainViewModel?.gameHost?.hideProgressIndicator()
} }
} }

View File

@ -26,4 +26,6 @@ class NativeHelpers {
external fun getMaxSwapInterval(nativeWindow: Long): Int external fun getMaxSwapInterval(nativeWindow: Long): Int
external fun getMinSwapInterval(nativeWindow: Long): Int external fun getMinSwapInterval(nativeWindow: Long): Int
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
external fun getProgressInfo() : String
external fun getProgressValue() : Float
} }

View File

@ -8,7 +8,7 @@ import org.ryujinx.android.RyujinxNative
class GameModel(var file: DocumentFile, val context: Context) { class GameModel(var file: DocumentFile, val context: Context) {
private var descriptor: ParcelFileDescriptor? = null var descriptor: ParcelFileDescriptor? = null
var fileName: String? var fileName: String?
var fileSize = 0.0 var fileSize = 0.0
var titleName: String? = null var titleName: String? = null

View File

@ -11,11 +11,13 @@ import com.anggrayudi.storage.file.extension
import com.anggrayudi.storage.file.getAbsolutePath import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.search import com.anggrayudi.storage.file.search
import org.ryujinx.android.MainActivity import org.ryujinx.android.MainActivity
import kotlin.concurrent.thread
class HomeViewModel( class HomeViewModel(
val activity: MainActivity? = null, val activity: MainActivity? = null,
val mainViewModel: MainViewModel? = null val mainViewModel: MainViewModel? = null
) { ) {
private var isLoading: Boolean = false
private var gameList: SnapshotStateList<GameModel>? = null private var gameList: SnapshotStateList<GameModel>? = null
private var loadedCache: List<GameModel> = listOf() private var loadedCache: List<GameModel> = listOf()
private var gameFolderPath: DocumentFile? = null private var gameFolderPath: DocumentFile? = null
@ -68,23 +70,39 @@ class HomeViewModel(
fun reloadGameList() { fun reloadGameList() {
var storage = activity?.storageHelper ?: return var storage = activity?.storageHelper ?: return
if(isLoading)
return
val folder = gameFolderPath ?: return val folder = gameFolderPath ?: return
isLoading = true
val files = mutableListOf<GameModel>() val files = mutableListOf<GameModel>()
for (file in folder.search(false, DocumentFileType.FILE)) { thread {
if (file.extension == "xci" || file.extension == "nsp") try {
activity.let { for (file in folder.search(false, DocumentFileType.FILE)) {
files.add(GameModel(file, it)) 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() { private fun applyFilter() {
if(isLoading)
return
gameList?.clear() gameList?.clear()
gameList?.addAll(loadedCache) gameList?.addAll(loadedCache)
} }
@ -93,4 +111,4 @@ class HomeViewModel(
gameList = list gameList = list
applyFilter() applyFilter()
} }
} }

View File

@ -7,6 +7,9 @@ import android.os.Build
import android.os.PerformanceHintManager import android.os.PerformanceHintManager
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.navigation.NavHostController 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.GameActivity
import org.ryujinx.android.GameController import org.ryujinx.android.GameController
import org.ryujinx.android.GameHost import org.ryujinx.android.GameHost
@ -25,13 +28,20 @@ import java.io.File
class MainViewModel(val activity: MainActivity) { class MainViewModel(val activity: MainActivity) {
var physicalControllerManager: PhysicalControllerManager? = null var physicalControllerManager: PhysicalControllerManager? = null
var gameModel: GameModel? = null var gameModel: GameModel? = null
var gameHost: GameHost? = null
var controller: GameController? = null var controller: GameController? = null
var performanceManager: PerformanceManager? = null var performanceManager: PerformanceManager? = null
var selected: GameModel? = null var selected: GameModel? = null
private var gameTimeState: MutableState<Double>? = null private var gameTimeState: MutableState<Double>? = null
private var gameFpsState: MutableState<Double>? = null private var gameFpsState: MutableState<Double>? = null
private var fifoState: MutableState<Double>? = null private var fifoState: MutableState<Double>? = null
private var progress: MutableState<String>? = null
private var progressValue: MutableState<Float>? = null
private var showLoading: MutableState<Boolean>? = null
var gameHost: GameHost? = null
set(value) {
field = value
field?.setProgressStates(showLoading, progressValue, progress)
}
var navController : NavHostController? = null var navController : NavHostController? = null
var homeViewModel: HomeViewModel = HomeViewModel(activity, this) var homeViewModel: HomeViewModel = HomeViewModel(activity, this)
@ -55,7 +65,7 @@ class MainViewModel(val activity: MainActivity) {
val descriptor = game.open() val descriptor = game.open()
if(descriptor == 0) if (descriptor == 0)
return false return false
gameModel = game gameModel = game
@ -69,7 +79,7 @@ class MainViewModel(val activity: MainActivity) {
BackendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal BackendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
}) })
if(!success) if (!success)
return false return false
val nativeHelpers = NativeHelpers() val nativeHelpers = NativeHelpers()
@ -120,27 +130,39 @@ class MainViewModel(val activity: MainActivity) {
nativeInterop.VkRequiredExtensions!!, nativeInterop.VkRequiredExtensions!!,
driverHandle driverHandle
) )
if(!success) if (!success)
return false return false
success = nativeRyujinx.deviceInitialize( val semaphore = Semaphore(1, 0)
settings.isHostMapped, runBlocking {
settings.useNce, semaphore.acquire()
SystemLanguage.AmericanEnglish.ordinal, launchOnUiThread {
RegionCode.USA.ordinal, // We are only able to initialize the emulation context on the main thread
settings.enableVsync, success = nativeRyujinx.deviceInitialize(
settings.enableDocked, settings.isHostMapped,
settings.enablePtc, settings.useNce,
false, SystemLanguage.AmericanEnglish.ordinal,
"UTC", RegionCode.USA.ordinal,
settings.ignoreMissingServices settings.enableVsync,
) settings.enableDocked,
if(!success) settings.enablePtc,
false,
"UTC",
settings.ignoreMissingServices
)
semaphore.release()
}
semaphore.acquire()
semaphore.release()
}
if (!success)
return false return false
success = nativeRyujinx.deviceLoadDescriptor(descriptor, game.isXci()) success = nativeRyujinx.deviceLoadDescriptor(descriptor, game.isXci())
if(!success) if (!success)
return false return false
return true return true
@ -180,4 +202,15 @@ class MainViewModel(val activity: MainActivity) {
val intent = Intent(activity, GameActivity::class.java) val intent = Intent(activity, GameActivity::class.java)
activity.startActivity(intent) activity.startActivity(intent)
} }
fun setProgressStates(
showLoading: MutableState<Boolean>,
progressValue: MutableState<Float>,
progress: MutableState<String>
) {
this.showLoading = showLoading
this.progressValue = progressValue
this.progress = progress
gameHost?.setProgressStates(showLoading, progressValue, progress)
}
} }

View File

@ -61,14 +61,13 @@ import androidx.compose.ui.zIndex
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.anggrayudi.storage.extension.launchOnUiThread import com.anggrayudi.storage.extension.launchOnUiThread
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.ryujinx.android.MainActivity import org.ryujinx.android.MainActivity
import org.ryujinx.android.R import org.ryujinx.android.R
import org.ryujinx.android.viewmodels.GameModel import org.ryujinx.android.viewmodels.GameModel
import org.ryujinx.android.viewmodels.HomeViewModel import org.ryujinx.android.viewmodels.HomeViewModel
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
import kotlin.concurrent.thread
import kotlin.math.roundToInt import kotlin.math.roundToInt
class HomeViews { class HomeViews {
@ -380,20 +379,18 @@ class HomeViews {
.combinedClickable( .combinedClickable(
onClick = { onClick = {
if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000") { if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000") {
runBlocking { thread {
launch { showLoading.value = true
showLoading.value = true val success =
val success = viewModel.mainViewModel?.loadGame(gameModel) ?: false
viewModel.mainViewModel?.loadGame(gameModel) ?: false if (success) {
if (success) { launchOnUiThread {
launchOnUiThread { viewModel.mainViewModel?.navigateToGame()
viewModel.mainViewModel?.navigateToGame()
}
} else {
gameModel.close()
} }
showLoading.value = false } else {
gameModel.close()
} }
showLoading.value = false
} }
} }
}, },