diff --git a/Assets/GamePerformanceSdk/Plugins.meta b/Assets/GamePerformanceSdk/Plugins.meta
new file mode 100644
index 0000000..cb81a00
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ac3a1b0385b933a4ba752182b9aff19e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GamePerformanceSdk/Plugins/Android.meta b/Assets/GamePerformanceSdk/Plugins/Android.meta
new file mode 100644
index 0000000..618f19c
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins/Android.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f2a5b10838cdc174f87fbc9ad15c12fe
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GamePerformanceSdk/Plugins/Android/AndroidManifest.xml b/Assets/GamePerformanceSdk/Plugins/Android/AndroidManifest.xml
new file mode 100644
index 0000000..f1d62c1
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins/Android/AndroidManifest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Assets/GamePerformanceSdk/Plugins/Android/AndroidManifest.xml.meta b/Assets/GamePerformanceSdk/Plugins/Android/AndroidManifest.xml.meta
new file mode 100644
index 0000000..6abf0e4
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins/Android/AndroidManifest.xml.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8255a3bd1c76aaf469a428a56ef5debf
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GamePerformanceSdk/Plugins/Android/GameActivity.java b/Assets/GamePerformanceSdk/Plugins/Android/GameActivity.java
new file mode 100644
index 0000000..e8a2308
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins/Android/GameActivity.java
@@ -0,0 +1,28 @@
+package com.x.gamesdk;
+
+import com.unity3d.player.UnityPlayerActivity;
+import android.os.Bundle;
+import android.os.Build;
+
+public class GameActivity extends UnityPlayerActivity {
+
+ private String appendCommandLineArgument(String cmdLine, String arg) {
+ if (arg == null || arg.isEmpty())
+ return cmdLine;
+ else if (cmdLine == null || cmdLine.isEmpty())
+ return arg;
+ else
+ return cmdLine + " " + arg;
+ }
+
+ @Override protected String updateUnityCommandLineArguments(String cmdLine)
+ {
+ return cmdLine; // 让 Unity 根据 PlayerSettings 选择图形 API
+ }
+
+ @Override protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ new GameSdk(getBaseContext());
+ }
+}
\ No newline at end of file
diff --git a/Assets/GamePerformanceSdk/Plugins/Android/GameActivity.java.meta b/Assets/GamePerformanceSdk/Plugins/Android/GameActivity.java.meta
new file mode 100644
index 0000000..e209c5a
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins/Android/GameActivity.java.meta
@@ -0,0 +1,32 @@
+fileFormatVersion: 2
+guid: a38f11cc1647a8f44b8dbc93244d95f5
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Android: Android
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GamePerformanceSdk/Plugins/Android/GamePerformanceSdk.cs b/Assets/GamePerformanceSdk/Plugins/Android/GamePerformanceSdk.cs
new file mode 100644
index 0000000..8de72cd
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins/Android/GamePerformanceSdk.cs
@@ -0,0 +1,177 @@
+using System;
+using UnityEngine;
+using UnityEngine.Rendering;
+
+[DefaultExecutionOrder(-9000)]
+public unsafe class GamePerformanceSdk : MonoBehaviour
+{
+ private static AndroidJavaObject s_GameSDK = null;
+ private static IntPtr s_GameSDKRawObjectID;
+ private static IntPtr s_SupportPerformanceHintMethodId;
+ private static IntPtr s_SetPerfHitTargetNanosfHintMethodId;
+ private static IntPtr s_ReportPerfHintMethodId;
+ private static IntPtr s_SupportGameStateMethodId;
+ private static IntPtr s_SetGameStateMethodId;
+
+ private static jvalue[] s_NoArgs = new jvalue[0];
+
+ private jvalue[] ReportPerfHintMethodArgs = new jvalue[1];
+ private jvalue[] SetGameStateMethodArgs = new jvalue[2];
+
+ private bool supportGameStateApi = true;
+ private bool supportPerformanceHintApi = true;
+
+ private static IntPtr GetJavaMethodID(IntPtr classId, string name, string sig)
+ {
+ AndroidJNI.ExceptionClear();
+ var mid = AndroidJNI.GetMethodID(classId, name, sig);
+
+ IntPtr ex = AndroidJNI.ExceptionOccurred();
+ if (ex != (IntPtr)0)
+ {
+ AndroidJNI.ExceptionDescribe();
+ AndroidJNI.ExceptionClear();
+ return (IntPtr)0;
+ }
+ else
+ {
+ return mid;
+ }
+ }
+
+ public enum GameState
+ {
+ MODE_UNKNOWN = 0,
+ /**
+ * No mode means that the game is not in active play, for example the user is using the game
+ * menu.
+ */
+ MODE_NONE = 1,
+
+ /**
+ * Indicates if the game is in active, but interruptible, game play.
+ */
+ MODE_GAMEPLAY_INTERRUPTIBLE = 2,
+
+ /**
+ * Indicates if the game is in active user play mode, which is real time and cannot be
+ * interrupted.
+ */
+ MODE_GAMEPLAY_UNINTERRUPTIBLE = 3,
+
+ /**
+ * Indicates that the current content shown is not gameplay related. For example it can be an
+ * ad, a web page, a text, or a video.
+ */
+ MODE_CONTENT = 4
+ }
+
+ private void Awake()
+ {
+ Application.targetFrameRate = 120;
+
+ s_GameSDK = new AndroidJavaObject("com.x.gamesdk.GameSdk").CallStatic("getInstance");
+ if (s_GameSDK != null)
+ {
+ Debug.Log("Init GamePerformanceSdk start.");
+ s_GameSDKRawObjectID = s_GameSDK.GetRawObject();
+
+ var classID = s_GameSDK.GetRawClass();
+ s_SupportPerformanceHintMethodId = GetJavaMethodID(classID, "SupportPerformanceHint", "()Z");
+ if (s_SupportPerformanceHintMethodId != IntPtr.Zero)
+ {
+ supportPerformanceHintApi = AndroidJNI.CallBooleanMethod(s_GameSDKRawObjectID, s_SupportPerformanceHintMethodId, s_NoArgs);
+ }
+
+ s_ReportPerfHintMethodId = GetJavaMethodID(classID, "ReportPerfHint", "(J)V");
+ if (s_ReportPerfHintMethodId != IntPtr.Zero)
+ {
+ Debug.Log("found ReportPerfHint.");
+ }
+
+ s_SetPerfHitTargetNanosfHintMethodId = GetJavaMethodID(classID, "SetPerfHitTargetNanos", "(J)V");
+ if (s_SetPerfHitTargetNanosfHintMethodId != IntPtr.Zero)
+ {
+ Debug.Log("found SetPerfHitTargetNanos.");
+ SetTargetFps(120);
+ }
+
+ s_SupportGameStateMethodId = GetJavaMethodID(classID, "SupportGameState", "()Z");
+ if (s_SupportGameStateMethodId != IntPtr.Zero)
+ {
+ supportGameStateApi = AndroidJNI.CallBooleanMethod(s_GameSDKRawObjectID, s_SupportGameStateMethodId, s_NoArgs);
+ }
+
+ s_SetGameStateMethodId = GetJavaMethodID(classID, "SetGameState", "(ZI)V");
+ if (s_SetGameStateMethodId != IntPtr.Zero)
+ {
+ Debug.Log("found SetGameState.");
+ }
+
+ Debug.Log($"Init GamePerformanceSdk done. supportGameStateApi:{supportGameStateApi} supportPerformanceHintApi:{supportPerformanceHintApi}");
+ }
+ }
+
+ private void OnEnable()
+ {
+ RenderPipelineManager.endFrameRendering += OnEndFrameRendering;
+ }
+
+ float time = 0;
+ System.Diagnostics.Stopwatch stopwatch = new();
+
+ // XXX: insert to early update
+ private void Update()
+ {
+ time = Time.realtimeSinceStartup;
+ stopwatch.Restart();
+ }
+
+ private void OnEndFrameRendering(ScriptableRenderContext context, Camera[] arg2)
+ {
+ var dt = Time.realtimeSinceStartup - time;
+ stopwatch.Stop();
+ if (s_ReportPerfHintMethodId != IntPtr.Zero)
+ {
+ ReportPerfHintMethodArgs[0] = new jvalue() { j = stopwatch.ElapsedMilliseconds * 1000 * 1000 };
+ AndroidJNI.CallVoidMethod(s_GameSDKRawObjectID, s_ReportPerfHintMethodId, ReportPerfHintMethodArgs);
+ }
+ }
+
+ private void OnDisable()
+ {
+ RenderPipelineManager.endFrameRendering -= OnEndFrameRendering;
+ }
+
+ public void SetTargetFps(int fps)
+ {
+ ReportPerfHintMethodArgs[0] = new jvalue() { j = (long)(1000f / fps * 1000 * 1000) };
+ AndroidJNI.CallVoidMethod(s_GameSDKRawObjectID, s_SetPerfHitTargetNanosfHintMethodId, ReportPerfHintMethodArgs);
+ }
+
+ public void SetGameState(bool isLoading, GameState gameState)
+ {
+ SetGameStateMethodArgs[0] = new jvalue() { z = isLoading };
+ SetGameStateMethodArgs[1] = new jvalue() { i = (int)gameState };
+
+ AndroidJNI.CallVoidMethod(s_GameSDKRawObjectID, s_SetGameStateMethodId, SetGameStateMethodArgs);
+ }
+
+ private void OnGUI()
+ {
+ if (GUI.Button(new Rect(100, 100, 150, 50), "SetLoding"))
+ {
+ SetGameState(true, GameState.MODE_GAMEPLAY_INTERRUPTIBLE);
+ }
+
+ if (GUI.Button(new Rect(100, 180, 150, 50), "SetUninterruptible"))
+ {
+ SetGameState(false, GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE);
+ }
+
+ if (GUI.Button(new Rect(100, 260, 150, 50), "SetUnknown"))
+ {
+ SetGameState(false, GameState.MODE_UNKNOWN);
+ }
+ }
+}
diff --git a/Assets/GamePerformanceSdk/Plugins/Android/GamePerformanceSdk.cs.meta b/Assets/GamePerformanceSdk/Plugins/Android/GamePerformanceSdk.cs.meta
new file mode 100644
index 0000000..1eb6d3d
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins/Android/GamePerformanceSdk.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fb094df10d730994d95a79fd42061d77
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GamePerformanceSdk/Plugins/Android/GameSdk.java b/Assets/GamePerformanceSdk/Plugins/Android/GameSdk.java
new file mode 100644
index 0000000..41fc044
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins/Android/GameSdk.java
@@ -0,0 +1,60 @@
+package com.x.gamesdk;
+
+import android.app.*;
+import android.content.Context;
+import android.os.Build;
+import android.os.PerformanceHintManager;
+import android.os.Process;
+import android.util.Log;
+
+public class GameSdk {
+ private static final String LOG_TAG = "GamePerformacneSDK";
+ private static GameSdk instane;
+
+ private final Context context;
+ private PerformanceHintManager.Session performanceHintSession;
+
+ public static GameSdk getInstance() {
+ return instane;
+ }
+
+ public GameSdk(Context context) {
+ instane = this;
+ this.context = context;
+ Log.d(LOG_TAG, "new Game Performance sdk.SupportGameState:" + SupportGameState() + "|Build.VERSION.SDK_INT:" + Build.VERSION.SDK_INT);
+
+ try {
+ PerformanceHintManager performanceHintManager = context.getSystemService(PerformanceHintManager.class);
+ performanceHintSession = performanceHintManager.createHintSession(new int[]{Process.myTid()}, 16666666);
+ Log.d(LOG_TAG, "create performance Hint Session." + performanceHintSession + "|tid:" + Process.myTid());
+ } catch (Exception e) {
+ Log.d(LOG_TAG, "create performance Hint err:" + e.getMessage());
+ }
+ }
+
+ public boolean SupportGameState() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
+ }
+
+ public boolean SupportPerformanceHint() {
+ return performanceHintSession != null;
+ }
+
+ public void SetGameState(boolean isLoading, int gameState) {
+ try {
+ GameManager gameManager = context.getSystemService(GameManager.class);
+ if (SupportGameState()) {
+ gameManager.setGameState(new GameState(isLoading, gameState));
+ Log.d(LOG_TAG, "set game state.");
+ }
+ } catch (Exception ignored) {
+ }
+ }
+
+ public void SetPerfHitTargetNanos( long targetNs) {
+ performanceHintSession.updateTargetWorkDuration(targetNs);
+ }
+ public void ReportPerfHint(long durantionNs) {
+ performanceHintSession.reportActualWorkDuration(durantionNs);
+ }
+}
diff --git a/Assets/GamePerformanceSdk/Plugins/Android/GameSdk.java.meta b/Assets/GamePerformanceSdk/Plugins/Android/GameSdk.java.meta
new file mode 100644
index 0000000..fc27c5f
--- /dev/null
+++ b/Assets/GamePerformanceSdk/Plugins/Android/GameSdk.java.meta
@@ -0,0 +1,32 @@
+fileFormatVersion: 2
+guid: 5b395be1482ba044c827eb603f632eca
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Android: Android
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ userData:
+ assetBundleName:
+ assetBundleVariant: