Assignment for RMIT Mixed Reality in 2020
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.

906 lines
40 KiB

  1. // Adaptive Quality|Utilities|90100
  2. // Adapted from The Lab Renderer's ValveCamera.cs, available at
  3. // https://github.com/ValveSoftware/the_lab_renderer/blob/ae64c48a8ccbe5406aba1e39b160d4f2f7156c2c/Assets/TheLabRenderer/Scripts/ValveCamera.cs
  4. // For The Lab Renderer's license see THIRD_PARTY_NOTICES.
  5. // **Only Compatible With Unity 5.4 and above**
  6. #if (UNITY_5_4_OR_NEWER)
  7. namespace VRTK
  8. {
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Collections.ObjectModel;
  12. using System.Linq;
  13. using System.Text;
  14. using UnityEngine;
  15. #if UNITY_2017_2_OR_NEWER
  16. using UnityEngine.XR;
  17. #else
  18. using XRSettings = UnityEngine.VR.VRSettings;
  19. using XRDevice = UnityEngine.VR.VRDevice;
  20. #endif
  21. /// <summary>
  22. /// Adaptive Quality dynamically changes rendering settings to maintain VR framerate while maximizing GPU utilization.
  23. /// </summary>
  24. /// <remarks>
  25. /// > **Only Compatible With Unity 5.4 and above**
  26. ///
  27. /// There are two goals:
  28. /// <list type="bullet">
  29. /// <item> <description>Reduce the chances of dropping frames and reprojecting</description> </item>
  30. /// <item> <description>Increase quality when there are idle GPU cycles</description> </item>
  31. /// </list>
  32. /// <para />
  33. /// This script currently changes the following to reach these goals:
  34. /// <list type="bullet">
  35. /// <item> <description>Rendering resolution and viewport size (aka Dynamic Resolution)</description> </item>
  36. /// </list>
  37. /// <para />
  38. /// In the future it could be changed to also change the following:
  39. /// <list type="bullet">
  40. /// <item> <description>MSAA level</description> </item>
  41. /// <item> <description>Fixed Foveated Rendering</description> </item>
  42. /// <item> <description>Radial Density Masking</description> </item>
  43. /// <item> <description>(Non-fixed) Foveated Rendering (once HMDs support eye tracking)</description> </item>
  44. /// </list>
  45. /// <para />
  46. /// Some shaders, especially Image Effects, need to be modified to work with the changed render scale. To fix them
  47. /// pass `1.0f / VRSettings.renderViewportScale` into the shader and scale all incoming UV values with it in the vertex
  48. /// program. Do this by using `Material.SetFloat` to set the value in the script that configures the shader.
  49. /// <para />
  50. /// In more detail:
  51. /// <list type="bullet">
  52. /// <item> <description>In the `.shader` file: Add a new runtime-set property value `float _InverseOfRenderViewportScale`
  53. /// and add `vertexInput.texcoord *= _InverseOfRenderViewportScale` to the start of the vertex program</description> </item>
  54. /// <item> <description>In the `.cs` file: Before using the material (eg. `Graphics.Blit`) add
  55. /// `material.SetFloat("_InverseOfRenderViewportScale", 1.0f / VRSettings.renderViewportScale)`</description> </item>
  56. /// </list>
  57. /// </remarks>
  58. /// <example>
  59. /// `VRTK/Examples/039_CameraRig_AdaptiveQuality` displays the frames per second in the centre of the headset view.
  60. /// The debug visualization of this script is displayed near the top edge of the headset view.
  61. /// Pressing the trigger generates a new sphere and pressing the touchpad generates ten new spheres.
  62. /// Eventually when lots of spheres are present the FPS will drop and demonstrate the script.
  63. /// </example>
  64. [AddComponentMenu("VRTK/Scripts/Utilities/VRTK_AdaptiveQuality")]
  65. public sealed class VRTK_AdaptiveQuality : MonoBehaviour
  66. {
  67. #region Public fields
  68. [Tooltip("Toggles whether to show the debug overlay.\n\n"
  69. + "Each square represents a different level on the quality scale. Levels increase from left to right,"
  70. + " the first green box that is lit above represents the recommended render target resolution provided by the"
  71. + " current `VRDevice`, the box that is lit below in cyan represents the current resolution and the filled box"
  72. + " represents the current viewport scale. The yellow boxes represent resolutions below the recommended render target resolution.\n"
  73. + "The currently lit box becomes red whenever the user is likely seeing reprojection in the HMD since the"
  74. + " application isn't maintaining VR framerate. If lit, the box all the way on the left is almost always lit"
  75. + " red because it represents the lowest render scale with reprojection on.")]
  76. public bool drawDebugVisualization;
  77. [Tooltip("Toggles whether to allow keyboard shortcuts to control this script.\n\n"
  78. + " * The supported shortcuts are:\n"
  79. + " * `Shift+F1`: Toggle debug visualization on/off\n"
  80. + " * `Shift+F2`: Toggle usage of override render scale on/off\n"
  81. + " * `Shift+F3`: Decrease override render scale level\n"
  82. + " * `Shift+F4`: Increase override render scale level")]
  83. public bool allowKeyboardShortcuts = true;
  84. [Tooltip("Toggles whether to allow command line arguments to control this script at startup of the standalone build.\n\n"
  85. + " * The supported command line arguments all begin with '-' and are:\n"
  86. + " * `-noaq`: Disable adaptive quality\n"
  87. + " * `-aqminscale X`: Set minimum render scale to X\n"
  88. + " * `-aqmaxscale X`: Set maximum render scale to X\n"
  89. + " * `-aqmaxres X`: Set maximum render target dimension to X\n"
  90. + " * `-aqfillratestep X`: Set render scale fill rate step size in percent to X (X from 1 to 100)\n"
  91. + " * `-aqoverride X`: Set override render scale level to X\n"
  92. + " * `-vrdebug`: Enable debug visualization\n"
  93. + " * `-msaa X`: Set MSAA level to X")]
  94. public bool allowCommandLineArguments = true;
  95. [Tooltip("The MSAA level to use.")]
  96. [Header("Quality")]
  97. [Range(0, 8)]
  98. public int msaaLevel = 4;
  99. [Tooltip("Toggles whether the render viewport scale is dynamically adjusted to maintain VR framerate.\n\n"
  100. + "If unchecked, the renderer will render at the recommended resolution provided by the current `VRDevice`.")]
  101. public bool scaleRenderViewport = true;
  102. [Tooltip("The minimum and maximum allowed render scale.")]
  103. [MinMaxRange(0.01f, 5f)]
  104. public Limits2D renderScaleLimits = new Limits2D(0.8f, 1.4f);
  105. [Obsolete("`VRTK_AdaptiveQuality.minimumRenderScale` has been replaced with the `VRTK_AdaptiveQuality.renderScaleLimits`. This parameter will be removed in a future version of VRTK.")]
  106. [ObsoleteInspector]
  107. public float minimumRenderScale = 0.8f;
  108. [Obsolete("`VRTK_AdaptiveQuality.maximumRenderScale` has been replaced with the `VRTK_AdaptiveQuality.renderScaleLimits`. This parameter will be removed in a future version of VRTK.")]
  109. [ObsoleteInspector]
  110. public float maximumRenderScale = 1.4f;
  111. [Tooltip("The maximum allowed render target dimension.\n\n"
  112. + "This puts an upper limit on the size of the render target regardless of the maximum render scale.")]
  113. public int maximumRenderTargetDimension = 4096;
  114. [Tooltip("The fill rate step size in percent by which the render scale levels will be calculated.")]
  115. [Range(1, 100)]
  116. public int renderScaleFillRateStepSizeInPercent = 15;
  117. [Tooltip("Toggles whether the render target resolution is dynamically adjusted to maintain VR framerate.\n\n"
  118. + "If unchecked, the renderer will use the maximum target resolution specified by `maximumRenderScale`.")]
  119. public bool scaleRenderTargetResolution = false;
  120. [Tooltip("Toggles whether to override the used render viewport scale level.")]
  121. [Header("Override")]
  122. [NonSerialized]
  123. public bool overrideRenderViewportScale;
  124. [Tooltip("The render viewport scale level to override the current one with.")]
  125. [NonSerialized]
  126. public int overrideRenderViewportScaleLevel;
  127. #endregion
  128. #region Public readonly fields & properties
  129. /// <summary>
  130. /// All the calculated render scales.
  131. /// </summary>
  132. /// <remarks>
  133. /// The elements of this collection are to be interpreted as modifiers to the recommended render target
  134. /// resolution provided by the current `VRDevice`.
  135. /// </remarks>
  136. public readonly ReadOnlyCollection<float> renderScales;
  137. /// <summary>
  138. /// The current render scale.
  139. /// </summary>
  140. /// <remarks>
  141. /// A render scale of `1.0` represents the recommended render target resolution provided by the current `VRDevice`.
  142. /// </remarks>
  143. public static float CurrentRenderScale
  144. {
  145. get { return VRTK_SharedMethods.GetEyeTextureResolutionScale() * XRSettings.renderViewportScale; }
  146. }
  147. /// <summary>
  148. /// The recommended render target resolution provided by the current `VRDevice`.
  149. /// </summary>
  150. public Vector2 defaultRenderTargetResolution
  151. {
  152. get { return RenderTargetResolutionForRenderScale(allRenderScales[defaultRenderViewportScaleLevel]); }
  153. }
  154. /// <summary>
  155. /// The current render target resolution.
  156. /// </summary>
  157. public Vector2 currentRenderTargetResolution
  158. {
  159. get { return RenderTargetResolutionForRenderScale(CurrentRenderScale); }
  160. }
  161. #endregion
  162. #region Private fields
  163. /// <summary>
  164. /// The frame duration in milliseconds to fallback to if the current `VRDevice` specifies no refresh rate.
  165. /// </summary>
  166. private const float DefaultFrameDurationInMilliseconds = 1000f / 90f;
  167. private readonly AdaptiveSetting<int> renderViewportScaleSetting = new AdaptiveSetting<int>(0, 30, 10);
  168. private readonly AdaptiveSetting<int> renderScaleSetting = new AdaptiveSetting<int>(0, 180, 90);
  169. private readonly List<float> allRenderScales = new List<float>();
  170. private int defaultRenderViewportScaleLevel;
  171. private float previousMinimumRenderScale;
  172. private float previousMaximumRenderScale;
  173. private float previousRenderScaleFillRateStepSizeInPercent;
  174. private readonly Timing timing = new Timing();
  175. private int lastRenderViewportScaleLevelBelowRenderScaleLevelFrameCount;
  176. private bool interleavedReprojectionEnabled;
  177. private bool hmdDisplayIsOnDesktop;
  178. private float singleFrameDurationInMilliseconds;
  179. private GameObject debugVisualizationQuad;
  180. private Material debugVisualizationQuadMaterial;
  181. #endregion
  182. public VRTK_AdaptiveQuality()
  183. {
  184. renderScales = allRenderScales.AsReadOnly();
  185. }
  186. /// <summary>
  187. /// Calculates and returns the render target resolution for a given render scale.
  188. /// </summary>
  189. /// <param name="renderScale">
  190. /// The render scale to calculate the render target resolution with.
  191. /// </param>
  192. /// <returns>
  193. /// The render target resolution for `renderScale`.
  194. /// </returns>
  195. public static Vector2 RenderTargetResolutionForRenderScale(float renderScale)
  196. {
  197. return new Vector2((int)(XRSettings.eyeTextureWidth / VRTK_SharedMethods.GetEyeTextureResolutionScale() * renderScale),
  198. (int)(XRSettings.eyeTextureHeight / VRTK_SharedMethods.GetEyeTextureResolutionScale() * renderScale));
  199. }
  200. /// <summary>
  201. /// Calculates and returns the biggest allowed maximum render scale to be used for `maximumRenderScale`
  202. /// given the current `maximumRenderTargetDimension`.
  203. /// </summary>
  204. /// <returns>
  205. /// The biggest allowed maximum render scale.
  206. /// </returns>
  207. public float BiggestAllowedMaximumRenderScale()
  208. {
  209. if (XRSettings.eyeTextureWidth == 0 || XRSettings.eyeTextureHeight == 0)
  210. {
  211. return renderScaleLimits.maximum;
  212. }
  213. float maximumHorizontalRenderScale = maximumRenderTargetDimension * VRTK_SharedMethods.GetEyeTextureResolutionScale()
  214. / XRSettings.eyeTextureWidth;
  215. float maximumVerticalRenderScale = maximumRenderTargetDimension * VRTK_SharedMethods.GetEyeTextureResolutionScale()
  216. / XRSettings.eyeTextureHeight;
  217. return Mathf.Min(maximumHorizontalRenderScale, maximumVerticalRenderScale);
  218. }
  219. /// <summary>
  220. /// A summary of this script by listing all the calculated render scales with their
  221. /// corresponding render target resolution.
  222. /// </summary>
  223. /// <returns>
  224. /// The summary.
  225. /// </returns>
  226. public override string ToString()
  227. {
  228. StringBuilder stringBuilder = new StringBuilder("Adaptive Quality\n");
  229. stringBuilder.AppendLine("Render Scale:");
  230. stringBuilder.AppendLine("Level - Resolution - Multiplier");
  231. for (int index = 0; index < allRenderScales.Count; index++)
  232. {
  233. float renderScale = allRenderScales[index];
  234. Vector2 renderTargetResolution = RenderTargetResolutionForRenderScale(renderScale);
  235. stringBuilder.AppendFormat(
  236. "{0, 3} {1, 5}x{2, -5} {3, -8}",
  237. index,
  238. (int)renderTargetResolution.x,
  239. (int)renderTargetResolution.y,
  240. renderScale);
  241. if (index == 0)
  242. {
  243. stringBuilder.Append(" (Interleaved reprojection hint)");
  244. }
  245. else if (index == defaultRenderViewportScaleLevel)
  246. {
  247. stringBuilder.Append(" (Default)");
  248. }
  249. if (index == renderViewportScaleSetting.currentValue)
  250. {
  251. stringBuilder.Append(" (Current Viewport)");
  252. }
  253. if (index == renderScaleSetting.currentValue)
  254. {
  255. stringBuilder.Append(" (Current Target Resolution)");
  256. }
  257. if (index != allRenderScales.Count - 1)
  258. {
  259. stringBuilder.AppendLine();
  260. }
  261. }
  262. return stringBuilder.ToString();
  263. }
  264. #region MonoBehaviour methods
  265. private void Awake()
  266. {
  267. VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
  268. }
  269. private void OnEnable()
  270. {
  271. Camera.onPreCull += OnCameraPreCull;
  272. hmdDisplayIsOnDesktop = VRTK_SDK_Bridge.IsDisplayOnDesktop();
  273. singleFrameDurationInMilliseconds = XRDevice.refreshRate > 0.0f
  274. ? 1000.0f / XRDevice.refreshRate
  275. : DefaultFrameDurationInMilliseconds;
  276. HandleCommandLineArguments();
  277. if (!Application.isEditor)
  278. {
  279. OnValidate();
  280. }
  281. }
  282. private void OnDisable()
  283. {
  284. Camera.onPreCull -= OnCameraPreCull;
  285. SetRenderScale(1.0f, 1.0f);
  286. }
  287. private void OnDestroy()
  288. {
  289. VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
  290. }
  291. private void OnValidate()
  292. {
  293. renderScaleLimits.minimum = Mathf.Max(0.01f, renderScaleLimits.minimum);
  294. renderScaleLimits.maximum = Mathf.Max(renderScaleLimits.minimum, renderScaleLimits.maximum);
  295. maximumRenderTargetDimension = Mathf.Max(2, maximumRenderTargetDimension);
  296. renderScaleFillRateStepSizeInPercent = Mathf.Max(1, renderScaleFillRateStepSizeInPercent);
  297. msaaLevel = msaaLevel == 1 ? 0 : Mathf.Clamp(Mathf.ClosestPowerOfTwo(msaaLevel), 0, 8);
  298. }
  299. private void Update()
  300. {
  301. HandleKeyPresses();
  302. UpdateRenderScaleLevels();
  303. CreateOrDestroyDebugVisualization();
  304. UpdateDebugVisualization();
  305. timing.SaveCurrentFrameTiming();
  306. }
  307. #if UNITY_5_4_1 || UNITY_5_4_2 || UNITY_5_4_3 || UNITY_5_5_OR_NEWER
  308. private void LateUpdate()
  309. {
  310. UpdateRenderScale();
  311. }
  312. #endif
  313. private void OnCameraPreCull(Camera camera)
  314. {
  315. if (camera.transform != VRTK_SDK_Bridge.GetHeadsetCamera())
  316. {
  317. return;
  318. }
  319. #if !(UNITY_5_4_1 || UNITY_5_4_2 || UNITY_5_4_3 || UNITY_5_5_OR_NEWER)
  320. UpdateRenderScale();
  321. #endif
  322. UpdateMSAALevel();
  323. }
  324. #endregion
  325. private void HandleCommandLineArguments()
  326. {
  327. if (!allowCommandLineArguments)
  328. {
  329. return;
  330. }
  331. string[] commandLineArguments = VRTK_SharedMethods.GetCommandLineArguements();
  332. for (int index = 0; index < commandLineArguments.Length; index++)
  333. {
  334. string argument = commandLineArguments[index];
  335. string nextArgument = index + 1 < commandLineArguments.Length ? commandLineArguments[index + 1] : "";
  336. switch (argument)
  337. {
  338. case CommandLineArguments.Disable:
  339. scaleRenderViewport = false;
  340. break;
  341. case CommandLineArguments.MinimumRenderScale:
  342. renderScaleLimits.minimum = float.Parse(nextArgument);
  343. break;
  344. case CommandLineArguments.MaximumRenderScale:
  345. renderScaleLimits.maximum = float.Parse(nextArgument);
  346. break;
  347. case CommandLineArguments.MaximumRenderTargetDimension:
  348. maximumRenderTargetDimension = int.Parse(nextArgument);
  349. break;
  350. case CommandLineArguments.RenderScaleFillRateStepSizeInPercent:
  351. renderScaleFillRateStepSizeInPercent = int.Parse(nextArgument);
  352. break;
  353. case CommandLineArguments.OverrideRenderScaleLevel:
  354. overrideRenderViewportScale = true;
  355. overrideRenderViewportScaleLevel = int.Parse(nextArgument);
  356. break;
  357. case CommandLineArguments.DrawDebugVisualization:
  358. drawDebugVisualization = true;
  359. break;
  360. case CommandLineArguments.MSAALevel:
  361. msaaLevel = int.Parse(nextArgument);
  362. break;
  363. }
  364. }
  365. }
  366. private void HandleKeyPresses()
  367. {
  368. if (!allowKeyboardShortcuts || !KeyboardShortcuts.Modifiers.Any(Input.GetKey))
  369. {
  370. return;
  371. }
  372. if (Input.GetKeyDown(KeyboardShortcuts.ToggleDrawDebugVisualization))
  373. {
  374. drawDebugVisualization = !drawDebugVisualization;
  375. }
  376. else if (Input.GetKeyDown(KeyboardShortcuts.ToggleOverrideRenderScale))
  377. {
  378. overrideRenderViewportScale = !overrideRenderViewportScale;
  379. }
  380. else if (Input.GetKeyDown(KeyboardShortcuts.DecreaseOverrideRenderScaleLevel))
  381. {
  382. overrideRenderViewportScaleLevel = ClampRenderScaleLevel(overrideRenderViewportScaleLevel - 1);
  383. }
  384. else if (Input.GetKeyDown(KeyboardShortcuts.IncreaseOverrideRenderScaleLevel))
  385. {
  386. overrideRenderViewportScaleLevel = ClampRenderScaleLevel(overrideRenderViewportScaleLevel + 1);
  387. }
  388. }
  389. private void UpdateMSAALevel()
  390. {
  391. if (QualitySettings.antiAliasing != msaaLevel)
  392. {
  393. QualitySettings.antiAliasing = msaaLevel;
  394. }
  395. }
  396. #region Render scale methods
  397. private void UpdateRenderScaleLevels()
  398. {
  399. if (Mathf.Abs(previousMinimumRenderScale - renderScaleLimits.minimum) <= float.Epsilon
  400. && Mathf.Abs(previousMaximumRenderScale - renderScaleLimits.maximum) <= float.Epsilon
  401. && Mathf.Abs(previousRenderScaleFillRateStepSizeInPercent - renderScaleFillRateStepSizeInPercent) <= float.Epsilon)
  402. {
  403. return;
  404. }
  405. previousMinimumRenderScale = renderScaleLimits.minimum;
  406. previousMaximumRenderScale = renderScaleLimits.maximum;
  407. previousRenderScaleFillRateStepSizeInPercent = renderScaleFillRateStepSizeInPercent;
  408. allRenderScales.Clear();
  409. // Respect maximumRenderTargetDimension
  410. float allowedMaximumRenderScale = BiggestAllowedMaximumRenderScale();
  411. float renderScaleToAdd = Mathf.Min(renderScaleLimits.minimum, allowedMaximumRenderScale);
  412. // Add min scale as the reprojection scale
  413. allRenderScales.Add(renderScaleToAdd);
  414. // Add all entries
  415. while (renderScaleToAdd <= renderScaleLimits.maximum)
  416. {
  417. allRenderScales.Add(renderScaleToAdd);
  418. renderScaleToAdd =
  419. Mathf.Sqrt((renderScaleFillRateStepSizeInPercent + 100) / 100.0f * renderScaleToAdd * renderScaleToAdd);
  420. if (renderScaleToAdd > allowedMaximumRenderScale)
  421. {
  422. // Too large
  423. break;
  424. }
  425. }
  426. // Figure out default render viewport scale level for debug visualization
  427. defaultRenderViewportScaleLevel = Mathf.Clamp(
  428. allRenderScales.FindIndex(renderScale => renderScale >= 1.0f),
  429. 1,
  430. allRenderScales.Count - 1);
  431. renderViewportScaleSetting.currentValue = defaultRenderViewportScaleLevel;
  432. renderScaleSetting.currentValue = defaultRenderViewportScaleLevel;
  433. overrideRenderViewportScaleLevel = ClampRenderScaleLevel(overrideRenderViewportScaleLevel);
  434. }
  435. private void UpdateRenderScale()
  436. {
  437. if (allRenderScales.Count == 0)
  438. {
  439. return;
  440. }
  441. if (!scaleRenderViewport)
  442. {
  443. renderViewportScaleSetting.currentValue = defaultRenderViewportScaleLevel;
  444. renderScaleSetting.currentValue = defaultRenderViewportScaleLevel;
  445. SetRenderScale(1.0f, 1.0f);
  446. return;
  447. }
  448. // Rendering in low resolution means adaptive quality needs to scale back the render scale target to free up gpu cycles
  449. bool renderInLowResolution = VRTK_SDK_Bridge.ShouldAppRenderWithLowResources();
  450. // Thresholds
  451. float allowedSingleFrameDurationInMilliseconds = renderInLowResolution
  452. ? singleFrameDurationInMilliseconds * 0.75f
  453. : singleFrameDurationInMilliseconds;
  454. float lowThresholdInMilliseconds = 0.7f * allowedSingleFrameDurationInMilliseconds;
  455. float extrapolationThresholdInMilliseconds = 0.85f * allowedSingleFrameDurationInMilliseconds;
  456. float highThresholdInMilliseconds = 0.9f * allowedSingleFrameDurationInMilliseconds;
  457. int newRenderViewportScaleLevel = renderViewportScaleSetting.currentValue;
  458. // Rapidly reduce render viewport scale level if cost of last 1 or 3 frames, or the predicted next frame's cost are expensive
  459. if (timing.WasFrameTimingBad(
  460. 1,
  461. highThresholdInMilliseconds,
  462. renderViewportScaleSetting.lastChangeFrameCount,
  463. renderViewportScaleSetting.decreaseFrameCost)
  464. || timing.WasFrameTimingBad(
  465. 3,
  466. highThresholdInMilliseconds,
  467. renderViewportScaleSetting.lastChangeFrameCount,
  468. renderViewportScaleSetting.decreaseFrameCost)
  469. || timing.WillFrameTimingBeBad(
  470. extrapolationThresholdInMilliseconds,
  471. highThresholdInMilliseconds,
  472. renderViewportScaleSetting.lastChangeFrameCount,
  473. renderViewportScaleSetting.decreaseFrameCost))
  474. {
  475. // Always drop 2 levels except when dropping from level 2 (level 0 is for reprojection)
  476. newRenderViewportScaleLevel = ClampRenderScaleLevel(renderViewportScaleSetting.currentValue == 2
  477. ? 1
  478. : renderViewportScaleSetting.currentValue - 2);
  479. }
  480. // Rapidly increase render viewport scale level if last 12 frames are cheap
  481. else if (timing.WasFrameTimingGood(
  482. 12,
  483. lowThresholdInMilliseconds,
  484. renderViewportScaleSetting.lastChangeFrameCount - renderViewportScaleSetting.increaseFrameCost,
  485. renderViewportScaleSetting.increaseFrameCost))
  486. {
  487. newRenderViewportScaleLevel = ClampRenderScaleLevel(renderViewportScaleSetting.currentValue + 2);
  488. }
  489. // Slowly increase render viewport scale level if last 6 frames are cheap
  490. else if (timing.WasFrameTimingGood(
  491. 6,
  492. lowThresholdInMilliseconds,
  493. renderViewportScaleSetting.lastChangeFrameCount,
  494. renderViewportScaleSetting.increaseFrameCost))
  495. {
  496. // Only increase by 1 level to prevent frame drops caused by adjusting
  497. newRenderViewportScaleLevel = ClampRenderScaleLevel(renderViewportScaleSetting.currentValue + 1);
  498. }
  499. // Apply and remember when render viewport scale level changed
  500. if (newRenderViewportScaleLevel != renderViewportScaleSetting.currentValue)
  501. {
  502. if (renderViewportScaleSetting.currentValue >= renderScaleSetting.currentValue
  503. && newRenderViewportScaleLevel < renderScaleSetting.currentValue)
  504. {
  505. lastRenderViewportScaleLevelBelowRenderScaleLevelFrameCount = Time.frameCount;
  506. }
  507. renderViewportScaleSetting.currentValue = newRenderViewportScaleLevel;
  508. }
  509. // Ignore frame timings if overriding
  510. if (overrideRenderViewportScale)
  511. {
  512. renderViewportScaleSetting.currentValue = overrideRenderViewportScaleLevel;
  513. }
  514. // Force on interleaved reprojection for level 0 which is just a replica of level 1 with reprojection enabled
  515. float additionalViewportScale = 1.0f;
  516. if (!hmdDisplayIsOnDesktop)
  517. {
  518. if (renderViewportScaleSetting.currentValue == 0)
  519. {
  520. if (interleavedReprojectionEnabled && timing.GetFrameTiming(0) < singleFrameDurationInMilliseconds * 0.85f)
  521. {
  522. interleavedReprojectionEnabled = false;
  523. }
  524. else if (timing.GetFrameTiming(0) > singleFrameDurationInMilliseconds * 0.925f)
  525. {
  526. interleavedReprojectionEnabled = true;
  527. }
  528. }
  529. else
  530. {
  531. interleavedReprojectionEnabled = false;
  532. }
  533. VRTK_SDK_Bridge.ForceInterleavedReprojectionOn(interleavedReprojectionEnabled);
  534. }
  535. // Not running in direct mode! Interleaved reprojection not supported, so scale down the viewport some more
  536. else if (renderViewportScaleSetting.currentValue == 0)
  537. {
  538. additionalViewportScale = 0.8f;
  539. }
  540. int newRenderScaleLevel = renderScaleSetting.currentValue;
  541. int levelInBetween = (renderViewportScaleSetting.currentValue - renderScaleSetting.currentValue) / 2;
  542. // Increase render scale level to the level in between
  543. if (renderScaleSetting.currentValue < renderViewportScaleSetting.currentValue
  544. && Time.frameCount >= renderScaleSetting.lastChangeFrameCount + renderScaleSetting.increaseFrameCost)
  545. {
  546. newRenderScaleLevel = ClampRenderScaleLevel(renderScaleSetting.currentValue + Mathf.Max(1, levelInBetween));
  547. }
  548. // Decrease render scale level
  549. else if (renderScaleSetting.currentValue > renderViewportScaleSetting.currentValue
  550. && Time.frameCount >= renderScaleSetting.lastChangeFrameCount + renderScaleSetting.decreaseFrameCost
  551. && Time.frameCount >= lastRenderViewportScaleLevelBelowRenderScaleLevelFrameCount + renderViewportScaleSetting.increaseFrameCost)
  552. {
  553. // Slowly decrease render scale level to level in between if last 6 frames are cheap, otherwise rapidly
  554. newRenderScaleLevel = timing.WasFrameTimingGood(6, lowThresholdInMilliseconds, 0, 0)
  555. ? ClampRenderScaleLevel(renderScaleSetting.currentValue + Mathf.Min(-1, levelInBetween))
  556. : renderViewportScaleSetting.currentValue;
  557. }
  558. // Apply and remember when render scale level changed
  559. renderScaleSetting.currentValue = newRenderScaleLevel;
  560. if (!scaleRenderTargetResolution)
  561. {
  562. renderScaleSetting.currentValue = allRenderScales.Count - 1;
  563. }
  564. float newRenderScale = allRenderScales[renderScaleSetting.currentValue];
  565. float newRenderViewportScale = allRenderScales[Mathf.Min(renderViewportScaleSetting.currentValue, renderScaleSetting.currentValue)]
  566. / newRenderScale * additionalViewportScale;
  567. SetRenderScale(newRenderScale, newRenderViewportScale);
  568. }
  569. private static void SetRenderScale(float renderScale, float renderViewportScale)
  570. {
  571. if (Mathf.Abs(VRTK_SharedMethods.GetEyeTextureResolutionScale() - renderScale) > float.Epsilon)
  572. {
  573. VRTK_SharedMethods.SetEyeTextureResolutionScale(renderScale);
  574. }
  575. if (Mathf.Abs(XRSettings.renderViewportScale - renderViewportScale) > float.Epsilon)
  576. {
  577. XRSettings.renderViewportScale = renderViewportScale;
  578. }
  579. }
  580. private int ClampRenderScaleLevel(int renderScaleLevel)
  581. {
  582. return Mathf.Clamp(renderScaleLevel, 0, allRenderScales.Count - 1);
  583. }
  584. #endregion
  585. #region Debug visualization methods
  586. private void CreateOrDestroyDebugVisualization()
  587. {
  588. if (!Application.isPlaying)
  589. {
  590. return;
  591. }
  592. if (enabled && drawDebugVisualization && debugVisualizationQuad == null)
  593. {
  594. Mesh mesh = new Mesh
  595. {
  596. vertices =
  597. new[]
  598. {
  599. new Vector3(-0.5f, 0.9f, 1.0f),
  600. new Vector3(-0.5f, 1.0f, 1.0f),
  601. new Vector3(0.5f, 1.0f, 1.0f),
  602. new Vector3(0.5f, 0.9f, 1.0f)
  603. },
  604. uv =
  605. new[]
  606. {
  607. new Vector2(0.0f, 0.0f),
  608. new Vector2(0.0f, 1.0f),
  609. new Vector2(1.0f, 1.0f),
  610. new Vector2(1.0f, 0.0f)
  611. },
  612. triangles = new[] { 0, 1, 2, 0, 2, 3 }
  613. };
  614. #if !UNITY_5_5_OR_NEWER
  615. mesh.Optimize();
  616. #endif
  617. mesh.UploadMeshData(true);
  618. debugVisualizationQuad = new GameObject(VRTK_SharedMethods.GenerateVRTKObjectName(true, "AdaptiveQualityDebugVisualizationQuad"));
  619. debugVisualizationQuad.transform.parent = VRTK_DeviceFinder.HeadsetTransform();
  620. debugVisualizationQuad.transform.localPosition = Vector3.forward;
  621. debugVisualizationQuad.transform.localRotation = Quaternion.identity;
  622. debugVisualizationQuad.AddComponent<MeshFilter>().mesh = mesh;
  623. debugVisualizationQuadMaterial = Resources.Load<Material>("AdaptiveQualityDebugVisualization");
  624. debugVisualizationQuad.AddComponent<MeshRenderer>().material = debugVisualizationQuadMaterial;
  625. }
  626. else if ((!enabled || !drawDebugVisualization) && debugVisualizationQuad != null)
  627. {
  628. Destroy(debugVisualizationQuad);
  629. debugVisualizationQuad = null;
  630. debugVisualizationQuadMaterial = null;
  631. }
  632. }
  633. private void UpdateDebugVisualization()
  634. {
  635. if (!drawDebugVisualization || debugVisualizationQuadMaterial == null)
  636. {
  637. return;
  638. }
  639. int lastFrameIsInBudget = (interleavedReprojectionEnabled || VRTK_SharedMethods.GetGPUTimeLastFrame() > singleFrameDurationInMilliseconds ? 0 : 1);
  640. debugVisualizationQuadMaterial.SetInt(ShaderPropertyIDs.RenderScaleLevelsCount, allRenderScales.Count);
  641. debugVisualizationQuadMaterial.SetInt(ShaderPropertyIDs.DefaultRenderViewportScaleLevel, defaultRenderViewportScaleLevel);
  642. debugVisualizationQuadMaterial.SetInt(ShaderPropertyIDs.CurrentRenderViewportScaleLevel, renderViewportScaleSetting.currentValue);
  643. debugVisualizationQuadMaterial.SetInt(ShaderPropertyIDs.CurrentRenderScaleLevel, renderScaleSetting.currentValue);
  644. debugVisualizationQuadMaterial.SetInt(ShaderPropertyIDs.LastFrameIsInBudget, lastFrameIsInBudget);
  645. }
  646. #endregion
  647. #region Private helper classes
  648. private sealed class AdaptiveSetting<T>
  649. {
  650. public T currentValue
  651. {
  652. get { return _currentValue; }
  653. set
  654. {
  655. if (!EqualityComparer<T>.Default.Equals(value, _currentValue))
  656. {
  657. lastChangeFrameCount = Time.frameCount;
  658. }
  659. previousValue = _currentValue;
  660. _currentValue = value;
  661. }
  662. }
  663. public T previousValue { get; private set; }
  664. public int lastChangeFrameCount { get; private set; }
  665. public readonly int increaseFrameCost;
  666. public readonly int decreaseFrameCost;
  667. private T _currentValue;
  668. public AdaptiveSetting(T currentValue, int increaseFrameCost, int decreaseFrameCost)
  669. {
  670. previousValue = currentValue;
  671. this.currentValue = currentValue;
  672. this.increaseFrameCost = increaseFrameCost;
  673. this.decreaseFrameCost = decreaseFrameCost;
  674. }
  675. }
  676. private static class CommandLineArguments
  677. {
  678. public const string Disable = "-noaq";
  679. public const string MinimumRenderScale = "-aqminscale";
  680. public const string MaximumRenderScale = "-aqmaxscale";
  681. public const string MaximumRenderTargetDimension = "-aqmaxres";
  682. public const string RenderScaleFillRateStepSizeInPercent = "-aqfillratestep";
  683. public const string OverrideRenderScaleLevel = "-aqoverride";
  684. public const string DrawDebugVisualization = "-vrdebug";
  685. public const string MSAALevel = "-msaa";
  686. }
  687. private static class KeyboardShortcuts
  688. {
  689. public static readonly KeyCode[] Modifiers = { KeyCode.LeftShift, KeyCode.RightShift };
  690. public const KeyCode ToggleDrawDebugVisualization = KeyCode.F1;
  691. public const KeyCode ToggleOverrideRenderScale = KeyCode.F2;
  692. public const KeyCode DecreaseOverrideRenderScaleLevel = KeyCode.F3;
  693. public const KeyCode IncreaseOverrideRenderScaleLevel = KeyCode.F4;
  694. }
  695. private static class ShaderPropertyIDs
  696. {
  697. public static readonly int RenderScaleLevelsCount = Shader.PropertyToID("_RenderScaleLevelsCount");
  698. public static readonly int DefaultRenderViewportScaleLevel = Shader.PropertyToID("_DefaultRenderViewportScaleLevel");
  699. public static readonly int CurrentRenderViewportScaleLevel = Shader.PropertyToID("_CurrentRenderViewportScaleLevel");
  700. public static readonly int CurrentRenderScaleLevel = Shader.PropertyToID("_CurrentRenderScaleLevel");
  701. public static readonly int LastFrameIsInBudget = Shader.PropertyToID("_LastFrameIsInBudget");
  702. }
  703. private sealed class Timing
  704. {
  705. private readonly float[] buffer = new float[12];
  706. private int bufferIndex;
  707. public void SaveCurrentFrameTiming()
  708. {
  709. bufferIndex = (bufferIndex + 1) % buffer.Length;
  710. buffer[bufferIndex] = VRTK_SharedMethods.GetGPUTimeLastFrame();
  711. }
  712. public float GetFrameTiming(int framesAgo)
  713. {
  714. return buffer[(bufferIndex - framesAgo + buffer.Length) % buffer.Length];
  715. }
  716. public bool WasFrameTimingBad(int framesAgo, float thresholdInMilliseconds, int lastChangeFrameCount, int changeFrameCost)
  717. {
  718. if (!AreFramesAvailable(framesAgo, lastChangeFrameCount, changeFrameCost))
  719. {
  720. // Too early to know
  721. return false;
  722. }
  723. for (int frame = 0; frame < framesAgo; frame++)
  724. {
  725. if (GetFrameTiming(frame) <= thresholdInMilliseconds)
  726. {
  727. return false;
  728. }
  729. }
  730. return true;
  731. }
  732. public bool WasFrameTimingGood(int framesAgo, float thresholdInMilliseconds, int lastChangeFrameCount, int changeFrameCost)
  733. {
  734. if (!AreFramesAvailable(framesAgo, lastChangeFrameCount, changeFrameCost))
  735. {
  736. // Too early to know
  737. return false;
  738. }
  739. for (int frame = 0; frame < framesAgo; frame++)
  740. {
  741. if (GetFrameTiming(frame) > thresholdInMilliseconds)
  742. {
  743. return false;
  744. }
  745. }
  746. return true;
  747. }
  748. public bool WillFrameTimingBeBad(float extrapolationThresholdInMilliseconds, float thresholdInMilliseconds,
  749. int lastChangeFrameCount, int changeFrameCost)
  750. {
  751. if (!AreFramesAvailable(2, lastChangeFrameCount, changeFrameCost))
  752. {
  753. // Too early to know
  754. return false;
  755. }
  756. // Predict next frame's cost using linear extrapolation: max(frame-1 to frame+1, frame-2 to frame+1)
  757. float frameMinus0Timing = GetFrameTiming(0);
  758. if (frameMinus0Timing <= extrapolationThresholdInMilliseconds)
  759. {
  760. return false;
  761. }
  762. float delta = frameMinus0Timing - GetFrameTiming(1);
  763. if (!AreFramesAvailable(3, lastChangeFrameCount, changeFrameCost))
  764. {
  765. delta = Mathf.Max(delta, (frameMinus0Timing - GetFrameTiming(2)) / 2f);
  766. }
  767. return frameMinus0Timing + delta > thresholdInMilliseconds;
  768. }
  769. private static bool AreFramesAvailable(int framesAgo, int lastChangeFrameCount, int changeFrameCost)
  770. {
  771. return Time.frameCount >= framesAgo + lastChangeFrameCount + changeFrameCost;
  772. }
  773. }
  774. #endregion
  775. }
  776. }
  777. #endif