diff --git a/src/LibRyujinx/Android/JniExportedMethods.cs b/src/LibRyujinx/Android/JniExportedMethods.cs index 782b05c11..f9bf65434 100644 --- a/src/LibRyujinx/Android/JniExportedMethods.cs +++ b/src/LibRyujinx/Android/JniExportedMethods.cs @@ -36,6 +36,12 @@ namespace LibRyujinx [DllImport("libryujinxjni")] private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch); + [DllImport("libryujinxjni")] + internal extern static void setRenderingThread(); + + [DllImport("libryujinxjni")] + internal extern static void onFrameEnd(double time); + public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance); [UnmanagedCallersOnly(EntryPoint = "JNI_OnLoad")] @@ -279,6 +285,11 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")] public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj) { + SetSwapBuffersCallback(() => + { + var time = SwitchDevice.EmulationContext.Statistics.GetGameFrameTime(); + onFrameEnd(time); + }); RunLoop(); } diff --git a/src/LibRyujinx/LibRyujinx.Graphics.cs b/src/LibRyujinx/LibRyujinx.Graphics.cs index 23e4f112f..cb0cb3624 100644 --- a/src/LibRyujinx/LibRyujinx.Graphics.cs +++ b/src/LibRyujinx/LibRyujinx.Graphics.cs @@ -165,6 +165,11 @@ namespace LibRyujinx return; } + if (Ryujinx.Common.SystemInfo.SystemInfo.IsBionic) + { + setRenderingThread(); + } + if (device.WaitFifo()) { device.Statistics.RecordFifoStart(); diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index 443990c33..347167e43 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -477,6 +477,9 @@ namespace Ryujinx.Graphics.Vulkan { _gd.SwapchainApi.QueuePresent(_gd.Queue, presentInfo); } + + //While this does nothing in most cases, it's useful to notify the end of the frame. + swapBuffersCallback?.Invoke(); } public override void SetAntiAliasing(AntiAliasing effect) diff --git a/src/RyujinxAndroid/app/src/main/AndroidManifest.xml b/src/RyujinxAndroid/app/src/main/AndroidManifest.xml index 778370752..d11615d69 100644 --- a/src/RyujinxAndroid/app/src/main/AndroidManifest.xml +++ b/src/RyujinxAndroid/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:largeHeap="true" + android:appCategory="game" android:theme="@style/Theme.RyujinxAndroid" tools:targetApi="31"> + +jmethodID _updateFrameTime; +JNIEnv* _rendererEnv = nullptr; + +std::chrono::time_point _currentTimePoint; + +JNIEnv* getEnv(bool isRenderer){ + JNIEnv* env; + if(isRenderer){ + env = _rendererEnv; + } + + if(env != nullptr) + return env; + + auto result = _vm->AttachCurrentThread(&env, NULL); + + return env; +} + +void detachEnv(){ + auto result = _vm->DetachCurrentThread(); +} extern "C" { @@ -128,4 +153,39 @@ jstring createString( } +} +extern "C" +JNIEXPORT jlong JNICALL +Java_org_ryujinx_android_MainActivity_getRenderingThreadId(JNIEnv *env, jobject thiz) { + return _currentRenderingThreadId; +} +extern "C" +void setRenderingThread(){ + auto currentId = pthread_self(); + + _currentRenderingThreadId = currentId; + _renderingThreadId = currentId; + + _currentTimePoint = std::chrono::high_resolution_clock::now(); +} +extern "C" +JNIEXPORT void JNICALL +Java_org_ryujinx_android_MainActivity_initVm(JNIEnv *env, jobject thiz) { + JavaVM* vm = nullptr; + auto success = env->GetJavaVM(&vm); + _vm = vm; + _mainActivity = thiz; + _mainActivityClass = env->GetObjectClass(thiz); +} + +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"); + + auto now = std::chrono::high_resolution_clock::now(); + auto nano = std::chrono::duration_cast(now-_currentTimePoint).count(); + env->CallStaticVoidMethod(cl, _updateFrameTime, + nano); } \ No newline at end of file diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt index 563bd4f28..2c99d5f04 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt @@ -1,13 +1,10 @@ package org.ryujinx.android import android.content.Context -import android.os.ParcelFileDescriptor +import android.os.Build import android.view.MotionEvent import android.view.SurfaceHolder import android.view.SurfaceView -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import org.ryujinx.android.viewmodels.GameModel import org.ryujinx.android.viewmodels.MainViewModel import org.ryujinx.android.viewmodels.QuickSettings @@ -15,6 +12,7 @@ import kotlin.concurrent.thread import kotlin.math.roundToInt class GameHost(context: Context?, val controller: GameController, val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback { + private var _renderingThreadWatcher: Thread? = null private var _height: Int = 0 private var _width: Int = 0 private var _updateThread: Thread? = null @@ -168,7 +166,25 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo } private fun runGame() : Unit{ + // 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) + var newthreadId = mainViewModel.activity.getRenderingThreadId() + + if (threadId != newthreadId) { + mainViewModel.performanceManager?.closeCurrentRenderingSession() + } + threadId = newthreadId; + if (threadId != 0L) { + mainViewModel.performanceManager?.initializeRenderingSession(threadId) + } + } + } + } _nativeRyujinx.graphicsRendererRunLoop() } - } \ No newline at end of file diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt index 8458adf80..fc23c3c7d 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt @@ -33,23 +33,39 @@ import org.ryujinx.android.views.MainView class MainActivity : ComponentActivity() { var physicalControllerManager: PhysicalControllerManager - private var mainViewModel: MainViewModel? = null private var _isInit: Boolean = false var storageHelper: SimpleStorageHelper? = null companion object { + var mainViewModel: MainViewModel? = null var AppPath : String? var StorageHelper: SimpleStorageHelper? = null init { AppPath = "" } + + @JvmStatic + fun updateRenderSessionPerformance(gameTime : Long) + { + if(gameTime <= 0) + return + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + mainViewModel?.performanceManager?.updateRenderingSessionTime(gameTime) + } + } } init { physicalControllerManager = PhysicalControllerManager(this) storageHelper = SimpleStorageHelper(this) StorageHelper = storageHelper + System.loadLibrary("ryujinxjni") + initVm() } + external fun getRenderingThreadId() : Long + external fun initVm() + fun setFullScreen() :Unit { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PerformanceManager.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PerformanceManager.kt new file mode 100644 index 000000000..4568158b5 --- /dev/null +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PerformanceManager.kt @@ -0,0 +1,49 @@ +package org.ryujinx.android + +import android.os.Build +import android.os.PerformanceHintManager +import androidx.annotation.RequiresApi + +class PerformanceManager(val performanceHintManager: PerformanceHintManager) { + private var _isEnabled: Boolean = false + private var renderingSession: PerformanceHintManager.Session? = null + val DEFAULT_TARGET_NS = 16666666L + + @RequiresApi(Build.VERSION_CODES.S) + fun initializeRenderingSession(threadId : Long){ + if(!_isEnabled || renderingSession != null) + return + + var 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() + } + } + + 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) + } + } +} \ No newline at end of file diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt index c8d3664b2..bd7ad9442 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt @@ -1,18 +1,33 @@ 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 org.ryujinx.android.GameHost import org.ryujinx.android.MainActivity +import org.ryujinx.android.PerformanceManager +@SuppressLint("WrongConstant") class MainViewModel(val activity: MainActivity) { + var performanceManager: PerformanceManager? = null var selected: GameModel? = null private var gameTimeState: MutableState? = null private var gameFpsState: MutableState? = null private var fifoState: MutableState? = null private var navController : NavHostController? = null - var homeViewModel: HomeViewModel = HomeViewModel(activity, this,) + var homeViewModel: HomeViewModel = HomeViewModel(activity, this) + + init { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + var hintService = + activity.getSystemService(Context.PERFORMANCE_HINT_SERVICE) as PerformanceHintManager + performanceManager = PerformanceManager(hintService) + } + } fun loadGame(game:GameModel) { var controller = navController?: return;