No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SamsungGameSDKAdaptivePerformanceSubsystem.cs 49KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424
  1. #if UNITY_ANDROID
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Threading;
  6. using static System.Threading.Thread;
  7. using UnityEngine.Scripting;
  8. using UnityEngine.AdaptivePerformance.Provider;
  9. [assembly: AlwaysLinkAssembly]
  10. namespace UnityEngine.AdaptivePerformance.Samsung.Android
  11. {
  12. internal static class GameSDKLog
  13. {
  14. static SamsungAndroidProviderSettings settings = SamsungAndroidProviderSettings.GetSettings();
  15. [Conditional("DEVELOPMENT_BUILD")]
  16. public static void Debug(string format, params object[] args)
  17. {
  18. if (settings != null && settings.samsungProviderLogging)
  19. UnityEngine.Debug.Log(System.String.Format("[Samsung GameSDK] " + format, args));
  20. }
  21. }
  22. internal class AsyncUpdater : IDisposable
  23. {
  24. private Thread m_Thread;
  25. private bool m_Disposed = false;
  26. private bool m_Quit = false;
  27. private List<Action> m_UpdateAction = new List<Action>();
  28. private int[] m_UpdateRequests = null;
  29. private bool[] m_RequestComplete = null;
  30. private int m_UpdateRequestReadIndex = 0;
  31. private int m_UpdateRequestWriteIndex = 0;
  32. private object m_Mutex = new object();
  33. private Semaphore m_Semaphore = null;
  34. public int Register(Action action)
  35. {
  36. if (m_Thread.IsAlive)
  37. return -1;
  38. int handle = m_UpdateAction.Count;
  39. m_UpdateAction.Add(action);
  40. return handle;
  41. }
  42. public void Start()
  43. {
  44. int maxRequests = m_UpdateAction.Count;
  45. if (maxRequests <= 0)
  46. return;
  47. m_Semaphore = new Semaphore(0, maxRequests);
  48. m_UpdateRequests = new int[maxRequests];
  49. m_RequestComplete = new bool[maxRequests];
  50. m_Thread.Start();
  51. }
  52. public bool RequestUpdate(int handle)
  53. {
  54. lock (m_Mutex)
  55. {
  56. int newWriteIndex = (m_UpdateRequestWriteIndex + 1) % m_UpdateRequests.Length;
  57. if (newWriteIndex == m_UpdateRequestReadIndex)
  58. {
  59. return false;
  60. }
  61. m_UpdateRequests[m_UpdateRequestWriteIndex] = handle;
  62. m_RequestComplete[handle] = false;
  63. m_UpdateRequestWriteIndex = newWriteIndex;
  64. }
  65. m_Semaphore.Release();
  66. return true;
  67. }
  68. public bool IsRequestComplete(int handle)
  69. {
  70. lock (m_Mutex)
  71. {
  72. return m_RequestComplete[handle];
  73. }
  74. }
  75. public AsyncUpdater()
  76. {
  77. m_Thread = new Thread(new ThreadStart(ThreadProc));
  78. m_Thread.Name = "SamsungGameSDK";
  79. }
  80. private void ThreadProc()
  81. {
  82. AndroidJNI.AttachCurrentThread();
  83. while (true)
  84. {
  85. try
  86. {
  87. m_Semaphore.WaitOne();
  88. }
  89. catch (Exception)
  90. {
  91. break;
  92. }
  93. int handle = -1;
  94. lock (m_Mutex)
  95. {
  96. if (m_Quit)
  97. break;
  98. if (m_UpdateRequestReadIndex != m_UpdateRequestWriteIndex)
  99. {
  100. handle = m_UpdateRequests[m_UpdateRequestReadIndex];
  101. m_UpdateRequestReadIndex = (m_UpdateRequestReadIndex + 1) % m_UpdateRequests.Length;
  102. }
  103. }
  104. if (handle >= 0)
  105. {
  106. try
  107. {
  108. m_UpdateAction[handle].Invoke();
  109. }
  110. catch (Exception)
  111. {
  112. }
  113. lock (m_Mutex)
  114. {
  115. m_RequestComplete[handle] = true;
  116. }
  117. }
  118. }
  119. AndroidJNI.DetachCurrentThread();
  120. }
  121. private void Dispose(bool disposing)
  122. {
  123. if (m_Disposed)
  124. return;
  125. if (disposing)
  126. {
  127. if (m_Thread.IsAlive)
  128. {
  129. lock (m_Mutex)
  130. {
  131. m_Quit = true;
  132. }
  133. m_Semaphore.Release();
  134. m_Thread.Join();
  135. }
  136. }
  137. m_Disposed = true;
  138. }
  139. public void Dispose()
  140. {
  141. Dispose(true);
  142. GC.SuppressFinalize(this);
  143. }
  144. }
  145. internal class AsyncValue<T>
  146. {
  147. private AsyncUpdater updater = null;
  148. private int updateHandle = -1;
  149. private bool pendingUpdate = false;
  150. private Func<T> updateFunc = null;
  151. private T newValue;
  152. private float updateTimeDeltaSeconds;
  153. private float updateTimestamp;
  154. public AsyncValue(AsyncUpdater updater, T value, float updateTimeDeltaSeconds, Func<T> updateFunc)
  155. {
  156. this.updater = updater;
  157. this.updateTimeDeltaSeconds = updateTimeDeltaSeconds;
  158. this.updateFunc = updateFunc;
  159. this.value = value;
  160. this.updateHandle = updater.Register(() => newValue = updateFunc());
  161. }
  162. public bool Update(float timestamp)
  163. {
  164. bool changed = false;
  165. if (pendingUpdate && updater.IsRequestComplete(updateHandle))
  166. {
  167. changed = !value.Equals(newValue);
  168. if (changed)
  169. changeTimestamp = timestamp;
  170. value = newValue;
  171. updateTimestamp = timestamp;
  172. pendingUpdate = false;
  173. }
  174. if (!pendingUpdate)
  175. {
  176. if (timestamp - updateTimestamp > updateTimeDeltaSeconds)
  177. {
  178. pendingUpdate = updater.RequestUpdate(updateHandle);
  179. }
  180. }
  181. return changed;
  182. }
  183. public void SyncUpdate(float timestamp)
  184. {
  185. var oldValue = value;
  186. updateTimestamp = timestamp;
  187. value = updateFunc();
  188. if (!value.Equals(oldValue))
  189. changeTimestamp = timestamp;
  190. }
  191. public T value { get; private set; }
  192. public float changeTimestamp { get; private set; }
  193. }
  194. [Preserve]
  195. public class SamsungGameSDKAdaptivePerformanceSubsystem : AdaptivePerformanceSubsystem, IApplicationLifecycle, IDevicePerformanceLevelControl
  196. {
  197. private NativeApi m_Api = null;
  198. private AsyncUpdater m_AsyncUpdater;
  199. private PerformanceDataRecord m_Data = new PerformanceDataRecord();
  200. private object m_DataLock = new object();
  201. private AsyncValue<double> m_SkinTemp = null;
  202. private AsyncValue<double> m_GPUTime = null;
  203. private Version m_Version = null;
  204. private float m_MinTempLevel = 0.0f;
  205. private float m_MaxTempLevel = 10.0f;
  206. bool m_PerformanceLevelControlSystemChange = false;
  207. bool m_AllowPerformanceLevelControlChanges = true;
  208. private AutoVariableRefreshRate m_AutoVariableRefreshRate;
  209. public override IApplicationLifecycle ApplicationLifecycle { get { return this; } }
  210. public override IDevicePerformanceLevelControl PerformanceLevelControl { get { return this; } }
  211. public int MaxCpuPerformanceLevel { get; set; }
  212. public int MaxGpuPerformanceLevel { get; set; }
  213. static SamsungAndroidProviderSettings settings = SamsungAndroidProviderSettings.GetSettings();
  214. /// <summary>
  215. /// InvalidOperation is the return value of an SDK API call when the feature is not available.
  216. /// </summary>
  217. /// <value>-999</value>
  218. const int k_InvalidOperation = -999;
  219. public SamsungGameSDKAdaptivePerformanceSubsystem()
  220. {
  221. MaxCpuPerformanceLevel = 3;
  222. MaxGpuPerformanceLevel = 3;
  223. m_Api = new NativeApi(OnPerformanceWarning, OnPerformanceLevelTimeout, () => (VariableRefreshRate.Instance as VRRManager)?.OnRefreshRateChanged(), OnCpuPerformanceBoostModeTimeout, OnGpuPerformanceBoostModeTimeout);
  224. m_AsyncUpdater = new AsyncUpdater();
  225. m_SkinTemp = new AsyncValue<double>(m_AsyncUpdater, -1.0, 2.7f, () => GetHighPrecisionSkinTempLevel());
  226. m_GPUTime = new AsyncValue<double>(m_AsyncUpdater, -1.0, 0.0f, () => m_Api.GetGpuFrameTime());
  227. Capabilities = Feature.CpuPerformanceLevel | Feature.GpuPerformanceLevel | Feature.PerformanceLevelControl | Feature.TemperatureLevel | Feature.WarningLevel | Feature.GpuFrameTime;
  228. m_AsyncUpdater.Start();
  229. }
  230. private void OnPerformanceWarning(WarningLevel warningLevel)
  231. {
  232. lock (m_DataLock)
  233. {
  234. m_Data.ChangeFlags |= Feature.WarningLevel;
  235. m_Data.ChangeFlags |= Feature.PerformanceLevelControl;
  236. m_Data.WarningLevel = warningLevel;
  237. }
  238. }
  239. private void OnPerformanceLevelTimeout()
  240. {
  241. lock (m_DataLock)
  242. {
  243. m_Data.ChangeFlags |= Feature.CpuPerformanceLevel;
  244. m_Data.ChangeFlags |= Feature.GpuPerformanceLevel;
  245. m_Data.CpuPerformanceLevel = Constants.UnknownPerformanceLevel;
  246. m_Data.GpuPerformanceLevel = Constants.UnknownPerformanceLevel;
  247. }
  248. }
  249. private void OnCpuPerformanceBoostModeTimeout()
  250. {
  251. lock (m_DataLock)
  252. {
  253. m_Data.ChangeFlags |= Feature.CpuPerformanceBoost;
  254. m_Data.CpuPerformanceBoost = false;
  255. }
  256. }
  257. private void OnGpuPerformanceBoostModeTimeout()
  258. {
  259. lock (m_DataLock)
  260. {
  261. m_Data.ChangeFlags |= Feature.GpuPerformanceBoost;
  262. m_Data.GpuPerformanceBoost = false;
  263. }
  264. }
  265. private float GetHighPrecisionSkinTempLevel()
  266. {
  267. return (float)m_Api.GetHighPrecisionSkinTempLevel();
  268. }
  269. private int GetClusterInfo()
  270. {
  271. return m_Api.GetClusterInfo();
  272. }
  273. private void ImmediateUpdateTemperature()
  274. {
  275. var timestamp = Time.time;
  276. m_SkinTemp.SyncUpdate(timestamp);
  277. lock (m_DataLock)
  278. {
  279. m_Data.ChangeFlags |= Feature.TemperatureLevel;
  280. m_Data.TemperatureLevel = GetTemperatureLevel();
  281. }
  282. }
  283. private static bool TryParseVersion(string versionString, out Version version)
  284. {
  285. try
  286. {
  287. version = new Version(versionString);
  288. }
  289. catch (Exception)
  290. {
  291. version = null;
  292. return false;
  293. }
  294. return true;
  295. }
  296. internal bool Initialize()
  297. {
  298. if (initialized)
  299. {
  300. return true;
  301. }
  302. if (!m_Api.Initialize())
  303. {
  304. return false;
  305. }
  306. if (TryParseVersion(m_Api.GetVersion(), out m_Version))
  307. {
  308. if (m_Version >= new Version(3, 5))
  309. {
  310. initialized = true;
  311. MaxCpuPerformanceLevel = m_Api.GetMaxCpuPerformanceLevel();
  312. MaxGpuPerformanceLevel = m_Api.GetMaxGpuPerformanceLevel();
  313. Capabilities |= Feature.CpuPerformanceBoost | Feature.GpuPerformanceBoost;
  314. }
  315. else if (m_Version >= new Version(3, 4))
  316. {
  317. initialized = true;
  318. MaxCpuPerformanceLevel = m_Api.GetMaxCpuPerformanceLevel();
  319. MaxGpuPerformanceLevel = m_Api.GetMaxGpuPerformanceLevel();
  320. }
  321. else if (m_Version >= new Version(3, 2))
  322. {
  323. initialized = true;
  324. MaxCpuPerformanceLevel = m_Api.GetMaxCpuPerformanceLevel();
  325. MaxGpuPerformanceLevel = m_Api.GetMaxGpuPerformanceLevel();
  326. }
  327. else
  328. {
  329. m_Api.Terminate();
  330. initialized = false;
  331. }
  332. }
  333. if (MaxCpuPerformanceLevel == k_InvalidOperation)
  334. {
  335. MaxCpuPerformanceLevel = Constants.UnknownPerformanceLevel;
  336. Capabilities &= ~Feature.CpuPerformanceLevel;
  337. m_AllowPerformanceLevelControlChanges = false;
  338. }
  339. if (MaxGpuPerformanceLevel == k_InvalidOperation)
  340. {
  341. MaxGpuPerformanceLevel = Constants.UnknownPerformanceLevel;
  342. Capabilities &= ~Feature.GpuPerformanceLevel;
  343. m_AllowPerformanceLevelControlChanges = false;
  344. }
  345. m_Data.PerformanceLevelControlAvailable = m_AllowPerformanceLevelControlChanges;
  346. return initialized;
  347. }
  348. public override void Start()
  349. {
  350. if (!initialized)
  351. {
  352. return;
  353. }
  354. ImmediateUpdateTemperature();
  355. Thread t = new Thread(CheckInitialTemperatureAndSendWarnings);
  356. t.Start();
  357. CheckAndInitializeVRR();
  358. }
  359. void CheckAndInitializeVRR()
  360. {
  361. if (m_Api.IsVariableRefreshRateSupported())
  362. {
  363. if (VariableRefreshRate.Instance == null)
  364. {
  365. VariableRefreshRate.Instance = new VRRManager(m_Api);
  366. m_AutoVariableRefreshRate = new AutoVariableRefreshRate(VariableRefreshRate.Instance);
  367. }
  368. }
  369. else
  370. {
  371. VariableRefreshRate.Instance = null;
  372. m_AutoVariableRefreshRate = null;
  373. }
  374. }
  375. void CheckInitialTemperatureAndSendWarnings()
  376. {
  377. // If the device is already warm upon startup and past the throttling imminent warning level
  378. // the warning callback is not called as it's not available yet. We need to set it manually based on temperature as workaround.
  379. // On startup the temperature reading is always 0. After a couple of seconds a true value is returned. Therefore we wait for 2 seconds before we make the reading.
  380. Sleep(TimeSpan.FromSeconds(2));
  381. float currentTempLevel = GetHighPrecisionSkinTempLevel();
  382. if (m_Version >= new Version(3, 2))
  383. {
  384. if (currentTempLevel >= 7)
  385. OnPerformanceWarning(WarningLevel.Throttling);
  386. else if (currentTempLevel >= 5)
  387. OnPerformanceWarning(WarningLevel.ThrottlingImminent);
  388. }
  389. if (m_Version >= new Version(3, 5))
  390. {
  391. // Cluster info is not available in the same frame as game sdk init so we need to wait a bit.
  392. int clusterInfo = m_Api.GetClusterInfo();
  393. if (clusterInfo != -999)
  394. {
  395. var aClusterInfo = new ClusterInfo();
  396. aClusterInfo.BigCore = clusterInfo / 100;
  397. aClusterInfo.MediumCore = (clusterInfo % 100) / 10;
  398. aClusterInfo.LittleCore = (clusterInfo % 100) % 10;
  399. lock (m_DataLock)
  400. {
  401. m_Data.ClusterInfo = aClusterInfo;
  402. m_Data.ChangeFlags |= Feature.ClusterInfo;
  403. }
  404. Capabilities |= Feature.ClusterInfo;
  405. }
  406. }
  407. }
  408. public override void Stop()
  409. {
  410. }
  411. protected override void OnDestroy()
  412. {
  413. VariableRefreshRate.Instance = null;
  414. m_AutoVariableRefreshRate = null;
  415. if (initialized)
  416. {
  417. m_Api.Terminate();
  418. initialized = false;
  419. }
  420. m_AsyncUpdater.Dispose();
  421. }
  422. public override string Stats => $"SkinTemp={m_SkinTemp?.value ?? -1} GPUTime={m_GPUTime?.value ?? -1}";
  423. public override PerformanceDataRecord Update()
  424. {
  425. // GameSDK API is very slow (~4ms per call), so update those numbers once per frame from another thread
  426. float timeSinceStartup = Time.time;
  427. m_GPUTime.Update(timeSinceStartup);
  428. bool tempChanged = m_SkinTemp.Update(timeSinceStartup);
  429. (VariableRefreshRate.Instance as VRRManager)?.Update();
  430. if ((VariableRefreshRate.Instance as VRRManager) != null && settings.automaticVRR)
  431. if (QualitySettings.vSyncCount == 0)
  432. m_AutoVariableRefreshRate.UpdateAutoVRR();
  433. if (m_PerformanceLevelControlSystemChange)
  434. {
  435. var temperatureLevel = (float)m_SkinTemp.value;
  436. if (temperatureLevel < 5)
  437. {
  438. lock (m_DataLock)
  439. {
  440. DisableSystemControl();
  441. }
  442. }
  443. }
  444. lock (m_DataLock)
  445. {
  446. if (tempChanged)
  447. {
  448. m_Data.ChangeFlags |= Feature.TemperatureLevel;
  449. m_Data.TemperatureLevel = GetTemperatureLevel();
  450. }
  451. m_Data.GpuFrameTime = LatestGpuFrameTime();
  452. m_Data.ChangeFlags |= Feature.GpuFrameTime;
  453. PerformanceDataRecord result = m_Data;
  454. m_Data.ChangeFlags = Feature.None;
  455. return result;
  456. }
  457. }
  458. public override Version Version
  459. {
  460. get
  461. {
  462. return m_Version;
  463. }
  464. }
  465. private static float NormalizeTemperatureLevel(float currentTempLevel, float minValue, float maxValue)
  466. {
  467. float tempLevel = -1.0f;
  468. if (currentTempLevel >= minValue && currentTempLevel <= maxValue)
  469. {
  470. tempLevel = currentTempLevel / maxValue;
  471. tempLevel = Math.Min(Math.Max(tempLevel, Constants.MinTemperatureLevel), maxValue);
  472. }
  473. return tempLevel;
  474. }
  475. private float NormalizeTemperatureLevel(float currentTempLevel)
  476. {
  477. return NormalizeTemperatureLevel(currentTempLevel, m_MinTempLevel, m_MaxTempLevel);
  478. }
  479. private float GetTemperatureLevel()
  480. {
  481. return NormalizeTemperatureLevel((float)m_SkinTemp.value);
  482. }
  483. private float LatestGpuFrameTime()
  484. {
  485. return (float)(m_GPUTime.value / 1000.0);
  486. }
  487. public bool SetPerformanceLevel(ref int cpuLevel, ref int gpuLevel)
  488. {
  489. if ((Capabilities & Feature.CpuPerformanceLevel) != Feature.CpuPerformanceLevel ||
  490. (Capabilities & Feature.GpuPerformanceLevel) != Feature.GpuPerformanceLevel)
  491. return false;
  492. if (cpuLevel < 0)
  493. cpuLevel = 0;
  494. else if (cpuLevel > MaxCpuPerformanceLevel)
  495. cpuLevel = MaxCpuPerformanceLevel;
  496. if (gpuLevel < 0)
  497. gpuLevel = 0;
  498. else if (gpuLevel > MaxGpuPerformanceLevel)
  499. gpuLevel = MaxGpuPerformanceLevel;
  500. if (m_Version == new Version(3, 2) && cpuLevel == 0)
  501. cpuLevel = 1;
  502. bool success = false;
  503. int result = m_Api.SetFreqLevels(cpuLevel, gpuLevel);
  504. success = result == 1;
  505. lock (m_DataLock)
  506. {
  507. var oldCpuLevel = m_Data.CpuPerformanceLevel;
  508. var oldGpuLevel = m_Data.GpuPerformanceLevel;
  509. m_Data.CpuPerformanceLevel = success ? cpuLevel : Constants.UnknownPerformanceLevel;
  510. m_Data.GpuPerformanceLevel = success ? gpuLevel : Constants.UnknownPerformanceLevel;
  511. if (success)
  512. {
  513. if (m_Data.CpuPerformanceLevel != oldCpuLevel)
  514. m_Data.ChangeFlags |= Feature.CpuPerformanceLevel;
  515. if (m_Data.GpuPerformanceLevel != oldGpuLevel)
  516. m_Data.ChangeFlags |= Feature.GpuPerformanceLevel;
  517. }
  518. if (result > 1)
  519. {
  520. if (result == 2)
  521. {
  522. GameSDKLog.Debug($"Thermal Mitigation Logic is working and CPU({cpuLevel})/GPU({gpuLevel}) level change request was not approved.");
  523. }
  524. else if (result == 3)
  525. {
  526. GameSDKLog.Debug($"CPU or GPU Boost mode is active and CPU({cpuLevel})/GPU({gpuLevel}) level change request was not approved.");
  527. }
  528. EnableSystemControl();
  529. }
  530. }
  531. return success;
  532. }
  533. public bool EnableCpuBoost()
  534. {
  535. var result = m_Api.EnableCpuBoost();
  536. lock (m_DataLock)
  537. {
  538. var oldPerformanceBoost = m_Data.CpuPerformanceBoost;
  539. m_Data.CpuPerformanceBoost = result;
  540. if (m_Data.CpuPerformanceBoost != oldPerformanceBoost)
  541. m_Data.ChangeFlags |= Feature.CpuPerformanceBoost;
  542. if (result)
  543. {
  544. EnableSystemControl();
  545. }
  546. }
  547. return result;
  548. }
  549. public bool EnableGpuBoost()
  550. {
  551. var result = m_Api.EnableGpuBoost();
  552. lock (m_DataLock)
  553. {
  554. var oldPerformanceBoost = m_Data.GpuPerformanceBoost;
  555. m_Data.GpuPerformanceBoost = result;
  556. if (m_Data.GpuPerformanceBoost != oldPerformanceBoost)
  557. m_Data.ChangeFlags |= Feature.GpuPerformanceBoost;
  558. if (result)
  559. {
  560. EnableSystemControl();
  561. }
  562. }
  563. return result;
  564. }
  565. public void ApplicationPause() {}
  566. public void ApplicationResume()
  567. {
  568. //We need to re-initialize because some Android onForegroundchange() APIs do not detect the change (e.g. bixby)
  569. if (!m_Api.Initialize())
  570. GameSDKLog.Debug("Resume: reinitialization failed!");
  571. if ((Capabilities & Feature.CpuPerformanceLevel) == Feature.CpuPerformanceLevel)
  572. {
  573. lock (m_DataLock)
  574. {
  575. m_Data.CpuPerformanceLevel = Constants.UnknownPerformanceLevel;
  576. m_Data.ChangeFlags |= Feature.CpuPerformanceLevel;
  577. }
  578. }
  579. if ((Capabilities & Feature.GpuPerformanceLevel) == Feature.GpuPerformanceLevel)
  580. {
  581. lock (m_DataLock)
  582. {
  583. m_Data.GpuPerformanceLevel = Constants.UnknownPerformanceLevel;
  584. m_Data.ChangeFlags |= Feature.GpuPerformanceLevel;
  585. }
  586. }
  587. ImmediateUpdateTemperature();
  588. CheckAndInitializeVRR();
  589. (VariableRefreshRate.Instance as VRRManager)?.Resume();
  590. }
  591. void EnableSystemControl()
  592. {
  593. if (!m_AllowPerformanceLevelControlChanges)
  594. return;
  595. m_Data.PerformanceLevelControlAvailable = false;
  596. m_Data.ChangeFlags |= Feature.PerformanceLevelControl;
  597. m_PerformanceLevelControlSystemChange = true;
  598. }
  599. void DisableSystemControl()
  600. {
  601. if (!m_AllowPerformanceLevelControlChanges)
  602. return;
  603. m_Data.PerformanceLevelControlAvailable = true;
  604. m_Data.ChangeFlags |= Feature.PerformanceLevelControl;
  605. m_PerformanceLevelControlSystemChange = false;
  606. }
  607. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
  608. static void RegisterDescriptor()
  609. {
  610. if (!SystemInfo.deviceModel.StartsWith("samsung", StringComparison.OrdinalIgnoreCase))
  611. {
  612. GameSDKLog.Debug($"The device {SystemInfo.deviceModel} is not a supported Samsung phone. This provider will not run. Aborting registering the Adaptive Performance provider descriptor.");
  613. return;
  614. }
  615. if (!NativeApi.IsAvailable())
  616. {
  617. GameSDKLog.Debug($"The native API for this provider is not available. Aborting registering the Adaptive Performance provider descriptor.");
  618. return;
  619. }
  620. AdaptivePerformanceSubsystemDescriptor.RegisterDescriptor(new AdaptivePerformanceSubsystemDescriptor.Cinfo
  621. {
  622. id = "SamsungGameSDK",
  623. subsystemImplementationType = typeof(SamsungGameSDKAdaptivePerformanceSubsystem)
  624. });
  625. }
  626. internal class NativeApi : AndroidJavaProxy
  627. {
  628. static private AndroidJavaObject s_GameSDK = null;
  629. static private IntPtr s_GameSDKRawObjectID;
  630. static private IntPtr s_GetGpuFrameTimeID;
  631. static private IntPtr s_GetHighPrecisionSkinTempLevelID;
  632. static private IntPtr s_GetClusterInfolID;
  633. static private bool s_isAvailable = false;
  634. static private jvalue[] s_NoArgs = new jvalue[0];
  635. private Action<WarningLevel> PerformanceWarningEvent;
  636. private Action PerformanceLevelTimeoutEvent;
  637. private Action CpuPerformanceBoostReleasedByTimeoutEvent;
  638. private Action GpuPerformanceBoostReleasedByTimeoutEvent;
  639. private Action RefreshRateChangedEvent;
  640. public NativeApi(Action<WarningLevel> sustainedPerformanceWarning, Action sustainedPerformanceTimeout, Action refreshRateChanged, Action cpuPerformanceBoostReleasedByTimeout, Action gpuPerformanceBoostReleasedByTimeout)
  641. : base("com.samsung.android.gamesdk.GameSDKManager$Listener")
  642. {
  643. PerformanceWarningEvent = sustainedPerformanceWarning;
  644. PerformanceLevelTimeoutEvent = sustainedPerformanceTimeout;
  645. RefreshRateChangedEvent = refreshRateChanged;
  646. CpuPerformanceBoostReleasedByTimeoutEvent = cpuPerformanceBoostReleasedByTimeout;
  647. GpuPerformanceBoostReleasedByTimeoutEvent = gpuPerformanceBoostReleasedByTimeout;
  648. StaticInit();
  649. }
  650. [Preserve]
  651. void onHighTempWarning(int warningLevel)
  652. {
  653. GameSDKLog.Debug("Listener: onHighTempWarning(warningLevel={0})", warningLevel);
  654. if (warningLevel == 0)
  655. PerformanceWarningEvent(WarningLevel.NoWarning);
  656. else if (warningLevel == 1)
  657. PerformanceWarningEvent(WarningLevel.ThrottlingImminent);
  658. else if (warningLevel == 2)
  659. PerformanceWarningEvent(WarningLevel.Throttling);
  660. }
  661. [Preserve]
  662. void onReleasedByTimeout()
  663. {
  664. GameSDKLog.Debug("Listener: onReleasedByTimeout()");
  665. PerformanceLevelTimeoutEvent();
  666. }
  667. [Preserve]
  668. void onReleasedCpuBoost()
  669. {
  670. GameSDKLog.Debug("Listener: onReleasedCpuBoost()");
  671. CpuPerformanceBoostReleasedByTimeoutEvent();
  672. }
  673. [Preserve]
  674. void onReleasedGpuBoost()
  675. {
  676. GameSDKLog.Debug("Listener: onReleasedGPUBoost()");
  677. GpuPerformanceBoostReleasedByTimeoutEvent();
  678. }
  679. [Preserve]
  680. void onRefreshRateChanged()
  681. {
  682. GameSDKLog.Debug("Listener: onRefreshRateChanged()");
  683. RefreshRateChangedEvent();
  684. }
  685. static IntPtr GetJavaMethodID(IntPtr classId, string name, string sig)
  686. {
  687. AndroidJNI.ExceptionClear();
  688. var mid = AndroidJNI.GetMethodID(classId, name, sig);
  689. IntPtr ex = AndroidJNI.ExceptionOccurred();
  690. if (ex != (IntPtr)0)
  691. {
  692. AndroidJNI.ExceptionDescribe();
  693. AndroidJNI.ExceptionClear();
  694. return (IntPtr)0;
  695. }
  696. else
  697. {
  698. return mid;
  699. }
  700. }
  701. static private void StaticInit()
  702. {
  703. if (s_GameSDK == null)
  704. {
  705. try
  706. {
  707. s_GameSDK = new AndroidJavaObject("com.samsung.android.gamesdk.GameSDKManager");
  708. if (s_GameSDK != null)
  709. s_isAvailable = s_GameSDK.CallStatic<bool>("isAvailable");
  710. }
  711. catch (Exception ex)
  712. {
  713. GameSDKLog.Debug($"GameSDK is not available due to {ex} Aborting Adaptive Performance initialization.");
  714. s_isAvailable = false;
  715. s_GameSDK = null;
  716. }
  717. if (s_isAvailable)
  718. {
  719. if (!IsAPVersionSupported())
  720. {
  721. GameSDKLog.Debug($"GameSDK is the wrong version. Aborting Adaptive Performance Samsung Android initialization.");
  722. s_isAvailable = false;
  723. s_GameSDK = null;
  724. return;
  725. }
  726. s_GameSDKRawObjectID = s_GameSDK.GetRawObject();
  727. var classID = s_GameSDK.GetRawClass();
  728. s_GetGpuFrameTimeID = GetJavaMethodID(classID, "getGpuFrameTime", "()D");
  729. s_GetHighPrecisionSkinTempLevelID = GetJavaMethodID(classID, "getHighPrecisionSkinTempLevel", "()D");
  730. s_GetClusterInfolID = GetJavaMethodID(classID, "getClusterInfo", "()I");
  731. if (s_GetGpuFrameTimeID == (IntPtr)0 || s_GetHighPrecisionSkinTempLevelID == (IntPtr)0)
  732. s_isAvailable = false;
  733. }
  734. }
  735. }
  736. static public bool IsAvailable()
  737. {
  738. StaticInit();
  739. return s_isAvailable;
  740. }
  741. public bool RegisterListener()
  742. {
  743. bool success = false;
  744. try
  745. {
  746. success = s_GameSDK.Call<bool>("setListener", this);
  747. }
  748. catch (Exception)
  749. {
  750. success = false;
  751. }
  752. if (!success)
  753. GameSDKLog.Debug("failed to register listener");
  754. return success;
  755. }
  756. public void UnregisterListener()
  757. {
  758. bool success = true;
  759. try
  760. {
  761. GameSDKLog.Debug("setListener(null)");
  762. success = s_GameSDK.Call<bool>("setListener", (Object)null);
  763. }
  764. catch (Exception)
  765. {
  766. success = false;
  767. }
  768. if (!success)
  769. GameSDKLog.Debug("setListener(null) failed!");
  770. }
  771. static public bool IsAPVersionSupported()
  772. {
  773. try
  774. {
  775. Version initVersion;
  776. if (TryParseVersion(s_GameSDK.Call<string>("getVersion"), out initVersion))
  777. return initVersion >= new Version(3, 2);
  778. else
  779. return false;
  780. }
  781. catch (Exception)
  782. {
  783. GameSDKLog.Debug("[Exception] IsAPVersionSupported() failed!");
  784. }
  785. return false;
  786. }
  787. public bool Initialize()
  788. {
  789. bool isInitialized = false;
  790. try
  791. {
  792. Version initVersion;
  793. if (TryParseVersion(GetVersion(), out initVersion))
  794. {
  795. if (initVersion >= new Version(3, 2))
  796. {
  797. isInitialized = s_GameSDK.Call<bool>("initialize", initVersion.ToString());
  798. }
  799. else
  800. {
  801. GameSDKLog.Debug("GameSDK {0} is not supported and will not be initialized, Adaptive Performance will not be used.", initVersion);
  802. }
  803. if (isInitialized)
  804. {
  805. isInitialized = RegisterListener();
  806. }
  807. else
  808. {
  809. GameSDKLog.Debug("GameSDK.initialize() failed!");
  810. }
  811. }
  812. }
  813. catch (Exception)
  814. {
  815. GameSDKLog.Debug("[Exception] GameSDK.initialize() failed!");
  816. }
  817. return isInitialized;
  818. }
  819. public void Terminate()
  820. {
  821. UnregisterListener();
  822. try
  823. {
  824. var packageName = Application.identifier;
  825. GameSDKLog.Debug("GameSDK.finalize({0})", packageName);
  826. s_GameSDK.Call<bool>("finalize", packageName);
  827. }
  828. catch (Exception)
  829. {
  830. GameSDKLog.Debug("GameSDK.finalize() failed!");
  831. }
  832. }
  833. public string GetVersion()
  834. {
  835. string sdkVersion = "";
  836. try
  837. {
  838. sdkVersion = s_GameSDK.Call<string>("getVersion");
  839. }
  840. catch (Exception)
  841. {
  842. GameSDKLog.Debug("[Exception] GameSDK.getVersion() failed!");
  843. }
  844. return sdkVersion;
  845. }
  846. public double GetHighPrecisionSkinTempLevel()
  847. {
  848. double currentTempLevel = -1.0;
  849. try
  850. {
  851. currentTempLevel = AndroidJNI.CallDoubleMethod(s_GameSDKRawObjectID, s_GetHighPrecisionSkinTempLevelID, s_NoArgs);
  852. if (AndroidJNI.ExceptionOccurred() != IntPtr.Zero)
  853. {
  854. AndroidJNI.ExceptionDescribe();
  855. AndroidJNI.ExceptionClear();
  856. }
  857. }
  858. catch (Exception)
  859. {
  860. GameSDKLog.Debug("[Exception] GameSDK.getHighPrecisionSkinTempLevel() failed!");
  861. }
  862. return currentTempLevel;
  863. }
  864. public double GetGpuFrameTime()
  865. {
  866. double gpuFrameTime = -1.0;
  867. try
  868. {
  869. gpuFrameTime = AndroidJNI.CallDoubleMethod(s_GameSDKRawObjectID, s_GetGpuFrameTimeID, s_NoArgs);
  870. if (AndroidJNI.ExceptionOccurred() != IntPtr.Zero)
  871. {
  872. AndroidJNI.ExceptionDescribe();
  873. AndroidJNI.ExceptionClear();
  874. }
  875. }
  876. catch (Exception)
  877. {
  878. GameSDKLog.Debug("[Exception] GameSDK.getGpuFrameTime() failed!");
  879. }
  880. return gpuFrameTime;
  881. }
  882. public int SetFreqLevels(int cpu, int gpu)
  883. {
  884. int result = 0;
  885. try
  886. {
  887. result = s_GameSDK.Call<int>("setFreqLevels", cpu, gpu);
  888. GameSDKLog.Debug("setFreqLevels({0}, {1}) -> {2}", cpu, gpu, result);
  889. }
  890. catch (Exception x)
  891. {
  892. GameSDKLog.Debug("[Exception] GameSDK.setFreqLevels({0}, {1}) failed: {2}", cpu, gpu, x);
  893. }
  894. return result;
  895. }
  896. public bool EnableCpuBoost()
  897. {
  898. bool result = false;
  899. try
  900. {
  901. result = s_GameSDK.Call<bool>("setCpuBoostMode", 1);
  902. GameSDKLog.Debug("setCpuBoostMode(1) -> {0}", result);
  903. }
  904. catch (Exception x)
  905. {
  906. GameSDKLog.Debug("[Exception] GameSDK.setCpuBoostMode(1) failed: {0}", x);
  907. }
  908. return result;
  909. }
  910. public bool EnableGpuBoost()
  911. {
  912. bool result = false;
  913. try
  914. {
  915. result = s_GameSDK.Call<bool>("setGpuBoostMode", 1);
  916. GameSDKLog.Debug("setGpuBoostMode(1) -> {0}", result);
  917. }
  918. catch (Exception x)
  919. {
  920. GameSDKLog.Debug("[Exception] GameSDK.setGpuBoostMode(1) failed: {0}", x);
  921. }
  922. return result;
  923. }
  924. public int GetClusterInfo()
  925. {
  926. int result = -999;
  927. try
  928. {
  929. result = AndroidJNI.CallIntMethod(s_GameSDKRawObjectID, s_GetClusterInfolID, s_NoArgs);
  930. if (AndroidJNI.ExceptionOccurred() != IntPtr.Zero)
  931. {
  932. AndroidJNI.ExceptionDescribe();
  933. AndroidJNI.ExceptionClear();
  934. }
  935. GameSDKLog.Debug("getClusterInfo() -> {0}", result);
  936. }
  937. catch (Exception x)
  938. {
  939. GameSDKLog.Debug("[Exception] GameSDK.getClusterInfo() failed: {0}", x);
  940. }
  941. return result;
  942. }
  943. public int GetMaxCpuPerformanceLevel()
  944. {
  945. int maxCpuPerformanceLevel = Constants.UnknownPerformanceLevel;
  946. try
  947. {
  948. maxCpuPerformanceLevel = s_GameSDK.Call<int>("getCPULevelMax");
  949. }
  950. catch (Exception)
  951. {
  952. GameSDKLog.Debug("[Exception] GameSDK.getCPULevelMax() failed!");
  953. }
  954. return maxCpuPerformanceLevel;
  955. }
  956. public int GetMaxGpuPerformanceLevel()
  957. {
  958. int maxGpuPerformanceLevel = Constants.UnknownPerformanceLevel;
  959. try
  960. {
  961. maxGpuPerformanceLevel = s_GameSDK.Call<int>("getGPULevelMax");
  962. }
  963. catch (Exception)
  964. {
  965. GameSDKLog.Debug("[Exception] GameSDK.getGPULevelMax() failed!");
  966. }
  967. return maxGpuPerformanceLevel;
  968. }
  969. public bool IsVariableRefreshRateSupported()
  970. {
  971. bool vrrSupported = false;
  972. try
  973. {
  974. vrrSupported = s_GameSDK.Call<bool>("isGameSDKVariableRefreshRateSupported");
  975. GameSDKLog.Debug("isGameSDKVariableRefreshRateSupported->{0}", vrrSupported);
  976. }
  977. catch (Exception x)
  978. {
  979. GameSDKLog.Debug("[Exception] GameSDK.isGameSDKVariableRefreshRateSupported() failed: " + x.Message);
  980. }
  981. return vrrSupported;
  982. }
  983. public int[] GetSupportedRefreshRates()
  984. {
  985. int[] result = null;
  986. try
  987. {
  988. result = s_GameSDK.Call<int[]>("getSupportedRefreshRates");
  989. }
  990. catch (Exception x)
  991. {
  992. GameSDKLog.Debug("[Exception] GameSDK.getSupportedRefreshRates() failed: " + x.Message);
  993. }
  994. return result != null ? result : new int[0];
  995. }
  996. public bool SetRefreshRate(int targetRefreshRate)
  997. {
  998. try
  999. {
  1000. s_GameSDK.Call("setRefreshRate", targetRefreshRate);
  1001. }
  1002. catch (Exception x)
  1003. {
  1004. GameSDKLog.Debug("[Exception] GameSDK.setRefreshRate() failed: " + x.Message);
  1005. return false;
  1006. }
  1007. return true;
  1008. }
  1009. public bool ResetRefreshRate()
  1010. {
  1011. try
  1012. {
  1013. s_GameSDK.Call("resetRefreshRate");
  1014. }
  1015. catch (Exception x)
  1016. {
  1017. GameSDKLog.Debug("[Exception] GameSDK.resetRefreshRate() failed: " + x.Message);
  1018. return false;
  1019. }
  1020. return true;
  1021. }
  1022. public int GetCurrentRefreshRate()
  1023. {
  1024. int result = -1;
  1025. try
  1026. {
  1027. result = s_GameSDK.Call<int>("getCurrentRefreshRate");
  1028. }
  1029. catch (Exception x)
  1030. {
  1031. GameSDKLog.Debug("[Exception] GameSDK.getCurrentRefreshRate() failed: " + x.Message);
  1032. }
  1033. return result;
  1034. }
  1035. }
  1036. [Preserve]
  1037. internal class VRRManager : IVariableRefreshRate
  1038. {
  1039. NativeApi m_Api;
  1040. object m_RefreshRateChangedLock = new object();
  1041. bool m_RefreshRateChanged;
  1042. int[] m_SupportedRefreshRates = new int[0];
  1043. int m_CurrentRefreshRate = -1;
  1044. int m_LastSetRefreshRate = -1;
  1045. private void UpdateRefreshRateInfo()
  1046. {
  1047. var supportedRefreshRates = m_Api.GetSupportedRefreshRates();
  1048. if (settings.highSpeedVRR)
  1049. {
  1050. m_SupportedRefreshRates = supportedRefreshRates;
  1051. }
  1052. else
  1053. {
  1054. List<int> shrunkSupportedRefreshRates = new List<int>();
  1055. for (var i = 0; i < supportedRefreshRates.Length; ++i)
  1056. {
  1057. if (supportedRefreshRates[i] <= 60)
  1058. shrunkSupportedRefreshRates.Add(supportedRefreshRates[i]);
  1059. }
  1060. m_SupportedRefreshRates = shrunkSupportedRefreshRates.ToArray();
  1061. }
  1062. m_CurrentRefreshRate = m_Api.GetCurrentRefreshRate();
  1063. }
  1064. public VRRManager(NativeApi api)
  1065. {
  1066. m_Api = api;
  1067. SetDefaultVRR();
  1068. UpdateRefreshRateInfo();
  1069. }
  1070. // If HighSpeedVRR is not enabled we should not set over 60hz by default
  1071. private void SetDefaultVRR()
  1072. {
  1073. if (settings.highSpeedVRR)
  1074. return;
  1075. var index = Array.IndexOf(m_SupportedRefreshRates, 60);
  1076. if (index != -1)
  1077. {
  1078. SetRefreshRateByIndexInternal(index);
  1079. }
  1080. }
  1081. public void Resume()
  1082. {
  1083. bool changed = false;
  1084. var oldSupportedRefreshRates = m_SupportedRefreshRates;
  1085. var oldRefreshRate = m_LastSetRefreshRate;
  1086. UpdateRefreshRateInfo();
  1087. if (m_CurrentRefreshRate != oldRefreshRate)
  1088. changed = true;
  1089. else if (oldSupportedRefreshRates != m_SupportedRefreshRates)
  1090. changed = true;
  1091. if (changed)
  1092. {
  1093. lock (m_RefreshRateChangedLock)
  1094. {
  1095. m_RefreshRateChanged = true;
  1096. }
  1097. }
  1098. }
  1099. public void Update()
  1100. {
  1101. bool refreshRateChanged = false;
  1102. lock (m_RefreshRateChangedLock)
  1103. {
  1104. refreshRateChanged = m_RefreshRateChanged;
  1105. m_RefreshRateChanged = false;
  1106. }
  1107. if (refreshRateChanged)
  1108. {
  1109. UpdateRefreshRateInfo();
  1110. var index = Array.IndexOf(m_SupportedRefreshRates, m_LastSetRefreshRate);
  1111. if (index != -1)
  1112. {
  1113. SetRefreshRateByIndexInternal(index);
  1114. }
  1115. else if (index == -1 && m_LastSetRefreshRate != -1)
  1116. {
  1117. // Previous set refresh rate is not in available in the refreshrate list.
  1118. // Need to set 60Hz or lowest refresh rate possible.
  1119. // User sets 48Hz, but 48Hz is not on list anymore, because user changed Setting App - Display - Smooth option.
  1120. index = Array.IndexOf(m_SupportedRefreshRates, 60);
  1121. if (index != -1)
  1122. SetRefreshRateByIndexInternal(index);
  1123. }
  1124. RefreshRateChanged?.Invoke();
  1125. }
  1126. }
  1127. public int[] SupportedRefreshRates { get { return m_SupportedRefreshRates; } }
  1128. public int CurrentRefreshRate { get { return m_CurrentRefreshRate; } }
  1129. public bool SetRefreshRateByIndex(int index)
  1130. {
  1131. // Refreshrate potentially set by user
  1132. settings.automaticVRR = false;
  1133. return SetRefreshRateByIndexInternal(index);
  1134. }
  1135. private bool SetRefreshRateByIndexInternal(int index)
  1136. {
  1137. if (index >= 0 && index < SupportedRefreshRates.Length)
  1138. {
  1139. var refreshRateFromIndex = SupportedRefreshRates[index];
  1140. if (Application.targetFrameRate > 0 && index > 0 && SupportedRefreshRates[--index] > Application.targetFrameRate)
  1141. {
  1142. GameSDKLog.Debug("SetRefreshRateByIndex tries to set the refreshRateTarget {0} way higher than the targetFrameRate {1} which is not recommended due to temperature increase and unused performance.", refreshRateFromIndex, Application.targetFrameRate);
  1143. }
  1144. if (!settings.highSpeedVRR)
  1145. {
  1146. if (refreshRateFromIndex > 60)
  1147. {
  1148. GameSDKLog.Debug("High-Speed VRR is not enabled in the settings. Setting a refreshrate ({0}Hz) over 60Hz is not permitted due to temperature reasons.", refreshRateFromIndex);
  1149. return false;
  1150. }
  1151. }
  1152. if (m_Api.SetRefreshRate(refreshRateFromIndex))
  1153. {
  1154. m_CurrentRefreshRate = refreshRateFromIndex;
  1155. m_LastSetRefreshRate = refreshRateFromIndex;
  1156. return true;
  1157. }
  1158. }
  1159. return false;
  1160. }
  1161. public event VariableRefreshRateEventHandler RefreshRateChanged;
  1162. public void OnRefreshRateChanged()
  1163. {
  1164. lock (m_RefreshRateChangedLock)
  1165. {
  1166. m_RefreshRateChanged = true;
  1167. }
  1168. }
  1169. }
  1170. internal class AutoVariableRefreshRate
  1171. {
  1172. SamsungAndroidProviderSettings settings = SamsungAndroidProviderSettings.GetSettings();
  1173. IVariableRefreshRate vrrManager;
  1174. public AutoVariableRefreshRate(IVariableRefreshRate vrrManagerInstance)
  1175. {
  1176. vrrManager = vrrManagerInstance;
  1177. }
  1178. // Temperature checks of hardware are around 5sec and we don't need to check that often.
  1179. float VrrUpdateTime = 1;
  1180. int lastRefreshRateIndex = -1;
  1181. public void UpdateAutoVRR()
  1182. {
  1183. VrrUpdateTime -= Time.unscaledDeltaTime;
  1184. if (VrrUpdateTime <= 0)
  1185. {
  1186. VrrUpdateTime = 1;
  1187. // targetFPS = 70 (in 48/60/96/120)-> vrr 96 never 120
  1188. // targetFPS = 40 (in 48/60/96/120)-> vrr 60 never 96
  1189. // targetFPS = 48/60/96/120 (in 48/60/96/120) -> vrr 48/60/96/12 never higher
  1190. // targetFPS = 70 (in 48/60)-> 60
  1191. var refreshRateIndex = vrrManager.SupportedRefreshRates.Length - 1;
  1192. // we look if a targetFrameRate is set, even in vsync mode were target framerate is ignored. Otherwise we use maximum framerate
  1193. if (Application.targetFrameRate > 0)
  1194. {
  1195. for (int i = 0; i < vrrManager.SupportedRefreshRates.Length; ++i)
  1196. {
  1197. if (Application.targetFrameRate > vrrManager.SupportedRefreshRates[i])
  1198. {
  1199. continue;
  1200. }
  1201. else
  1202. {
  1203. refreshRateIndex = i;
  1204. break;
  1205. }
  1206. }
  1207. }
  1208. if (lastRefreshRateIndex != refreshRateIndex)
  1209. {
  1210. lastRefreshRateIndex = refreshRateIndex;
  1211. vrrManager.SetRefreshRateByIndex(refreshRateIndex);
  1212. // automatic VRR gets disabled in SetRefreshRateByIndex and we want to ensure we still get updated.
  1213. settings.automaticVRR = true;
  1214. }
  1215. }
  1216. }
  1217. }
  1218. }
  1219. }
  1220. #endif