1
0
forked from MeloNX/MeloNX

fix audio rebase

remove redundant sdk performance session usage

remove debug code and formatting
This commit is contained in:
Emmanuel Hansen 2024-02-25 22:06:54 +00:00
parent d40dc00769
commit 06a7c0217f
15 changed files with 273 additions and 619 deletions

View File

@ -3,7 +3,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.Ui;
using Ryujinx.HLE.UI;
using System;
using System.Collections.Generic;
using System.Linq;
@ -13,17 +13,17 @@ using System.Threading.Tasks;
namespace LibRyujinx.Android
{
internal class AndroidUiHandler : IHostUiHandler, IDisposable
internal class AndroidUIHandler : IHostUIHandler, IDisposable
{
public IHostUiTheme HostUiTheme => throw new NotImplementedException();
public ManualResetEvent _waitEvent;
public ManualResetEvent _responseEvent;
private bool _isDisposed;
private bool _isOkPressed;
private long _input;
public AndroidUiHandler()
public IHostUITheme HostUITheme => throw new NotImplementedException();
public AndroidUIHandler()
{
_waitEvent = new ManualResetEvent(false);
_responseEvent = new ManualResetEvent(false);
@ -47,7 +47,7 @@ namespace LibRyujinx.Android
return _isOkPressed;
}
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText)
{
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString("Software Keyboard"));
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(args.HeaderText ?? ""));
@ -81,7 +81,7 @@ namespace LibRyujinx.Android
return _isOkPressed;
}
public bool DisplayMessageDialog(ControllerAppletUiArgs args)
public bool DisplayMessageDialog(ControllerAppletUIArgs args)
{
string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}";

View File

@ -1,18 +0,0 @@
namespace LibRyujinx.Shared.Audio.Oboe
{
internal class OboeAudioBuffer
{
public readonly ulong DriverIdentifier;
public readonly ulong SampleCount;
public readonly byte[] Data;
public ulong SamplePlayed;
public OboeAudioBuffer(ulong driverIdentifier, byte[] data, ulong sampleCount)
{
DriverIdentifier = driverIdentifier;
Data = data;
SampleCount = sampleCount;
SamplePlayed = 0;
}
}
}

View File

@ -1,105 +0,0 @@
using Ryujinx.Audio;
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
namespace LibRyujinx.Shared.Audio.Oboe
{
internal class OboeHardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly ManualResetEvent _updateRequiredEvent;
private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<OboeHardwareDeviceSession, byte> _sessions;
public OboeHardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
_sessions = new ConcurrentDictionary<OboeHardwareDeviceSession, byte>();
}
public static bool IsSupported => true;
public ManualResetEvent GetUpdateRequiredEvent()
{
return _updateRequiredEvent;
}
public ManualResetEvent GetPauseEvent()
{
return _pauseEvent;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
{
if (channelCount == 0)
{
channelCount = 2;
}
if (sampleRate == 0)
{
sampleRate = Constants.TargetSampleRate;
}
if (direction != Direction.Output)
{
throw new NotImplementedException("Input direction is currently not implemented on Oboe backend!");
}
OboeHardwareDeviceSession session = new OboeHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
_sessions.TryAdd(session, 0);
return session;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach (OboeHardwareDeviceSession session in _sessions.Keys)
{
session.Dispose();
}
_pauseEvent.Dispose();
_sessions.Clear();
}
}
public bool SupportsSampleRate(uint sampleRate)
{
return true;
}
public bool SupportsSampleFormat(SampleFormat sampleFormat)
{
return sampleFormat != SampleFormat.Adpcm;
}
public bool SupportsChannelCount(uint channelCount)
{
return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6;
}
public bool SupportsDirection(Direction direction)
{
return direction == Direction.Output;
}
}
}

View File

@ -1,189 +0,0 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Threading;
namespace LibRyujinx.Shared.Audio.Oboe
{
internal class OboeHardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private OboeHardwareDeviceDriver _driver;
private bool _isClosed;
private bool _isWorkerActive;
private Queue<OboeAudioBuffer> _queuedBuffers;
private bool _isActive;
private ulong _playedSampleCount;
private Thread _workerThread;
private ManualResetEvent _updateRequiredEvent;
private IntPtr _session;
private object _queueLock = new object();
private object _trackLock = new object();
public OboeHardwareDeviceSession(OboeHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_isActive = false;
_playedSampleCount = 0;
_isWorkerActive = true;
_queuedBuffers = new Queue<OboeAudioBuffer>();
_updateRequiredEvent = driver.GetUpdateRequiredEvent();
_session = OboeInterop.CreateSession((int)requestedSampleFormat, requestedSampleRate, requestedChannelCount);
_workerThread = new Thread(Update);
_workerThread.Name = $"HardwareDeviceSession.Android.Track";
_workerThread.Start();
SetVolume(requestedVolume);
}
public override void UnregisterBuffer(AudioBuffer buffer) { }
public unsafe void Update(object ignored)
{
while (_isWorkerActive)
{
bool needUpdate = false;
bool hasBuffer;
OboeAudioBuffer buffer;
lock (_queueLock)
{
hasBuffer = _queuedBuffers.TryPeek(out buffer);
}
while (hasBuffer)
{
StartIfNotPlaying();
if (_isClosed)
break;
fixed(byte* ptr = buffer.Data)
OboeInterop.WriteToSession(_session, (ulong)ptr, buffer.SampleCount);
lock (_queueLock)
{
_playedSampleCount += buffer.SampleCount;
_queuedBuffers.TryDequeue(out _);
}
needUpdate = true;
lock (_queueLock)
{
hasBuffer = _queuedBuffers.TryPeek(out buffer);
}
}
if (needUpdate)
{
_updateRequiredEvent.Set();
}
// No work
Thread.Sleep(5);
}
}
public override void Dispose()
{
if (_session == 0)
return;
PrepareToClose();
OboeInterop.CloseSession(_session);
_session = 0;
}
public override void PrepareToClose()
{
_isClosed = true;
_isWorkerActive = false;
_workerThread?.Join();
Stop();
}
private void StartIfNotPlaying()
{
lock (_trackLock)
{
if (_isClosed)
return;
if (OboeInterop.IsPlaying(_session) == 0)
{
Start();
}
}
}
public override void QueueBuffer(AudioBuffer buffer)
{
lock (_queueLock)
{
OboeAudioBuffer driverBuffer = new OboeAudioBuffer(buffer.DataPointer, buffer.Data, GetSampleCount(buffer));
_queuedBuffers.Enqueue(driverBuffer);
if (_isActive)
{
StartIfNotPlaying();
}
}
}
public override float GetVolume()
{
return OboeInterop.GetSessionVolume(_session);
}
public override ulong GetPlayedSampleCount()
{
lock (_queueLock)
{
return _playedSampleCount;
}
}
public override void SetVolume(float volume)
{
volume = 1;
OboeInterop.SetSessionVolume(_session, volume);
}
public override void Start()
{
if (_isClosed)
return;
OboeInterop.StartSession(_session);
}
public override void Stop()
{
OboeInterop.StopSession(_session);
}
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{
lock (_queueLock)
{
if (!_queuedBuffers.TryPeek(out OboeAudioBuffer driverBuffer))
{
return true;
}
return driverBuffer.DriverIdentifier != buffer.DataPointer;
}
}
}
}

View File

@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace LibRyujinx.Shared.Audio.Oboe
{
internal static partial class OboeInterop
{
private const string InteropLib = "libryujinxjni";
[LibraryImport(InteropLib, EntryPoint = "create_session")]
public static partial IntPtr CreateSession(int sample_format,
uint sample_rate,
uint channel_count);
[LibraryImport(InteropLib, EntryPoint = "start_session")]
public static partial void StartSession(IntPtr session);
[LibraryImport(InteropLib, EntryPoint = "stop_session")]
public static partial void StopSession(IntPtr session);
[LibraryImport(InteropLib, EntryPoint = "close_session")]
public static partial void CloseSession(IntPtr session);
[LibraryImport(InteropLib, EntryPoint = "set_session_volume")]
public static partial void SetSessionVolume(IntPtr session, float volume);
[LibraryImport(InteropLib, EntryPoint = "get_session_volume")]
public static partial float GetSessionVolume(IntPtr session);
[LibraryImport(InteropLib, EntryPoint = "is_playing")]
public static partial int IsPlaying(IntPtr session);
[LibraryImport(InteropLib, EntryPoint = "write_to_session")]
public static partial void WriteToSession(IntPtr session, ulong data, ulong samples);
}
}

View File

@ -3,7 +3,6 @@ using LibRyujinx.Jni.Pointers;
using LibRyujinx.Jni.Primitives;
using LibRyujinx.Jni.References;
using LibRyujinx.Jni.Values;
using LibRyujinx.Shared.Audio.Oboe;
using Rxmxnx.PInvoke;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Common;
@ -233,8 +232,6 @@ namespace LibRyujinx
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var list = GetDlcContentList(GetStoredString(pathPtr), (ulong)(long)titleId);
debug_break(4);
return CreateStringArray(jEnv, list);
}

View File

@ -4,6 +4,7 @@
#ifndef RYUJINXNATIVE_RYUIJNX_H
#define RYUJINXNATIVE_RYUIJNX_H
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
@ -36,25 +37,38 @@
#define LoadLib(a) dlopen(a, RTLD_NOW)
void* _ryujinxNative = NULL;
void *_ryujinxNative = NULL;
class UiHandler {
public:
void setTitle(long storedTitle);
void setMessage(long storedMessage);
void setWatermark(long wm);
void setType(int t);
void setMode(int t);
void setMinLength(int t);
void setMaxLength(int t);
void setInitialText(long text);
void setSubtitle(long text);
long getTitle();
long getMessage();
long getWatermark();
long getInitialText();
long getSubtitle();
int type = 0;
int keyboardMode = 0;
int min_length = -1;
@ -69,11 +83,10 @@ private:
};
// Ryujinx imported functions
bool (*initialize)(char*) = NULL;
bool (*initialize)(char *) = NULL;
long _renderingThreadId = 0;
long _currentRenderingThreadId = 0;
JavaVM* _vm = nullptr;
JavaVM *_vm = nullptr;
jobject _mainActivity = nullptr;
jclass _mainActivityClass = nullptr;
string_helper str_helper = string_helper();

View File

@ -21,21 +21,20 @@
#include <chrono>
#include <csignal>
jmethodID _updateFrameTime;
JNIEnv* _rendererEnv = nullptr;
JNIEnv *_rendererEnv = nullptr;
std::chrono::time_point<std::chrono::steady_clock, std::chrono::nanoseconds> _currentTimePoint;
std::string progressInfo = "";
float progress = -1;
JNIEnv* getEnv(bool isRenderer){
JNIEnv* env;
if(isRenderer){
JNIEnv *getEnv(bool isRenderer) {
JNIEnv *env;
if (isRenderer) {
env = _rendererEnv;
}
if(env != nullptr)
if (env != nullptr)
return env;
auto result = _vm->AttachCurrentThread(&env, NULL);
@ -43,82 +42,82 @@ JNIEnv* getEnv(bool isRenderer){
return env;
}
void detachEnv(){
void detachEnv() {
auto result = _vm->DetachCurrentThread();
}
extern "C"
{
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getNativeWindow(
JNIEnv *env,
jobject instance,
jobject surface) {
auto nativeWindow = ANativeWindow_fromSurface(env, surface);
return nativeWindow == NULL ? -1 : (jlong) nativeWindow;
}
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getNativeWindow(
JNIEnv *env,
jobject instance,
jobject surface) {
auto nativeWindow = ANativeWindow_fromSurface(env, surface);
return nativeWindow == NULL ? -1 : (jlong) nativeWindow;
}
JNIEXPORT void JNICALL
Java_org_ryujinx_android_NativeHelpers_releaseNativeWindow(
JNIEnv *env,
jobject instance,
jlong window) {
auto nativeWindow = (ANativeWindow *) window;
JNIEXPORT void JNICALL
Java_org_ryujinx_android_NativeHelpers_releaseNativeWindow(
JNIEnv *env,
jobject instance,
jlong window) {
auto nativeWindow = (ANativeWindow *) window;
if (nativeWindow != NULL)
ANativeWindow_release(nativeWindow);
}
if (nativeWindow != NULL)
ANativeWindow_release(nativeWindow);
}
JNIEXPORT void JNICALL
Java_org_ryujinx_android_NativeHelpers_attachCurrentThread(
JNIEnv *env,
jobject instance) {
JavaVM* jvm = NULL;
env->GetJavaVM(&jvm);
JavaVM *jvm = NULL;
env->GetJavaVM(&jvm);
if(jvm != NULL)
jvm->AttachCurrentThread(&env, NULL);
if (jvm != NULL)
jvm->AttachCurrentThread(&env, NULL);
}
JNIEXPORT void JNICALL
Java_org_ryujinx_android_NativeHelpers_detachCurrentThread(
JNIEnv *env,
jobject instance) {
JavaVM* jvm = NULL;
JavaVM *jvm = NULL;
env->GetJavaVM(&jvm);
if(jvm != NULL)
if (jvm != NULL)
jvm->DetachCurrentThread();
}
long createSurface(long native_surface, long instance)
{
long createSurface(long native_surface, long instance) {
auto nativeWindow = (ANativeWindow *) native_surface;
VkSurfaceKHR surface;
auto vkInstance = (VkInstance)instance;
auto vkInstance = (VkInstance) instance;
auto fpCreateAndroidSurfaceKHR =
reinterpret_cast<PFN_vkCreateAndroidSurfaceKHR>(vkGetInstanceProcAddr(vkInstance, "vkCreateAndroidSurfaceKHR"));
reinterpret_cast<PFN_vkCreateAndroidSurfaceKHR>(vkGetInstanceProcAddr(vkInstance,
"vkCreateAndroidSurfaceKHR"));
if (!fpCreateAndroidSurfaceKHR)
return -1;
VkAndroidSurfaceCreateInfoKHR info = { VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR };
VkAndroidSurfaceCreateInfoKHR info = {VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR};
info.window = nativeWindow;
VK_CHECK(fpCreateAndroidSurfaceKHR(vkInstance, &info, nullptr, &surface));
return (long)surface;
return (long) surface;
}
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getCreateSurfacePtr(
JNIEnv *env,
jobject instance) {
return (jlong)createSurface;
return (jlong) createSurface;
}
char* getStringPointer(
char *getStringPointer(
JNIEnv *env,
jstring jS) {
const char *cparam = env->GetStringUTFChars(jS, 0);
auto len = env->GetStringUTFLength(jS);
char* s= new char[len];
char *s = new char[len];
strcpy(s, cparam);
env->ReleaseStringUTFChars(jS, cparam);
@ -127,7 +126,7 @@ char* getStringPointer(
jstring createString(
JNIEnv *env,
char* ch) {
char *ch) {
auto str = env->NewStringUTF(ch);
return str;
@ -144,15 +143,9 @@ jstring createStringFromStdString(
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_MainActivity_getRenderingThreadId(JNIEnv *env, jobject thiz) {
return _currentRenderingThreadId;
}
extern "C"
void setRenderingThread(){
void setRenderingThread() {
auto currentId = pthread_self();
_currentRenderingThreadId = currentId;
_renderingThreadId = currentId;
_currentTimePoint = std::chrono::high_resolution_clock::now();
@ -160,7 +153,7 @@ void setRenderingThread(){
extern "C"
JNIEXPORT void JNICALL
Java_org_ryujinx_android_MainActivity_initVm(JNIEnv *env, jobject thiz) {
JavaVM* vm = nullptr;
JavaVM *vm = nullptr;
auto success = env->GetJavaVM(&vm);
_vm = vm;
_mainActivity = thiz;
@ -171,25 +164,26 @@ extern "C"
void onFrameEnd(double time) {
auto env = getEnv(true);
auto cl = env->FindClass("org/ryujinx/android/MainActivity");
_updateFrameTime = env->GetStaticMethodID(cl, "updateRenderSessionPerformance", "(J)V");
jmethodID frameEnd = env->GetStaticMethodID(cl, "frameEnded", "(J)V");
auto now = std::chrono::high_resolution_clock::now();
auto nano = std::chrono::duration_cast<std::chrono::nanoseconds>(
now - _currentTimePoint).count();
env->CallStaticVoidMethod(cl, _updateFrameTime,
env->CallStaticVoidMethod(cl, frameEnd,
nano);
}
extern "C"
void setProgressInfo(char* info, float progressValue) {
progressInfo = std::string (info);
void setProgressInfo(char *info, float progressValue) {
progressInfo = std::string(info);
progress = progressValue;
}
bool isInitialOrientationFlipped = true;
extern "C"
void setCurrentTransform(long native_window, int transform){
if(native_window == 0 || native_window == -1)
void setCurrentTransform(long native_window, int transform) {
if (native_window == 0 || native_window == -1)
return;
auto nativeWindow = (ANativeWindow *) native_window;
@ -203,10 +197,12 @@ void setCurrentTransform(long native_window, int transform){
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
break;
case 0x2:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_90;
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_90;
break;
case 0x4:
nativeTransform = isInitialOrientationFlipped ? ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY : ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_180;
nativeTransform = isInitialOrientationFlipped
? ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY
: ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_180;
break;
case 0x8:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_270;
@ -232,7 +228,8 @@ void setCurrentTransform(long native_window, int transform){
break;
}
nativeWindow->perform(nativeWindow, NATIVE_WINDOW_SET_BUFFERS_TRANSFORM, static_cast<int32_t>(nativeTransform));
nativeWindow->perform(nativeWindow, NATIVE_WINDOW_SET_BUFFERS_TRANSFORM,
static_cast<int32_t>(nativeTransform));
}
extern "C"
@ -254,19 +251,19 @@ Java_org_ryujinx_android_NativeHelpers_loadDriver(JNIEnv *env, jobject thiz,
driverName,
nullptr,
nullptr
);
);
delete libPath;
delete privateAppsPath;
delete driverName;
return (jlong)handle;
return (jlong) handle;
}
extern "C"
void debug_break(int code){
if(code >= 3)
int r = 0;
void debug_break(int code) {
if (code >= 3)
int r = 0;
}
extern "C"
@ -278,7 +275,7 @@ Java_org_ryujinx_android_NativeHelpers_setTurboMode(JNIEnv *env, jobject thiz, j
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getMaxSwapInterval(JNIEnv *env, jobject thiz,
jlong native_window) {
jlong native_window) {
auto nativeWindow = (ANativeWindow *) native_window;
return nativeWindow->maxSwapInterval;
@ -315,14 +312,14 @@ Java_org_ryujinx_android_NativeHelpers_getProgressInfo(JNIEnv *env, jobject thiz
}
extern "C"
long storeString(char* str){
long storeString(char *str) {
return str_helper.store_cstring(str);
}
extern "C"
const char* getString(long id){
const char *getString(long id) {
auto str = str_helper.get_stored(id);
auto cstr = (char*)::malloc(str.length() + 1);
auto cstr = (char *) ::malloc(str.length() + 1);
::strcpy(cstr, str.c_str());
return cstr;
}
@ -381,7 +378,7 @@ Java_org_ryujinx_android_NativeHelpers_setIsInitialOrientationFlipped(JNIEnv *en
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestType(JNIEnv *env, jobject thiz) {
return ui_handler.type;
return ui_handler.type;
}
extern "C"
JNIEXPORT jlong JNICALL
@ -396,7 +393,7 @@ Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestMessage(JNIEnv *env, j
void UiHandler::setTitle(long storedTitle) {
if(title != -1){
if (title != -1) {
str_helper.get_stored(title);
title = -1;
}
@ -405,7 +402,7 @@ void UiHandler::setTitle(long storedTitle) {
}
void UiHandler::setMessage(long storedMessage) {
if(message != -1){
if (message != -1) {
str_helper.get_stored(message);
message = -1;
}
@ -430,7 +427,7 @@ long UiHandler::getMessage() {
}
void UiHandler::setWatermark(long wm) {
if(watermark != -1){
if (watermark != -1) {
str_helper.get_stored(watermark);
watermark = -1;
}
@ -453,7 +450,7 @@ long UiHandler::getWatermark() {
}
void UiHandler::setInitialText(long text) {
if(initialText != -1){
if (initialText != -1) {
str_helper.get_stored(watermark);
initialText = -1;
}
@ -462,7 +459,7 @@ void UiHandler::setInitialText(long text) {
}
void UiHandler::setSubtitle(long text) {
if(subtitle != -1){
if (subtitle != -1) {
str_helper.get_stored(subtitle);
subtitle = -1;
}
@ -489,12 +486,12 @@ void UiHandler::setMode(int t) {
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerMinLength(JNIEnv *env, jobject thiz) {
return ui_handler.min_length;
return ui_handler.min_length;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerMaxLength(JNIEnv *env, jobject thiz) {
return ui_handler.max_length;
return ui_handler.max_length;
}
extern "C"

View File

@ -2,7 +2,6 @@ package org.ryujinx.android
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.compose.runtime.MutableState
@ -130,7 +129,8 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
if (c >= 1000) {
if (helper.getProgressValue() == -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(
@ -144,26 +144,6 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
}
private fun runGame() {
// RenderingThreadWatcher
_renderingThreadWatcher = thread(start = true) {
var threadId = 0L
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
mainViewModel.performanceManager?.enable()
while (_isStarted) {
Thread.sleep(1000)
val newthreadId = mainViewModel.activity.getRenderingThreadId()
if (threadId != newthreadId) {
mainViewModel.performanceManager?.closeCurrentRenderingSession()
}
threadId = newthreadId
if (threadId != 0L) {
mainViewModel.performanceManager?.initializeRenderingSession(threadId)
}
}
mainViewModel.performanceManager?.closeCurrentRenderingSession()
}
}
thread {
mainViewModel.activity.uiHandler.listen()

View File

@ -1,9 +1,7 @@
package org.ryujinx.android
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.view.KeyEvent
@ -22,7 +20,6 @@ import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.QuickSettings
import org.ryujinx.android.views.MainView
import kotlin.math.abs
class MainActivity : BaseActivity() {
@ -31,24 +28,23 @@ class MainActivity : BaseActivity() {
private lateinit var motionSensorManager: MotionSensorManager
private var _isInit: Boolean = false
var isGameRunning = false
var isActive = false
var storageHelper: SimpleStorageHelper? = null
lateinit var uiHandler: UiHandler
companion object {
var mainViewModel: MainViewModel? = null
var AppPath : String = ""
var AppPath: String = ""
var StorageHelper: SimpleStorageHelper? = null
val performanceMonitor = PerformanceMonitor()
@JvmStatic
fun updateRenderSessionPerformance(gameTime : Long)
{
if(gameTime <= 0)
return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
mainViewModel?.performanceManager?.updateRenderingSessionTime(gameTime)
fun frameEnded(gameTime: Long) {
mainViewModel?.activity?.apply {
if (isActive && QuickSettings(this).enablePerformanceMode) {
mainViewModel?.performanceManager?.setTurboMode(true);
}
}
mainViewModel?.gameHost?.hideProgressIndicator()
}
}
@ -60,7 +56,6 @@ class MainActivity : BaseActivity() {
initVm()
}
external fun getRenderingThreadId() : Long
private external fun initVm()
private fun initialize() {
@ -70,26 +65,52 @@ class MainActivity : BaseActivity() {
val appPath: String = AppPath
var quickSettings = QuickSettings(this)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Debug.ordinal, quickSettings.enableDebugLogs)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Info.ordinal, quickSettings.enableInfoLogs)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Stub.ordinal, quickSettings.enableStubLogs)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Warning.ordinal, quickSettings.enableWarningLogs)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Error.ordinal, quickSettings.enableErrorLogs)
RyujinxNative.instance.loggingSetEnabled(LogLevel.AccessLog.ordinal, quickSettings.enableAccessLogs)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Guest.ordinal, quickSettings.enableGuestLogs)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Trace.ordinal, quickSettings.enableTraceLogs)
val success = RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath))
RyujinxNative.instance.loggingSetEnabled(
LogLevel.Debug.ordinal,
quickSettings.enableDebugLogs
)
RyujinxNative.instance.loggingSetEnabled(
LogLevel.Info.ordinal,
quickSettings.enableInfoLogs
)
RyujinxNative.instance.loggingSetEnabled(
LogLevel.Stub.ordinal,
quickSettings.enableStubLogs
)
RyujinxNative.instance.loggingSetEnabled(
LogLevel.Warning.ordinal,
quickSettings.enableWarningLogs
)
RyujinxNative.instance.loggingSetEnabled(
LogLevel.Error.ordinal,
quickSettings.enableErrorLogs
)
RyujinxNative.instance.loggingSetEnabled(
LogLevel.AccessLog.ordinal,
quickSettings.enableAccessLogs
)
RyujinxNative.instance.loggingSetEnabled(
LogLevel.Guest.ordinal,
quickSettings.enableGuestLogs
)
RyujinxNative.instance.loggingSetEnabled(
LogLevel.Trace.ordinal,
quickSettings.enableTraceLogs
)
val success =
RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath))
uiHandler = UiHandler()
_isInit = success
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
motionSensorManager = MotionSensorManager(this)
Thread.setDefaultUncaughtExceptionHandler(crashHandler)
if(
if (
!Environment.isExternalStorageManager()
) {
storageHelper?.storage?.requestFullStorageAccess()
@ -99,8 +120,9 @@ class MainActivity : BaseActivity() {
initialize()
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
WindowCompat.setDecorFitsSystemWindows(window,false)
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
WindowCompat.setDecorFitsSystemWindows(window, false)
mainViewModel = MainViewModel(this)
mainViewModel!!.physicalControllerManager = physicalControllerManager
@ -133,25 +155,6 @@ class MainActivity : BaseActivity() {
storageHelper?.onRestoreInstanceState(savedInstanceState)
}
// Game Stuff
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) {
}
if (enable)
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 }
}
fun setFullScreen(fullscreen: Boolean) {
requestedOrientation =
if (fullscreen) ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_FULL_USER
@ -190,20 +193,19 @@ class MainActivity : BaseActivity() {
override fun onStop() {
super.onStop()
isActive = false
if(isGameRunning) {
NativeHelpers.instance.setTurboMode(false)
force60HzRefreshRate(false)
if (isGameRunning) {
mainViewModel?.performanceManager?.setTurboMode(false)
}
}
override fun onResume() {
super.onResume()
isActive = true
if(isGameRunning) {
if (isGameRunning) {
setFullScreen(true)
NativeHelpers.instance.setTurboMode(true)
force60HzRefreshRate(true)
if (QuickSettings(this).enableMotion)
motionSensorManager.register()
}
@ -211,10 +213,10 @@ class MainActivity : BaseActivity() {
override fun onPause() {
super.onPause()
isActive = true
if(isGameRunning) {
NativeHelpers.instance.setTurboMode(false)
force60HzRefreshRate(false)
if (isGameRunning) {
mainViewModel?.performanceManager?.setTurboMode(false)
}
motionSensorManager.unregister()

View File

@ -1,49 +1,31 @@
package org.ryujinx.android
import android.os.Build
import android.os.PerformanceHintManager
import androidx.annotation.RequiresApi
import android.content.Intent
import kotlin.math.abs
class PerformanceManager(private val performanceHintManager: PerformanceHintManager) {
private var _isEnabled: Boolean = false
private var renderingSession: PerformanceHintManager.Session? = null
private val DEFAULT_TARGET_NS = 16666666L
@RequiresApi(Build.VERSION_CODES.S)
fun initializeRenderingSession(threadId : Long){
if(!_isEnabled || renderingSession != null)
return
val threads = IntArray(1)
threads[0] = threadId.toInt()
renderingSession = performanceHintManager.createHintSession(threads, DEFAULT_TARGET_NS)
}
@RequiresApi(Build.VERSION_CODES.S)
fun closeCurrentRenderingSession() {
if (_isEnabled)
renderingSession?.apply {
renderingSession = null
this.close()
class PerformanceManager(private val activity: MainActivity) {
companion object {
fun force60HzRefreshRate(enable: Boolean, activity: MainActivity) {
// 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)
activity.sendBroadcast(setFpsIntent)
} catch (_: Exception) {
}
}
fun enable(){
_isEnabled = true
}
@RequiresApi(Build.VERSION_CODES.S)
fun updateRenderingSessionTime(newTime : Long){
if(!_isEnabled)
return
var effectiveTime = newTime
if(newTime < DEFAULT_TARGET_NS)
effectiveTime = DEFAULT_TARGET_NS
renderingSession?.apply {
this.reportActualWorkDuration(effectiveTime)
if (enable)
activity.display?.supportedModes?.minByOrNull { abs(it.refreshRate - 60f) }
?.let { activity.window.attributes.preferredDisplayModeId = it.modeId }
else
activity.display?.supportedModes?.maxByOrNull { it.refreshRate }
?.let { activity.window.attributes.preferredDisplayModeId = it.modeId }
}
}
fun setTurboMode(enable: Boolean) {
NativeHelpers.instance.setTurboMode(enable)
force60HzRefreshRate(enable, activity)
}
}

View File

@ -1,9 +1,6 @@
package org.ryujinx.android.viewmodels
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.os.PerformanceHintManager
import androidx.compose.runtime.MutableState
import androidx.navigation.NavHostController
import com.anggrayudi.storage.extension.launchOnUiThread
@ -49,16 +46,12 @@ class MainViewModel(val activity: MainActivity) {
field = value
field?.setProgressStates(showLoading, progressValue, progress)
}
var navController : NavHostController? = null
var navController: NavHostController? = null
var homeViewModel: HomeViewModel = HomeViewModel(activity, this)
init {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val hintService =
activity.getSystemService(Context.PERFORMANCE_HINT_SERVICE) as PerformanceHintManager
performanceManager = PerformanceManager(hintService)
}
performanceManager = PerformanceManager(activity)
}
fun closeGame() {
@ -70,14 +63,14 @@ class MainViewModel(val activity: MainActivity) {
motionSensorManager?.setControllerId(-1)
}
fun refreshFirmwareVersion(){
fun refreshFirmwareVersion() {
var handle = RyujinxNative.instance.deviceGetInstalledFirmwareVersion()
if(handle != -1L) {
if (handle != -1L) {
firmwareVersion = NativeHelpers.instance.getStringJava(handle)
}
}
fun loadGame(game:GameModel) : Boolean {
fun loadGame(game: GameModel): Boolean {
val nativeRyujinx = RyujinxNative.instance
val descriptor = game.open()
@ -188,7 +181,7 @@ class MainViewModel(val activity: MainActivity) {
return true
}
fun loadMiiEditor() : Boolean {
fun loadMiiEditor(): Boolean {
val nativeRyujinx = RyujinxNative.instance
gameModel = null
@ -292,42 +285,42 @@ class MainViewModel(val activity: MainActivity) {
return true
}
fun clearPptcCache(titleId :String){
if(titleId.isNotEmpty()){
fun clearPptcCache(titleId: String) {
if (titleId.isNotEmpty()) {
val basePath = MainActivity.AppPath + "/games/$titleId/cache/cpu"
if(File(basePath).exists()){
if (File(basePath).exists()) {
var caches = mutableListOf<String>()
val mainCache = basePath + "${File.separator}0"
File(mainCache).listFiles()?.forEach {
if(it.isFile && it.name.endsWith(".cache"))
if (it.isFile && it.name.endsWith(".cache"))
caches.add(it.absolutePath)
}
val backupCache = basePath + "${File.separator}1"
File(backupCache).listFiles()?.forEach {
if(it.isFile && it.name.endsWith(".cache"))
if (it.isFile && it.name.endsWith(".cache"))
caches.add(it.absolutePath)
}
for(path in caches)
for (path in caches)
File(path).delete()
}
}
}
fun purgeShaderCache(titleId :String) {
if(titleId.isNotEmpty()){
fun purgeShaderCache(titleId: String) {
if (titleId.isNotEmpty()) {
val basePath = MainActivity.AppPath + "/games/$titleId/cache/shader"
if(File(basePath).exists()){
if (File(basePath).exists()) {
var caches = mutableListOf<String>()
File(basePath).listFiles()?.forEach {
if(!it.isFile)
if (!it.isFile)
it.delete()
else{
if(it.name.endsWith(".toc") || it.name.endsWith(".data"))
else {
if (it.name.endsWith(".toc") || it.name.endsWith(".data"))
caches.add(it.absolutePath)
}
}
for(path in caches)
for (path in caches)
File(path).delete()
}
}
@ -347,7 +340,7 @@ class MainViewModel(val activity: MainActivity) {
fifo: Double,
gameFps: Double,
gameTime: Double
){
) {
fifoState?.apply {
this.value = fifo
}

View File

@ -14,10 +14,11 @@ class QuickSettings(val activity: Activity) {
var isHostMapped: Boolean
var enableShaderCache: Boolean
var enableTextureRecompression: Boolean
var resScale : Float
var isGrid : Boolean
var useSwitchLayout : Boolean
var enableMotion : Boolean
var resScale: Float
var isGrid: Boolean
var useSwitchLayout: Boolean
var enableMotion: Boolean
var enablePerformanceMode: Boolean
// Logs
var enableDebugLogs: Boolean
@ -29,7 +30,8 @@ class QuickSettings(val activity: Activity) {
var enableAccessLogs: Boolean
var enableTraceLogs: Boolean
private var sharedPref: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
private var sharedPref: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(activity)
init {
isHostMapped = sharedPref.getBoolean("isHostMapped", true)
@ -45,6 +47,7 @@ class QuickSettings(val activity: Activity) {
isGrid = sharedPref.getBoolean("isGrid", true)
useSwitchLayout = sharedPref.getBoolean("useSwitchLayout", true)
enableMotion = sharedPref.getBoolean("enableMotion", true)
enablePerformanceMode = sharedPref.getBoolean("enablePerformanceMode", true)
enableDebugLogs = sharedPref.getBoolean("enableDebugLogs", false)
enableStubLogs = sharedPref.getBoolean("enableStubLogs", false)
@ -56,7 +59,7 @@ class QuickSettings(val activity: Activity) {
enableTraceLogs = sharedPref.getBoolean("enableStubLogs", false)
}
fun save(){
fun save() {
val editor = sharedPref.edit()
editor.putBoolean("isHostMapped", isHostMapped)
@ -72,6 +75,7 @@ class QuickSettings(val activity: Activity) {
editor.putBoolean("isGrid", isGrid)
editor.putBoolean("useSwitchLayout", useSwitchLayout)
editor.putBoolean("enableMotion", enableMotion)
editor.putBoolean("enablePerformanceMode", enablePerformanceMode)
editor.putBoolean("enableDebugLogs", enableDebugLogs)
editor.putBoolean("enableStubLogs", enableStubLogs)

View File

@ -56,6 +56,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
isGrid: MutableState<Boolean>,
useSwitchLayout: MutableState<Boolean>,
enableMotion: MutableState<Boolean>,
enablePerformanceMode: MutableState<Boolean>,
enableDebugLogs: MutableState<Boolean>,
enableStubLogs: MutableState<Boolean>,
enableInfoLogs: MutableState<Boolean>,
@ -80,6 +81,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
isGrid.value = sharedPref.getBoolean("isGrid", true)
useSwitchLayout.value = sharedPref.getBoolean("useSwitchLayout", true)
enableMotion.value = sharedPref.getBoolean("enableMotion", true)
enablePerformanceMode.value = sharedPref.getBoolean("enablePerformanceMode", false)
enableDebugLogs.value = sharedPref.getBoolean("enableDebugLogs", false)
enableStubLogs.value = sharedPref.getBoolean("enableStubLogs", false)
@ -105,6 +107,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
isGrid: MutableState<Boolean>,
useSwitchLayout: MutableState<Boolean>,
enableMotion: MutableState<Boolean>,
enablePerformanceMode: MutableState<Boolean>,
enableDebugLogs: MutableState<Boolean>,
enableStubLogs: MutableState<Boolean>,
enableInfoLogs: MutableState<Boolean>,
@ -129,6 +132,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
editor.putBoolean("isGrid", isGrid.value)
editor.putBoolean("useSwitchLayout", useSwitchLayout.value)
editor.putBoolean("enableMotion", enableMotion.value)
editor.putBoolean("enablePerformanceMode", enablePerformanceMode.value)
editor.putBoolean("enableDebugLogs", enableDebugLogs.value)
editor.putBoolean("enableStubLogs", enableStubLogs.value)
@ -255,7 +259,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
}
}
fun clearFirmwareSelection(installState: MutableState<FirmwareInstallState>){
fun clearFirmwareSelection(installState: MutableState<FirmwareInstallState>) {
selectedFirmwareFile = null
selectedFirmwareVersion = ""
installState.value = FirmwareInstallState.None
@ -263,7 +267,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
}
enum class FirmwareInstallState{
enum class FirmwareInstallState {
None,
Cancelled,
Verifying,

View File

@ -57,6 +57,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.file.extension
import org.ryujinx.android.Helpers
@ -122,6 +123,7 @@ class SettingViews {
val isGrid = remember { mutableStateOf(true) }
val useSwitchLayout = remember { mutableStateOf(true) }
val enableMotion = remember { mutableStateOf(true) }
val enablePerformanceMode = remember { mutableStateOf(true) }
val enableDebugLogs = remember { mutableStateOf(true) }
val enableStubLogs = remember { mutableStateOf(true) }
@ -144,6 +146,7 @@ class SettingViews {
isGrid,
useSwitchLayout,
enableMotion,
enablePerformanceMode,
enableDebugLogs,
enableStubLogs,
enableInfoLogs,
@ -177,6 +180,7 @@ class SettingViews {
isGrid,
useSwitchLayout,
enableMotion,
enablePerformanceMode,
enableDebugLogs,
enableStubLogs,
enableInfoLogs,
@ -192,9 +196,11 @@ class SettingViews {
}
})
}) { contentPadding ->
Column(modifier = Modifier
.padding(contentPadding)
.verticalScroll(rememberScrollState())) {
Column(
modifier = Modifier
.padding(contentPadding)
.verticalScroll(rememberScrollState())
) {
ExpandableView(onCardArrowClick = { }, title = "App") {
Column(modifier = Modifier.fillMaxWidth()) {
Row(
@ -303,9 +309,9 @@ class SettingViews {
}
}
if(showFirwmareDialog.value) {
if (showFirwmareDialog.value) {
AlertDialog(onDismissRequest = {
if(firmwareInstallState.value != FirmwareInstallState.Install) {
if (firmwareInstallState.value != FirmwareInstallState.Install) {
showFirwmareDialog.value = false
settingsViewModel.clearFirmwareSelection(firmwareInstallState)
}
@ -327,7 +333,8 @@ class SettingViews {
Text(text = "Select a zip or XCI file to install from.")
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp)
) {
Button(onClick = {
@ -350,7 +357,8 @@ class SettingViews {
Text(text = "Firmware ${settingsViewModel.selectedFirmwareVersion} will be installed. Do you want to continue?")
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp)
) {
Button(onClick = {
@ -358,9 +366,11 @@ class SettingViews {
firmwareInstallState
)
if(firmwareInstallState.value == FirmwareInstallState.None){
if (firmwareInstallState.value == FirmwareInstallState.None) {
showFirwmareDialog.value = false
settingsViewModel.clearFirmwareSelection(firmwareInstallState)
settingsViewModel.clearFirmwareSelection(
firmwareInstallState
)
}
}, modifier = Modifier.padding(horizontal = 8.dp)) {
Text(text = "Yes")
@ -376,34 +386,32 @@ class SettingViews {
}
} else if (firmwareInstallState.value == FirmwareInstallState.Install) {
Text(text = "Installing Firmware ${settingsViewModel.selectedFirmwareVersion}...")
LinearProgressIndicator(modifier = Modifier
.padding(top = 4.dp))
LinearProgressIndicator(
modifier = Modifier
.padding(top = 4.dp)
)
} else if (firmwareInstallState.value == FirmwareInstallState.Verifying) {
Text(text = "Verifying selected file...")
LinearProgressIndicator(modifier = Modifier
.fillMaxWidth()
)
}
else if (firmwareInstallState.value == FirmwareInstallState.Done) {
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
)
} else if (firmwareInstallState.value == FirmwareInstallState.Done) {
Text(text = "Installed Firmware ${settingsViewModel.selectedFirmwareVersion}")
firmwareVersion.value = mainViewModel.firmwareVersion
}
else if(firmwareInstallState.value == FirmwareInstallState.Cancelled){
} else if (firmwareInstallState.value == FirmwareInstallState.Cancelled) {
val file = settingsViewModel.selectedFirmwareFile
if(file != null){
if(file.extension == "xci" || file.extension == "zip"){
if(settingsViewModel.selectedFirmwareVersion.isEmpty()) {
if (file != null) {
if (file.extension == "xci" || file.extension == "zip") {
if (settingsViewModel.selectedFirmwareVersion.isEmpty()) {
Text(text = "Unable to find version in selected file")
}
else {
} else {
Text(text = "Unknown Error has occurred. Please check logs")
}
}
else {
} else {
Text(text = "File type is not supported")
}
}
else {
} else {
Text(text = "File type is not supported")
}
}
@ -503,6 +511,32 @@ class SettingViews {
ignoreMissingServices.value = !ignoreMissingServices.value
})
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.align(Alignment.CenterVertically)
) {
Text(
text = "Enable Performance Mode",
)
Text(
text = "Forces CPU and GPU to run at max clocks if available.",
fontSize = 12.sp
)
Text(
text = "OS power settings may override this.",
fontSize = 12.sp
)
}
Switch(checked = enablePerformanceMode.value, onCheckedChange = {
enablePerformanceMode.value = !enablePerformanceMode.value
})
}
val isImporting = remember {
mutableStateOf(false)
}
@ -1041,6 +1075,7 @@ class SettingViews {
isGrid,
useSwitchLayout,
enableMotion,
enablePerformanceMode,
enableDebugLogs,
enableStubLogs,
enableInfoLogs,