forked from MeloNX/MeloNX
improve async loading. add game load progress
This commit is contained in:
parent
c0091c838d
commit
28af479e2a
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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")]
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user