From ff3099e4a191365265799bdb8416ce28c2469359 Mon Sep 17 00:00:00 2001
From: Emmanuel Hansen <emmausssss@gmail.com>
Date: Thu, 20 Jul 2023 13:35:07 +0000
Subject: [PATCH] add performance hints

---
 src/LibRyujinx/Android/JniExportedMethods.cs  | 11 ++++
 src/LibRyujinx/LibRyujinx.Graphics.cs         |  5 ++
 src/Ryujinx.Graphics.Vulkan/Window.cs         |  3 +
 .../app/src/main/AndroidManifest.xml          |  1 +
 src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h |  6 ++
 .../app/src/main/cpp/ryujinx.cpp              | 60 +++++++++++++++++++
 .../main/java/org/ryujinx/android/GameHost.kt | 26 ++++++--
 .../java/org/ryujinx/android/MainActivity.kt  | 18 +++++-
 .../org/ryujinx/android/PerformanceManager.kt | 49 +++++++++++++++
 .../android/viewmodels/MainViewModel.kt       | 17 +++++-
 10 files changed, 189 insertions(+), 7 deletions(-)
 create mode 100644 src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PerformanceManager.kt

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">
         <activity
diff --git a/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h b/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h
index 2077c6fef..8770e3e87 100644
--- a/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h
+++ b/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h
@@ -36,4 +36,10 @@ void* _ryujinxNative = NULL;
 // Ryujinx imported functions
 bool (*initialize)(char*) = NULL;
 
+long _renderingThreadId = 0;
+long _currentRenderingThreadId = 0;
+JavaVM* _vm = nullptr;
+jobject _mainActivity = nullptr;
+jclass _mainActivityClass = nullptr;
+
 #endif //RYUJINXNATIVE_RYUIJNX_H
diff --git a/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp
index e5db677e0..ad7e4b0b9 100644
--- a/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp
+++ b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp
@@ -17,6 +17,31 @@
 //    }
 
 #include "ryuijnx.h"
+#include "pthread.h"
+#include <chrono>
+
+jmethodID _updateFrameTime;
+JNIEnv* _rendererEnv = nullptr;
+
+std::chrono::time_point<std::chrono::steady_clock, std::chrono::nanoseconds> _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<std::chrono::nanoseconds>(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<Double>? = null
     private var gameFpsState: MutableState<Double>? = null
     private var fifoState: MutableState<Double>? = 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;